Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
A perilous thing, a parallel lint
  • Loading branch information
weswigham committed Aug 12, 2016
commit 31adafb7bcfdc81b475245ae060d5bf4b073ec48
159 changes: 74 additions & 85 deletions Jakefile.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ var fs = require("fs");
var os = require("os");
var path = require("path");
var child_process = require("child_process");
var Linter = require("tslint");
var fold = require("travis-fold");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should think about switching Travis over to gulp so we don't have to duplicate effort like this in the future.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like that. Would also stop me from randomly breaking one of them when I
forget to duplicate effort.

On Mon, Aug 15, 2016, 9:53 AM Nathan Shively-Sanders <
notifications@github.com> wrote:

In Jakefile.js
#10313 (comment):

@@ -4,7 +4,6 @@ var fs = require("fs");
var os = require("os");
var path = require("path");
var child_process = require("child_process");
-var Linter = require("tslint");

we should think about switching Travis over to gulp so we don't have to
duplicate effort like this in the future.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/pull/10313/files/949bbf4e343afe9080698cc95bcf1dcf423478a0#r74793060,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ACzAMv6kXQAsxbA8_0eg5PQCd9hCZyyVks5qgJmOgaJpZM4Jjj11
.

var runTestsInParallel = require("./scripts/mocha-parallel").runTestsInParallel;

Expand Down Expand Up @@ -1054,36 +1053,6 @@ task("build-rules-end", [] , function() {
if (fold.isTravis()) console.log(fold.end("build-rules"));
});

function getLinterOptions() {
return {
configuration: require("./tslint.json"),
formatter: "prose",
formattersDirectory: undefined,
rulesDirectory: "built/local/tslint"
};
}

function lintFileContents(options, path, contents) {
var ll = new Linter(path, contents, options);
console.log("Linting '" + path + "'.");
return ll.lint();
}

function lintFile(options, path) {
var contents = fs.readFileSync(path, "utf8");
return lintFileContents(options, path, contents);
}

function lintFileAsync(options, path, cb) {
fs.readFile(path, "utf8", function(err, contents) {
if (err) {
return cb(err);
}
var result = lintFileContents(options, path, contents);
cb(undefined, result);
});
}

var lintTargets = compilerSources
.concat(harnessSources)
// Other harness sources
Expand All @@ -1094,75 +1063,95 @@ var lintTargets = compilerSources
.concat(["Gulpfile.ts"])
.concat([nodeServerInFile, perftscPath, "tests/perfsys.ts", webhostPath]);

function min(collection, predicate) {
var minimum;
var selected;
for (var key in collection) {
var value = predicate(collection[key]);
if (typeof minimum === "undefined" || value <= minimum) {
minimum = value;
selected = collection[key];
}
}
return selected;
}

function spawnLintWorker(bucket, callback) {
var child = child_process.fork("./scripts/parallel-lint");
var failures = 0;
child.on("message", function(data) {
switch (data.kind) {
case "start":
console.log("Linting '" + data.name + "'.");
break;
case "result":
if (data.failures > 0) {
failures += data.failures;
console.log(data.output);
}
break;
case "error":
console.error(data.error);
failures++;
break;
case "complete":
child.unref();
callback(failures);
break;
}
});
child.send({kind: "config", data: bucket.elements});
}

desc("Runs tslint on the compiler sources. Optional arguments are: f[iles]=regex");
task("lint", ["build-rules"], function() {
if (fold.isTravis()) console.log(fold.start("lint"));
var startTime = mark();
var lintOptions = getLinterOptions();
var failed = 0;
var fileMatcher = RegExp(process.env.f || process.env.file || process.env.files || "");
var done = {};
for (var i in lintTargets) {
var target = lintTargets[i];
if (!done[target] && fileMatcher.test(target)) {
var result = lintFile(lintOptions, target);
if (result.failureCount > 0) {
console.log(result.output);
failed += result.failureCount;
}
done[target] = true;
done[target] = fs.statSync(target).size;
}
}
measure(startTime);
if (fold.isTravis()) console.log(fold.end("lint"));
if (failed > 0) {
fail('Linter errors.', failed);
}
});

/**
* This is required because file watches on Windows get fires _twice_
* when a file changes on some node/windows version configuations
* (node v4 and win 10, for example). By not running a lint for a file
* which already has a pending lint, we avoid duplicating our work.
* (And avoid printing duplicate results!)
*/
var lintSemaphores = {};

function lintWatchFile(filename) {
fs.watch(filename, {persistent: true}, function(event) {
if (event !== "change") {
return;
}
var workerCount = (process.env.workerCount && +process.env.workerCount) || os.cpus().length;
var buckets = new Array(workerCount);
for (var i = 0; i < workerCount; i++) {
buckets[i] = {totalSize: 0, elements: []};
}

if (!lintSemaphores[filename]) {
lintSemaphores[filename] = true;
lintFileAsync(getLinterOptions(), filename, function(err, result) {
delete lintSemaphores[filename];
if (err) {
console.log(err);
return;
}
if (result.failureCount > 0) {
console.log("***Lint failure***");
for (var i = 0; i < result.failures.length; i++) {
var failure = result.failures[i];
var start = failure.startPosition.lineAndCharacter;
var end = failure.endPosition.lineAndCharacter;
console.log("warning " + filename + " (" + (start.line + 1) + "," + (start.character + 1) + "," + (end.line + 1) + "," + (end.character + 1) + "): " + failure.failure);
}
console.log("*** Total " + result.failureCount + " failures.");
}
});
}
var names = Object.keys(done).sort(function(namea, nameb) {
return done[namea] < done[nameb];
});
}
for (var i in names) {
var name = names[i];
var bucket = min(buckets, function(b) { return b.totalSize; });
bucket.totalSize += done[name];
bucket.elements.push(name);
}

desc("Watches files for changes to rerun a lint pass");
task("lint-server", ["build-rules"], function() {
console.log("Watching ./src for changes to linted files");
for (var i = 0; i < lintTargets.length; i++) {
lintWatchFile(lintTargets[i]);
console.log(buckets);
for (var i in buckets) {
spawnLintWorker(buckets[i], finished);
}
});

var completed = 0;
var failures = 0;
function finished(fails) {
completed++;
failures += fails;
if (completed === workerCount) {
measure(startTime);
if (fold.isTravis()) console.log(fold.end("lint"));
if (failures > 0) {
fail('Linter errors.', failed);
}
else {
complete();
}
}
}
}, {async: true});
50 changes: 50 additions & 0 deletions scripts/parallel-lint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
var Linter = require("tslint");
var fs = require("fs");

function getLinterOptions() {
return {
configuration: require("../tslint.json"),
formatter: "prose",
formattersDirectory: undefined,
rulesDirectory: "built/local/tslint"
};
}

function lintFileContents(options, path, contents) {
var ll = new Linter(path, contents, options);
return ll.lint();
}

function lintFileAsync(options, path, cb) {
fs.readFile(path, "utf8", function(err, contents) {
if (err) {
return cb(err);
}
process.send({kind: "start", name: path});
var result = lintFileContents(options, path, contents);
cb(undefined, result);
});
}

process.on("message", function(data) {
switch (data.kind) {
case "config":
var files = data.data;
var done = 0;
var lintOptions = getLinterOptions();
files.forEach(function(target) {
lintFileAsync(lintOptions, target, function(err, result) {
if (err) {
process.send({kind: "error", error: err.toString()});
done++;
return;
}
process.send({kind: "result", failures: result.failureCount, output: result.output});
done++;
if (done === files.length) {
process.send({kind: "complete"});
}
});
});
}
});