2016-06-03 15:27:17 -07:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
|
2016-03-17 18:17:58 +01:00
|
|
|
'use strict';
|
2016-08-13 23:12:07 +02:00
|
|
|
|
2016-03-17 18:17:58 +01:00
|
|
|
/*
|
2016-08-13 23:12:07 +02:00
|
|
|
* The forbidden identifiers script will check for blocked statements and also detect invalid
|
|
|
|
|
* imports of other scope packages.
|
|
|
|
|
*
|
|
|
|
|
* When running against a PR, the script will only analyze the specific amount of commits inside
|
|
|
|
|
* of the Pull Request.
|
|
|
|
|
*
|
|
|
|
|
* By default it checks all source files and fail if any errors were found.
|
2016-03-17 18:17:58 +01:00
|
|
|
*/
|
|
|
|
|
|
2016-04-03 14:23:17 -07:00
|
|
|
const child_process = require('child_process');
|
|
|
|
|
const fs = require('fs');
|
2016-07-07 07:25:47 +02:00
|
|
|
const path = require('path');
|
2016-08-13 23:12:07 +02:00
|
|
|
const glob = require('glob').sync;
|
2016-04-03 14:23:17 -07:00
|
|
|
|
|
|
|
|
const blocked_statements = [
|
|
|
|
|
'\\bddescribe\\(',
|
|
|
|
|
'\\bfdescribe\\(',
|
|
|
|
|
'\\biit\\(',
|
|
|
|
|
'\\bfit\\(',
|
|
|
|
|
'\\bxdescribe\\(',
|
|
|
|
|
'\\bxit\\(',
|
2016-06-03 15:27:17 -07:00
|
|
|
'\\bdebugger;',
|
|
|
|
|
'from \\\'rxjs/Rx\\\'',
|
2016-11-29 20:18:41 +01:00
|
|
|
'\\r'
|
2016-03-17 18:17:58 +01:00
|
|
|
];
|
|
|
|
|
|
2016-08-13 23:12:07 +02:00
|
|
|
const sourceFolders = ['./src', './e2e'];
|
2016-04-03 14:23:17 -07:00
|
|
|
const blockedRegex = new RegExp(blocked_statements.join('|'), 'g');
|
|
|
|
|
|
2016-08-13 23:12:07 +02:00
|
|
|
/*
|
|
|
|
|
* Verify that the current PR is not adding any forbidden identifiers.
|
|
|
|
|
* Run the forbidden identifiers check against all sources when not verifying a PR.
|
2016-04-03 14:23:17 -07:00
|
|
|
*/
|
2016-08-13 23:12:07 +02:00
|
|
|
|
|
|
|
|
findTestFiles()
|
|
|
|
|
|
|
|
|
|
/* Only match .js or .ts, and remove .d.ts files. */
|
|
|
|
|
.then(files => files.filter(name => /\.(js|ts)$/.test(name) && !/\.d\.ts$/.test(name)))
|
|
|
|
|
|
|
|
|
|
/* Read content of the filtered files */
|
|
|
|
|
.then(files => files.map(name => ({ name, content: fs.readFileSync(name, 'utf-8') })))
|
|
|
|
|
|
|
|
|
|
/* Run checks against content of the filtered files. */
|
2016-11-29 20:18:41 +01:00
|
|
|
.then(diffList => findErrors(diffList))
|
2016-08-13 23:12:07 +02:00
|
|
|
|
2016-11-29 20:18:41 +01:00
|
|
|
/* Groups similar errors to simplify console output */
|
|
|
|
|
.then(errors => groupErrors(errors))
|
2016-08-13 23:12:07 +02:00
|
|
|
|
2016-11-29 20:18:41 +01:00
|
|
|
/* Print the resolved errors to the console */
|
|
|
|
|
.then(errors => printErrors(errors))
|
|
|
|
|
|
|
|
|
|
.catch(err => {
|
|
|
|
|
// An error occurred in this script. Output the error and the stack.
|
|
|
|
|
console.error(err.stack || err);
|
|
|
|
|
process.exit(2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Finds errors inside of changed files by running them against the blocked statements.
|
|
|
|
|
* @param {{name: string, content: string}[]} diffList
|
|
|
|
|
*/
|
|
|
|
|
function findErrors(diffList) {
|
|
|
|
|
return diffList.reduce((errors, diffFile) => {
|
|
|
|
|
let fileName = diffFile.name;
|
|
|
|
|
let content = diffFile.content.split('\n');
|
|
|
|
|
let lineNumber = 0;
|
2016-08-13 23:12:07 +02:00
|
|
|
|
2016-11-29 20:18:41 +01:00
|
|
|
content.forEach(line => {
|
|
|
|
|
lineNumber++;
|
2016-08-13 23:12:07 +02:00
|
|
|
|
2016-11-29 20:18:41 +01:00
|
|
|
let matches = line.match(blockedRegex);
|
2016-08-13 23:12:07 +02:00
|
|
|
|
2016-11-29 20:18:41 +01:00
|
|
|
if (matches) {
|
2016-08-13 23:12:07 +02:00
|
|
|
|
2016-11-29 20:18:41 +01:00
|
|
|
errors.push({
|
|
|
|
|
fileName,
|
|
|
|
|
lineNumber,
|
|
|
|
|
statement: matches[0]
|
|
|
|
|
});
|
2016-08-13 23:12:07 +02:00
|
|
|
|
2016-11-29 20:18:41 +01:00
|
|
|
}
|
|
|
|
|
});
|
2016-08-13 23:12:07 +02:00
|
|
|
|
2016-11-29 20:18:41 +01:00
|
|
|
return errors;
|
2016-08-13 23:12:07 +02:00
|
|
|
|
2016-11-29 20:18:41 +01:00
|
|
|
}, []);
|
|
|
|
|
}
|
2016-08-13 23:12:07 +02:00
|
|
|
|
2016-11-29 20:18:41 +01:00
|
|
|
/**
|
|
|
|
|
* Groups similar errors in the same file which are present over a range of lines.
|
|
|
|
|
* @param {{fileName: string, lineNumber: number, statement: string}[]} errors
|
|
|
|
|
*/
|
|
|
|
|
function groupErrors(errors) {
|
2016-08-13 23:12:07 +02:00
|
|
|
|
2016-11-29 20:18:41 +01:00
|
|
|
let initialError, initialLine, previousLine;
|
2016-08-13 23:12:07 +02:00
|
|
|
|
2016-11-29 20:18:41 +01:00
|
|
|
return errors.filter(error => {
|
2016-08-13 23:12:07 +02:00
|
|
|
|
2016-11-29 20:18:41 +01:00
|
|
|
if (initialError && isSimilarError(error)) {
|
|
|
|
|
previousLine = error.lineNumber;
|
|
|
|
|
|
|
|
|
|
// Overwrite the lineNumber with a string, which indicates the range of lines.
|
|
|
|
|
initialError.lineNumber = `${initialLine}-${previousLine}`;
|
|
|
|
|
|
|
|
|
|
return false;
|
2016-08-13 23:12:07 +02:00
|
|
|
}
|
|
|
|
|
|
2016-11-29 20:18:41 +01:00
|
|
|
initialLine = previousLine = error.lineNumber;
|
|
|
|
|
initialError = error;
|
|
|
|
|
|
|
|
|
|
return true;
|
2016-08-13 23:12:07 +02:00
|
|
|
});
|
|
|
|
|
|
2016-11-29 20:18:41 +01:00
|
|
|
/** Checks whether the given error is similar to the previous one. */
|
|
|
|
|
function isSimilarError(error) {
|
|
|
|
|
return initialError.fileName === error.fileName &&
|
|
|
|
|
initialError.statement === error.statement &&
|
|
|
|
|
previousLine === (error.lineNumber - 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Prints all errors to the console and terminates the process if errors were present.
|
|
|
|
|
* @param {{fileName: string, lineNumber: number, statement: string}[]} errors
|
|
|
|
|
*/
|
|
|
|
|
function printErrors(errors) {
|
|
|
|
|
if (errors.length > 0) {
|
|
|
|
|
|
|
|
|
|
console.error('Error: You are using one or more blocked statements:\n');
|
|
|
|
|
|
|
|
|
|
errors.forEach(entry => {
|
|
|
|
|
|
|
|
|
|
// Stringify the statement to represent line-endings or other unescaped characters.
|
|
|
|
|
let statement = JSON.stringify(entry.statement);
|
|
|
|
|
|
|
|
|
|
console.error(` ${entry.fileName}@${entry.lineNumber}, Statement: ${statement}.\n`);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Exit the process with an error exit code to notify the CI.
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-04-03 14:23:17 -07:00
|
|
|
|
|
|
|
|
/**
|
2016-08-13 23:12:07 +02:00
|
|
|
* Resolves all files, which should run against the forbidden identifiers check.
|
|
|
|
|
* @return {Promise.<Array.<string>>} Files to be checked.
|
2016-04-03 14:23:17 -07:00
|
|
|
*/
|
2016-08-13 23:12:07 +02:00
|
|
|
function findTestFiles() {
|
|
|
|
|
if (process.env['TRAVIS_PULL_REQUEST']) {
|
|
|
|
|
return findChangedFiles();
|
2016-04-03 14:23:17 -07:00
|
|
|
}
|
2016-08-13 23:12:07 +02:00
|
|
|
|
2016-11-29 20:18:41 +01:00
|
|
|
let files = sourceFolders.map(folder => {
|
|
|
|
|
return glob(`${folder}/**/*`);
|
2016-08-13 23:12:07 +02:00
|
|
|
}).reduce((files, fileSet) => files.concat(fileSet), []);
|
|
|
|
|
|
|
|
|
|
return Promise.resolve(files);
|
2016-04-03 14:23:17 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* List all the files that have been changed or added in the last commit range.
|
2016-08-13 23:12:07 +02:00
|
|
|
* @returns {Promise.<Array.<string>>} Resolves with a list of files that are added or changed.
|
2016-04-03 14:23:17 -07:00
|
|
|
*/
|
|
|
|
|
function findChangedFiles() {
|
2016-08-13 23:12:07 +02:00
|
|
|
let commitRange = process.env['TRAVIS_COMMIT_RANGE'];
|
|
|
|
|
|
|
|
|
|
return exec(`git diff --name-status ${commitRange} ${sourceFolders.join(' ')}`)
|
2016-06-03 15:27:17 -07:00
|
|
|
.then(rawDiff => {
|
2016-08-13 23:12:07 +02:00
|
|
|
return rawDiff
|
|
|
|
|
.split('\n')
|
|
|
|
|
.filter(line => {
|
|
|
|
|
// Status: C## => Copied (##% confident)
|
|
|
|
|
// R## => Renamed (##% confident)
|
|
|
|
|
// D => Deleted
|
|
|
|
|
// M => Modified
|
|
|
|
|
// A => Added
|
|
|
|
|
return line.match(/([CR][0-9]*|[AM])\s+/);
|
|
|
|
|
})
|
|
|
|
|
.map(line => line.split(/\s+/, 2)[1]);
|
2016-04-03 14:23:17 -07:00
|
|
|
});
|
|
|
|
|
}
|
2016-03-17 18:17:58 +01:00
|
|
|
|
2016-08-13 23:12:07 +02:00
|
|
|
/**
|
|
|
|
|
* Executes a process command and wraps it inside of a promise.
|
|
|
|
|
* @returns {Promise.<String>}
|
|
|
|
|
*/
|
|
|
|
|
function exec(cmd) {
|
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
|
|
|
child_process.exec(cmd, function(err, stdout /*, stderr */) {
|
|
|
|
|
if (err) {
|
|
|
|
|
reject(err);
|
|
|
|
|
} else {
|
|
|
|
|
resolve(stdout);
|
|
|
|
|
}
|
2016-04-03 14:23:17 -07:00
|
|
|
});
|
|
|
|
|
});
|
2016-08-19 10:59:45 -07:00
|
|
|
}
|