esvalidate
  1  #!/usr/bin/env node
  2  /*
  3    Copyright JS Foundation and other contributors, https://js.foundation/
  4  
  5    Redistribution and use in source and binary forms, with or without
  6    modification, are permitted provided that the following conditions are met:
  7  
  8      * Redistributions of source code must retain the above copyright
  9        notice, this list of conditions and the following disclaimer.
 10      * Redistributions in binary form must reproduce the above copyright
 11        notice, this list of conditions and the following disclaimer in the
 12        documentation and/or other materials provided with the distribution.
 13  
 14    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 15    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 16    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 17    ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 18    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 19    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 20    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 21    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 22    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 23    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 24  */
 25  
 26  /*jslint sloppy:true plusplus:true node:true rhino:true */
 27  /*global phantom:true */
 28  
 29  var fs, system, esprima, options, fnames, forceFile, count;
 30  
 31  if (typeof esprima === 'undefined') {
 32      // PhantomJS can only require() relative files
 33      if (typeof phantom === 'object') {
 34          fs = require('fs');
 35          system = require('system');
 36          esprima = require('./esprima');
 37      } else if (typeof require === 'function') {
 38          fs = require('fs');
 39          try {
 40              esprima = require('esprima');
 41          } catch (e) {
 42              esprima = require('../');
 43          }
 44      } else if (typeof load === 'function') {
 45          try {
 46              load('esprima.js');
 47          } catch (e) {
 48              load('../esprima.js');
 49          }
 50      }
 51  }
 52  
 53  // Shims to Node.js objects when running under PhantomJS 1.7+.
 54  if (typeof phantom === 'object') {
 55      fs.readFileSync = fs.read;
 56      process = {
 57          argv: [].slice.call(system.args),
 58          exit: phantom.exit,
 59          on: function (evt, callback) {
 60              callback();
 61          }
 62      };
 63      process.argv.unshift('phantomjs');
 64  }
 65  
 66  // Shims to Node.js objects when running under Rhino.
 67  if (typeof console === 'undefined' && typeof process === 'undefined') {
 68      console = { log: print };
 69      fs = { readFileSync: readFile };
 70      process = {
 71          argv: arguments,
 72          exit: quit,
 73          on: function (evt, callback) {
 74              callback();
 75          }
 76      };
 77      process.argv.unshift('esvalidate.js');
 78      process.argv.unshift('rhino');
 79  }
 80  
 81  function showUsage() {
 82      console.log('Usage:');
 83      console.log('   esvalidate [options] [file.js...]');
 84      console.log();
 85      console.log('Available options:');
 86      console.log();
 87      console.log('  --format=type  Set the report format, plain (default) or junit');
 88      console.log('  -v, --version  Print program version');
 89      console.log();
 90      process.exit(1);
 91  }
 92  
 93  options = {
 94      format: 'plain'
 95  };
 96  
 97  fnames = [];
 98  
 99  process.argv.splice(2).forEach(function (entry) {
100  
101      if (forceFile || entry === '-' || entry.slice(0, 1) !== '-') {
102          fnames.push(entry);
103      } else if (entry === '-h' || entry === '--help') {
104          showUsage();
105      } else if (entry === '-v' || entry === '--version') {
106          console.log('ECMAScript Validator (using Esprima version', esprima.version, ')');
107          console.log();
108          process.exit(0);
109      } else if (entry.slice(0, 9) === '--format=') {
110          options.format = entry.slice(9);
111          if (options.format !== 'plain' && options.format !== 'junit') {
112              console.log('Error: unknown report format ' + options.format + '.');
113              process.exit(1);
114          }
115      } else if (entry === '--') {
116          forceFile = true;
117      } else {
118          console.log('Error: unknown option ' + entry + '.');
119          process.exit(1);
120      }
121  });
122  
123  if (fnames.length === 0) {
124      fnames.push('');
125  }
126  
127  if (options.format === 'junit') {
128      console.log('<?xml version="1.0" encoding="UTF-8"?>');
129      console.log('<testsuites>');
130  }
131  
132  count = 0;
133  
134  function run(fname, content) {
135      var timestamp, syntax, name;
136      try {
137          if (typeof content !== 'string') {
138              throw content;
139          }
140  
141          if (content[0] === '#' && content[1] === '!') {
142              content = '//' + content.substr(2, content.length);
143          }
144  
145          timestamp = Date.now();
146          syntax = esprima.parse(content, { tolerant: true });
147  
148          if (options.format === 'junit') {
149  
150              name = fname;
151              if (name.lastIndexOf('/') >= 0) {
152                  name = name.slice(name.lastIndexOf('/') + 1);
153              }
154  
155              console.log('<testsuite name="' + fname + '" errors="0" ' +
156                  ' failures="' + syntax.errors.length + '" ' +
157                  ' tests="' + syntax.errors.length + '" ' +
158                  ' time="' + Math.round((Date.now() - timestamp) / 1000) +
159                  '">');
160  
161              syntax.errors.forEach(function (error) {
162                  var msg = error.message;
163                  msg = msg.replace(/^Line\ [0-9]*\:\ /, '');
164                  console.log('  <testcase name="Line ' + error.lineNumber + ': ' + msg + '" ' +
165                      ' time="0">');
166                  console.log('    <error type="SyntaxError" message="' + error.message + '">' +
167                      error.message + '(' + name + ':' + error.lineNumber + ')' +
168                      '</error>');
169                  console.log('  </testcase>');
170              });
171  
172              console.log('</testsuite>');
173  
174          } else if (options.format === 'plain') {
175  
176              syntax.errors.forEach(function (error) {
177                  var msg = error.message;
178                  msg = msg.replace(/^Line\ [0-9]*\:\ /, '');
179                  msg = fname + ':' + error.lineNumber + ': ' + msg;
180                  console.log(msg);
181                  ++count;
182              });
183  
184          }
185      } catch (e) {
186          ++count;
187          if (options.format === 'junit') {
188              console.log('<testsuite name="' + fname + '" errors="1" failures="0" tests="1" ' +
189                  ' time="' + Math.round((Date.now() - timestamp) / 1000) + '">');
190              console.log(' <testcase name="' + e.message + '" ' + ' time="0">');
191              console.log(' <error type="ParseError" message="' + e.message + '">' +
192                  e.message + '(' + fname + ((e.lineNumber) ? ':' + e.lineNumber : '') +
193                  ')</error>');
194              console.log(' </testcase>');
195              console.log('</testsuite>');
196          } else {
197              console.log(fname + ':' + e.lineNumber + ': ' + e.message.replace(/^Line\ [0-9]*\:\ /, ''));
198          }
199      }
200  }
201  
202  fnames.forEach(function (fname) {
203      var content = '';
204      try {
205          if (fname && (fname !== '-' || forceFile)) {
206              content = fs.readFileSync(fname, 'utf-8');
207          } else {
208              fname = '';
209              process.stdin.resume();
210              process.stdin.on('data', function(chunk) {
211                  content += chunk;
212              });
213              process.stdin.on('end', function() {
214                  run(fname, content);
215              });
216              return;
217          }
218      } catch (e) {
219          content = e;
220      }
221      run(fname, content);
222  });
223  
224  process.on('exit', function () {
225      if (options.format === 'junit') {
226          console.log('</testsuites>');
227      }
228  
229      if (count > 0) {
230          process.exit(1);
231      }
232  
233      if (count === 0 && typeof phantom === 'object') {
234          process.exit(0);
235      }
236  });