2014-12-05 19:26:30 -05:00
|
|
|
var Q = require('q');
|
|
|
|
var readline = require('readline');
|
|
|
|
var spawn = require('child_process').spawn;
|
|
|
|
var path = require('path');
|
|
|
|
var glob = require('glob');
|
|
|
|
var fs = require('fs');
|
2015-09-26 18:36:11 -04:00
|
|
|
var travisFoldStart = require('../travis/travis-fold');
|
2015-02-20 20:44:23 -05:00
|
|
|
var util = require('./util');
|
2015-04-27 17:39:54 -04:00
|
|
|
var yaml = require('js-yaml');
|
2014-12-05 19:26:30 -05:00
|
|
|
|
|
|
|
module.exports = function(gulp, plugins, config) {
|
|
|
|
return function() {
|
2015-09-26 18:36:11 -04:00
|
|
|
var travisFoldEnd = travisFoldStart(`dartanalyzer-${config.use_ddc ? 'ddc' : ''}-${config.dest}`);
|
2014-12-05 19:26:30 -05:00
|
|
|
var tempFile = '_analyzer.dart';
|
2015-09-26 18:36:11 -04:00
|
|
|
|
2015-06-01 20:51:37 -04:00
|
|
|
return util.forEachSubDirSequential(config.dest, function(dir) {
|
|
|
|
var pubspecContents = fs.readFileSync(path.join(dir, 'pubspec.yaml'));
|
|
|
|
var pubspec = yaml.safeLoad(pubspecContents);
|
|
|
|
var packageName = pubspec.name;
|
|
|
|
|
|
|
|
var libFiles = [].slice.call(glob.sync('lib/**/*.dart', {cwd: dir}));
|
|
|
|
var webFiles = [].slice.call(glob.sync('web/**/*.dart', {cwd: dir}));
|
|
|
|
var testFiles = [].slice.call(glob.sync('test/**/*_spec.dart', {cwd: dir}));
|
2015-06-03 20:22:30 -04:00
|
|
|
|
2015-06-01 20:51:37 -04:00
|
|
|
var analyzeFile = ['library _analyzer;'];
|
|
|
|
libFiles.concat(testFiles).concat(webFiles).forEach(function(fileName, index) {
|
|
|
|
if (fileName !== tempFile && fileName.indexOf("/packages/") === -1) {
|
|
|
|
if (fileName.indexOf('lib') == 0) {
|
2015-06-23 11:18:04 -04:00
|
|
|
fileName = 'package:' + packageName + '/' + path.relative('lib', fileName).replace(/\\/g, '/');
|
2015-02-20 20:44:23 -05:00
|
|
|
}
|
2015-06-01 20:51:37 -04:00
|
|
|
analyzeFile.push('import "' + fileName + '" as mod' + index + ';');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
fs.writeFileSync(path.join(dir, tempFile), analyzeFile.join('\n'));
|
|
|
|
var defer = Q.defer();
|
2015-06-03 20:22:30 -04:00
|
|
|
if (config.use_ddc) {
|
|
|
|
analyze(dir, defer.makeNodeResolver(), true);
|
|
|
|
} else {
|
|
|
|
analyze(dir, defer.makeNodeResolver());
|
|
|
|
}
|
2015-06-01 20:51:37 -04:00
|
|
|
return defer.promise;
|
2015-09-26 18:36:11 -04:00
|
|
|
}).then(travisFoldEnd);
|
2014-12-05 19:26:30 -05:00
|
|
|
|
2015-06-03 20:22:30 -04:00
|
|
|
function analyze(dirName, done, useDdc) {
|
2015-06-01 20:51:37 -04:00
|
|
|
// TODO remove --package-warnings once dartanalyzer handles transitive libraries
|
2015-06-03 20:22:30 -04:00
|
|
|
var flags = ['--fatal-warnings', '--package-warnings', '--format=machine'];
|
|
|
|
|
|
|
|
if (useDdc) {
|
|
|
|
console.log('Using DDC analyzer to analyze', dirName);
|
|
|
|
flags.push('--strong');
|
|
|
|
}
|
|
|
|
|
|
|
|
var args = flags.concat(tempFile);
|
2015-04-15 21:17:12 -04:00
|
|
|
|
|
|
|
var stream = spawn(config.command, args, {
|
2014-12-05 19:26:30 -05:00
|
|
|
// inherit stdin and stderr, but filter stdout
|
2015-05-14 16:14:45 -04:00
|
|
|
stdio: [process.stdin, process.stdout, 'pipe'],
|
2014-12-05 19:26:30 -05:00
|
|
|
cwd: dirName
|
|
|
|
});
|
|
|
|
// Filter out unused imports from our generated file.
|
|
|
|
// We don't reexports from the generated file
|
|
|
|
// as this could lead to name clashes when two files
|
|
|
|
// export the same thing.
|
2015-06-01 20:51:37 -04:00
|
|
|
var rl = readline.createInterface(
|
|
|
|
{input: stream.stderr, output: process.stdout, terminal: false});
|
2014-12-05 19:26:30 -05:00
|
|
|
var hintCount = 0;
|
2014-12-29 21:09:30 -05:00
|
|
|
var errorCount = 0;
|
2015-04-10 19:52:41 -04:00
|
|
|
var warningCount = 0;
|
2014-12-05 19:26:30 -05:00
|
|
|
rl.on('line', function(line) {
|
2015-06-23 11:18:04 -04:00
|
|
|
if (line == "find: > bin [: No such file or directory") {
|
|
|
|
//Skip bad output from Dart SDK .bat files on Windows
|
|
|
|
return;
|
|
|
|
}
|
2015-05-14 16:14:45 -04:00
|
|
|
var parsedLine = _AnalyzerOutputLine.parse(line);
|
|
|
|
if (!parsedLine) {
|
|
|
|
errorCount++;
|
|
|
|
console.log('Unexpected output: ' + line);
|
|
|
|
return;
|
|
|
|
}
|
2015-06-01 20:51:37 -04:00
|
|
|
// TODO remove once dartanalyzer handles transitive libraries
|
|
|
|
// skip errors in third-party packages
|
|
|
|
if (parsedLine.sourcePath.indexOf(dirName) == -1) {
|
2014-12-29 21:09:30 -05:00
|
|
|
return;
|
|
|
|
}
|
2015-05-14 16:14:45 -04:00
|
|
|
if (parsedLine.shouldIgnore()) {
|
2015-05-19 13:05:18 -04:00
|
|
|
return;
|
|
|
|
}
|
2015-02-18 17:33:34 -05:00
|
|
|
|
2015-05-14 16:14:45 -04:00
|
|
|
if (parsedLine.isHint) {
|
|
|
|
hintCount++;
|
|
|
|
} else if (parsedLine.isWarning) {
|
|
|
|
warningCount++;
|
|
|
|
} else {
|
2015-06-01 20:51:37 -04:00
|
|
|
errorCount++;
|
2014-12-05 19:26:30 -05:00
|
|
|
}
|
2015-05-14 16:14:45 -04:00
|
|
|
console.log(dirName + ':' + parsedLine);
|
2014-12-05 19:26:30 -05:00
|
|
|
});
|
2014-12-29 21:09:30 -05:00
|
|
|
stream.on('close', function() {
|
2014-12-05 19:26:30 -05:00
|
|
|
var error;
|
2015-04-10 19:52:41 -04:00
|
|
|
var report = [];
|
2014-12-29 21:09:30 -05:00
|
|
|
if (errorCount > 0) {
|
2015-04-10 19:52:41 -04:00
|
|
|
report.push(errorCount + ' error(s)');
|
|
|
|
}
|
|
|
|
if (warningCount > 0) {
|
|
|
|
report.push(warningCount + ' warning(s)');
|
2014-12-05 19:26:30 -05:00
|
|
|
}
|
|
|
|
if (hintCount > 0) {
|
2015-04-10 19:52:41 -04:00
|
|
|
report.push(hintCount + ' hint(s)');
|
|
|
|
}
|
|
|
|
if (report.length > 0) {
|
|
|
|
error = 'Dartanalyzer showed ' + report.join(', ');
|
2014-12-05 19:26:30 -05:00
|
|
|
}
|
2015-08-19 18:29:36 -04:00
|
|
|
done(error);
|
2015-06-03 20:22:30 -04:00
|
|
|
});
|
2015-08-19 18:29:36 -04:00
|
|
|
stream.on('error', function(error) {
|
|
|
|
done(error);
|
2014-12-05 19:26:30 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
2015-05-14 16:14:45 -04:00
|
|
|
|
|
|
|
// See https://github.com/dart-lang/analyzer_cli/blob/master/lib/src/error_formatter.dart
|
|
|
|
function _AnalyzerOutputLine(result) {
|
|
|
|
this.severity = result[1];
|
|
|
|
this.errorType = result[2];
|
|
|
|
this.errorCode = result[3];
|
2015-06-01 20:51:37 -04:00
|
|
|
this.sourcePath = result[4];
|
2015-05-14 16:14:45 -04:00
|
|
|
this.lineNum = result[5];
|
|
|
|
this.colNum = result[6];
|
2015-06-01 20:51:37 -04:00
|
|
|
this.asciiLineLength = parseInt(result[7], 10);
|
|
|
|
this.errorMsg = result[8];
|
2015-05-14 16:14:45 -04:00
|
|
|
|
|
|
|
this.isError = Boolean(this.severity.match(/ERROR/i));
|
|
|
|
this.isHint = Boolean(this.severity.match(/INFO/i));
|
|
|
|
this.isWarning = Boolean(this.severity.match(/WARNING/i));
|
|
|
|
}
|
|
|
|
|
|
|
|
_AnalyzerOutputLine.parse = function(line) {
|
|
|
|
var result = _AnalyzerOutputLine._analyzerParseRegExp.exec(line);
|
|
|
|
return result ? new _AnalyzerOutputLine(result) : null;
|
|
|
|
};
|
|
|
|
|
2015-06-01 20:51:37 -04:00
|
|
|
_AnalyzerOutputLine._analyzerParseRegExp =
|
|
|
|
new RegExp('([^\|]+)\\|' + // #1, severity (NONE, INFO, WARNING, ERROR)
|
|
|
|
'([^\|]+)\\|' + // #2, errorCode.type (HINT, *_WARNING, *_ERROR, etc)
|
|
|
|
'([^\|]+)\\|' + // #3, errorCode (UNUSED_IMPORT, UNUSED_CATCH_STACK, etc)
|
|
|
|
'([^\|]+[^\|\\\\])\\|' + // #4, sourcePath with '|' chars backslash-escaped.
|
|
|
|
'([^\|]+)\\|' + // #5, line number
|
|
|
|
'([^\|]+)\\|' + // #6, column number
|
|
|
|
'([^\|]+)\\|' + // #7, length of the ASCII line to draw
|
|
|
|
'(.*)$'); // #8, error message
|
|
|
|
|
|
|
|
/* Maps file path (as string) to file source (an array of strings, one per line). */
|
|
|
|
_AnalyzerOutputLine.cache = {};
|
|
|
|
|
|
|
|
_AnalyzerOutputLine.ERR_NO_SOURCE = '(Could not find source line).';
|
2015-05-14 16:14:45 -04:00
|
|
|
|
|
|
|
_AnalyzerOutputLine.prototype = {
|
|
|
|
toString: function() {
|
2015-06-01 20:51:37 -04:00
|
|
|
var sourceLine = this._getSourceLine();
|
|
|
|
var lineText = _AnalyzerOutputLine.ERR_NO_SOURCE;
|
|
|
|
if (sourceLine) {
|
|
|
|
var repeat = function(str, num) {
|
|
|
|
if (str.repeat) return str.repeat(num);
|
|
|
|
return Array.prototype.join.call({length: num}, str);
|
|
|
|
};
|
|
|
|
|
|
|
|
lineText =
|
|
|
|
'\n' + sourceLine + '\n' + repeat(' ', this.colNum) + repeat('^', this.asciiLineLength);
|
|
|
|
}
|
|
|
|
return '[' + this.severity + '] ' + this.errorMsg + ' (' + this.sourcePath + ', line ' +
|
|
|
|
this.lineNum + ', col ' + this.colNum + ')' + lineText;
|
2015-05-14 16:14:45 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
shouldIgnore: function() {
|
|
|
|
if (this.errorCode.match(/UNUSED_IMPORT/i)) {
|
2015-06-01 20:51:37 -04:00
|
|
|
if (this.sourcePath.match(/_analyzer\.dart/)) {
|
2015-05-14 16:14:45 -04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2015-10-02 19:21:49 -04:00
|
|
|
|
|
|
|
if (this.errorCode.match(/DEPRECATED_MEMBER_USE/i)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-05-14 16:14:45 -04:00
|
|
|
// TODO: https://github.com/angular/ts2dart/issues/168
|
|
|
|
if (this.errorCode.match(/UNUSED_CATCH_STACK/i)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't worry about hints in generated files.
|
2015-06-01 20:51:37 -04:00
|
|
|
if (this.isHint && this.sourcePath.match(/generated/i)) {
|
2015-05-14 16:14:45 -04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2015-06-01 20:51:37 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
// Reads the source file for the Analyzer output, caching it for future use.
|
|
|
|
_getSourceLine: function() {
|
|
|
|
var cache = _AnalyzerOutputLine.cache;
|
|
|
|
var sourceLines = null;
|
|
|
|
if (cache.hasOwnProperty(this.sourcePath)) {
|
|
|
|
sourceLines = cache[this.sourcePath];
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
sourceLines = String(fs.readFileSync(this.sourcePath));
|
|
|
|
sourceLines = sourceLines.split('\n');
|
|
|
|
} catch (e) {
|
|
|
|
sourceLines = null;
|
|
|
|
} finally {
|
|
|
|
// Even if this fails, cache `null` so we don't try again.
|
|
|
|
cache[this.sourcePath] = sourceLines;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sourceLines && this.lineNum <= sourceLines.length ? sourceLines[this.lineNum - 1] : null;
|
2015-05-14 16:14:45 -04:00
|
|
|
}
|
|
|
|
};
|