0
0
mirror of https://github.com/nodejs/node.git synced 2024-11-29 23:16:30 +01:00
nodejs/tools/lint-js.js
Ruben Bridgewater 5f71520d10
tools: increase the maximum number of files to lint per worker
This increases the maximum number of files to lint per worker from
40 to 60 files. This should ideally reduce the total linting time
a tiny bit.

PR-URL: https://github.com/nodejs/node/pull/27670
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Richard Lau <riclau@uk.ibm.com>
Reviewed-By: Refael Ackermann (רפאל פלחי) <refack@gmail.com>
Reviewed-By: Daijiro Wachi <daijiro.wachi@gmail.com>
Reviewed-By: Ujjwal Sharma <usharma1998@gmail.com>
2019-06-11 15:59:20 +02:00

273 lines
7.8 KiB
JavaScript

'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 = 60;
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 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) {
let numCPUs = 1;
const paths = [];
let files = null;
let totalPaths = 0;
let failures = 0;
let successes = 0;
let lastLineLen = 0;
let curPath = 'Starting ...';
let showProgress = true;
const globOptions = {
nodir: true,
ignore: '**/node_modules/**/*'
};
const workerConfig = {};
let startTime;
let formatter;
let outFn;
let fd;
let 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 ESLint 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 ESLint 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');
const outPath = path.resolve(process.argv[i + 1]);
fd = fs.openSync(outPath, 'w');
outFn = function(str) {
fs.writeSync(fd, str, 'utf8');
};
process.on('exit', () => { 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', () => {
startTime = process.hrtime();
setInterval(printProgress, 1000).unref();
printProgress();
});
}
cluster.on('online', (worker) => {
// Configure worker and give it some initial work to do
worker.send(workerConfig);
sendWork(worker);
});
process.on('exit', (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) {
let dir = paths.shift();
curPath = 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));
let slice;
if (sliceLen === files.length) {
// Micro-optimization 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 = `${Math.floor(elapsed / 60)}`.padStart(2, '0');
const secs = `${elapsed % 60}`.padStart(2, '0');
const passed = `${successes}`.padStart(6);
const failed = `${failures}`.padStart(6);
let pct = `${Math.ceil(((totalPaths - paths.length) / totalPaths) * 100)}`;
pct = pct.padStart(3);
let 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);
}
} else {
// Worker
let config = {};
process.on('message', (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 (let 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;
}
});
}