summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorBrian White <mscdex@mscdex.net>2016-03-09 22:40:54 -0500
committerBrian White <mscdex@mscdex.net>2016-04-15 00:40:02 -0400
commit81fd4581b922079cf059d336d44272c288ea8fdf (patch)
tree9d9ae254e66676aa159541180d009039c2c35a39 /tools
parent00b221942009e0f438275bd482cf15f4ee8521b5 (diff)
downloadandroid-node-v8-81fd4581b922079cf059d336d44272c288ea8fdf.tar.gz
android-node-v8-81fd4581b922079cf059d336d44272c288ea8fdf.tar.bz2
android-node-v8-81fd4581b922079cf059d336d44272c288ea8fdf.zip
tools: improve js linter
This commit switches from the eslint command-line tool to a custom tool that uses eslint programmatically in order to perform linting in parallel and to display linting results incrementally instead of buffering them until the end. Fixes: https://github.com/nodejs/node/issues/5596 PR-URL: https://github.com/nodejs/node/pull/5638 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Johan Bergström <bugs@bergstroem.nu>
Diffstat (limited to 'tools')
-rw-r--r--tools/jslint.js262
1 files changed, 262 insertions, 0 deletions
diff --git a/tools/jslint.js b/tools/jslint.js
new file mode 100644
index 0000000000..7cd2fd7bcb
--- /dev/null
+++ b/tools/jslint.js
@@ -0,0 +1,262 @@
+'use strict';
+
+const rulesDirs = ['tools/eslint-rules'];
+// This is the maximum number of files to be linted per worker at any given time
+const maxWorkload = 40;
+
+const cluster = require('cluster');
+const path = require('path');
+const fs = require('fs');
+const totalCPUs = require('os').cpus().length;
+
+const CLIEngine = require('./eslint').CLIEngine;
+const glob = require('./eslint/node_modules/glob');
+
+const cwd = process.cwd();
+const cli = new CLIEngine({
+ rulePaths: rulesDirs
+});
+
+if (cluster.isMaster) {
+ var numCPUs = 1;
+ const paths = [];
+ var files = null;
+ var totalPaths = 0;
+ var failures = 0;
+ var successes = 0;
+ var lastLineLen = 0;
+ var curPath = 'Starting ...';
+ var showProgress = true;
+ const globOptions = {
+ nodir: true
+ };
+ const workerConfig = {};
+ var startTime;
+ var formatter;
+ var outFn;
+ var fd;
+ var i;
+
+ // Check if spreading work among all cores/cpus
+ if (process.argv.indexOf('-J') !== -1)
+ numCPUs = totalCPUs;
+
+ // Check if spreading work among an explicit number of cores/cpus
+ i = process.argv.indexOf('-j');
+ if (i !== -1) {
+ if (!process.argv[i + 1])
+ throw new Error('Missing parallel job count');
+ numCPUs = parseInt(process.argv[i + 1], 10);
+ if (!isFinite(numCPUs) || numCPUs <= 0)
+ throw new Error('Bad parallel job count');
+ }
+
+ // Check for custom JSLint report formatter
+ i = process.argv.indexOf('-f');
+ if (i !== -1) {
+ if (!process.argv[i + 1])
+ throw new Error('Missing format name');
+ const format = process.argv[i + 1];
+ formatter = cli.getFormatter(format);
+ if (!formatter)
+ throw new Error('Invalid format name');
+ // Automatically disable progress display
+ showProgress = false;
+ // Tell worker to send all results, not just linter errors
+ workerConfig.sendAll = true;
+ } else {
+ // Use default formatter
+ formatter = cli.getFormatter();
+ }
+
+ // Check if outputting JSLint report to a file instead of stdout
+ i = process.argv.indexOf('-o');
+ if (i !== -1) {
+ if (!process.argv[i + 1])
+ throw new Error('Missing output filename');
+ var outPath = process.argv[i + 1];
+ if (!path.isAbsolute(outPath))
+ outPath = path.join(cwd, outPath);
+ fd = fs.openSync(outPath, 'w');
+ outFn = function(str) {
+ fs.writeSync(fd, str, 'utf8');
+ };
+ process.on('exit', function() {
+ fs.closeSync(fd);
+ });
+ } else {
+ outFn = function(str) {
+ process.stdout.write(str);
+ };
+ }
+
+ // Process the rest of the arguments as paths to lint, ignoring any unknown
+ // flags
+ for (i = 2; i < process.argv.length; ++i) {
+ if (process.argv[i][0] === '-') {
+ switch (process.argv[i]) {
+ case '-f': // Skip format name
+ case '-o': // Skip filename
+ case '-j': // Skip parallel job count number
+ ++i;
+ break;
+ }
+ continue;
+ }
+ paths.push(process.argv[i]);
+ }
+
+ if (paths.length === 0)
+ return;
+ totalPaths = paths.length;
+
+ if (showProgress) {
+ // Start the progress display update timer when the first worker is ready
+ cluster.once('online', function(worker) {
+ startTime = process.hrtime();
+ setInterval(printProgress, 1000).unref();
+ printProgress();
+ });
+ }
+
+ cluster.on('online', function(worker) {
+ // Configure worker and give it some initial work to do
+ worker.send(workerConfig);
+ sendWork(worker);
+ });
+
+ cluster.on('message', function(worker, results) {
+ if (typeof results !== 'number') {
+ // The worker sent us results that are not all successes
+ if (!workerConfig.sendAll)
+ failures += results.length;
+ outFn(formatter(results) + '\r\n');
+ printProgress();
+ } else {
+ successes += results;
+ }
+ // Try to give the worker more work to do
+ sendWork(worker);
+ });
+
+ process.on('exit', function() {
+ if (showProgress) {
+ curPath = 'Done';
+ printProgress();
+ outFn('\r\n');
+ }
+ process.exit(failures ? 1 : 0);
+ });
+
+ for (i = 0; i < numCPUs; ++i)
+ cluster.fork();
+
+ function sendWork(worker) {
+ if (!files || !files.length) {
+ // We either just started or we have no more files to lint for the current
+ // path. Find the next path that has some files to be linted.
+ while (paths.length) {
+ var dir = paths.shift();
+ curPath = dir;
+ if (dir.indexOf('/') > 0)
+ dir = path.join(cwd, dir);
+ const patterns = cli.resolveFileGlobPatterns([dir]);
+ dir = path.resolve(patterns[0]);
+ files = glob.sync(dir, globOptions);
+ if (files.length)
+ break;
+ }
+ if ((!files || !files.length) && !paths.length) {
+ // We exhausted all input paths and thus have nothing left to do, so end
+ // the worker
+ return worker.disconnect();
+ }
+ }
+ // Give the worker an equal portion of the work left for the current path,
+ // but not exceeding a maximum file count in order to help keep *all*
+ // workers busy most of the time instead of only a minority doing most of
+ // the work.
+ const sliceLen = Math.min(maxWorkload, Math.ceil(files.length / numCPUs));
+ var slice;
+ if (sliceLen === files.length) {
+ // Micro-ptimization to avoid splicing to an empty array
+ slice = files;
+ files = null;
+ } else {
+ slice = files.splice(0, sliceLen);
+ }
+ worker.send(slice);
+ }
+
+ function printProgress() {
+ if (!showProgress)
+ return;
+
+ // Clear line
+ outFn('\r' + ' '.repeat(lastLineLen) + '\r');
+
+ // Calculate and format the data for displaying
+ const elapsed = process.hrtime(startTime)[0];
+ const mins = padString(Math.floor(elapsed / 60), 2, '0');
+ const secs = padString(elapsed % 60, 2, '0');
+ const passed = padString(successes, 6, ' ');
+ const failed = padString(failures, 6, ' ');
+ var pct = Math.ceil(((totalPaths - paths.length) / totalPaths) * 100);
+ pct = padString(pct, 3, ' ');
+
+ var line = `[${mins}:${secs}|%${pct}|+${passed}|-${failed}]: ${curPath}`;
+
+ // Truncate line like cpplint does in case it gets too long
+ if (line.length > 75)
+ line = line.slice(0, 75) + '...';
+
+ // Store the line length so we know how much to erase the next time around
+ lastLineLen = line.length;
+
+ outFn(line);
+ }
+
+ function padString(str, len, chr) {
+ str = '' + str;
+ if (str.length >= len)
+ return str;
+ return chr.repeat(len - str.length) + str;
+ }
+} else {
+ // Worker
+
+ var config = {};
+ process.on('message', function(files) {
+ if (files instanceof Array) {
+ // Lint some files
+ const report = cli.executeOnFiles(files);
+ if (config.sendAll) {
+ // Return both success and error results
+
+ const results = report.results;
+ // Silence warnings for files with no errors while keeping the "ok"
+ // status
+ if (report.warningCount > 0) {
+ for (var i = 0; i < results.length; ++i) {
+ const result = results[i];
+ if (result.errorCount === 0 && result.warningCount > 0) {
+ result.warningCount = 0;
+ result.messages = [];
+ }
+ }
+ }
+ process.send(results);
+ } else if (report.errorCount === 0) {
+ // No errors, return number of successful lint operations
+ process.send(files.length);
+ } else {
+ // One or more errors, return the error results only
+ process.send(CLIEngine.getErrorResults(report.results));
+ }
+ } else if (typeof files === 'object') {
+ // The master process is actually sending us our configuration and not a
+ // list of files to lint
+ config = files;
+ }
+ });
+}