You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

286 lines
8.1 KiB

'use strict';
const rulesDirs = ['tools/eslint-rules'];
const extensions = ['.js', '.md'];
// 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 cliOptions = {
rulePaths: rulesDirs,
extensions: extensions,
};
// Check if we should fix errors that are fixable
if (process.argv.indexOf('-F') !== -1)
cliOptions.fix = true;
const cli = new CLIEngine(cliOptions);
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);
});
process.on('exit', function(code) {
if (showProgress) {
curPath = 'Done';
printProgress();
outFn('\r\n');
}
if (code === 0)
process.exit(failures ? 1 : 0);
});
for (i = 0; i < numCPUs; ++i)
cluster.fork().on('message', onWorkerMessage).on('exit', onWorkerExit);
function onWorkerMessage(results) {
if (typeof results !== 'number') {
// The worker sent us results that are not all successes
if (workerConfig.sendAll) {
failures += results.errorCount;
results = results.results;
} else {
failures += results.length;
}
outFn(formatter(results) + '\r\n');
printProgress();
} else {
successes += results;
}
// Try to give the worker more work to do
sendWork(this);
}
function onWorkerExit(code, signal) {
if (code !== 0 || signal)
process.exit(2);
}
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 we were asked to fix the fixable issues, do so.
if (cliOptions.fix)
CLIEngine.outputFixes(report);
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: results, errorCount: report.errorCount });
} 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;
}
});
}