diff --git a/.vscode/tasks.json b/.vscode/tasks.json index e617f228378..9cf1c9d8f3f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -18,6 +18,25 @@ "problemMatcher": [ "$tsc" ] + }, + { + "taskName": "lint-server", + "args": [], + "problemMatcher": { + "owner": "typescript", + "fileLocation": ["relative", "${workspaceRoot}"], + "pattern": { + "regexp": "^(warning|error)\\s+([^(]+)\\s+\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\):\\s+(.*)$", + "severity": 1, + "file": 2, + "location": 3, + "message": 4 + }, + "watchedTaskBeginsRegExp": "^\\*\\*\\*Lint failure\\*\\*\\*$", + "watchedTaskEndsRegExp": "^\\*\\*\\* Total \\d+ failures\\.$" + }, + "showOutput": "always", + "isWatching": true } ] } \ No newline at end of file diff --git a/Jakefile.js b/Jakefile.js index c84713c18bc..f5ed4902fe7 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -4,6 +4,7 @@ var fs = require("fs"); var os = require("os"); var path = require("path"); var child_process = require("child_process"); +var Linter = require("tslint"); // Variables var compilerDirectory = "src/compiler/"; @@ -828,17 +829,92 @@ tslintRulesFiles.forEach(function(ruleFile, i) { compileFile(tslintRulesOutFiles[i], [ruleFile], [ruleFile], [], /*useBuiltCompiler*/ true, /*noOutFile*/ true, /*generateDeclarations*/ false, path.join(builtLocalDirectory, "tslint")); }); +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 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(harnessCoreSources); + // if the codebase were free of linter errors we could make jake runtests // run this task automatically desc("Runs tslint on the compiler sources"); task("lint", ["build-rules"], function() { - function success(f) { return function() { console.log('SUCCESS: No linter errors in ' + f + '\n'); }}; - function failure(f) { return function() { console.log('FAILURE: Please fix linting errors in ' + f + '\n') }}; - - var lintTargets = compilerSources.concat(harnessCoreSources); + var lintOptions = getLinterOptions(); for (var i in lintTargets) { - var f = lintTargets[i]; - var cmd = 'tslint --rules-dir built/local/tslint -c tslint.json ' + f; - exec(cmd, success(f), failure(f)); + var result = lintFile(lintOptions, lintTargets[i]); + if (result.failureCount > 0) { + console.log(result.output); + } } -}, { async: true }); +}); + +/** + * 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; + } + + 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."); + } + }); + } + }); +} + +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]); + } +});