diff --git a/.travis.yml b/.travis.yml index a09b88639b3..2d3d2346a42 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,6 +40,7 @@ env: - secure: "fq/U7VDMWO8O8SnAQkdbkoSe2X92PVqg4d044HmRYVmcf6YbO48+xeGJ8yOk0pCBwl3ISO4Q2ot0x546kxfiYBuHkZetlngZxZCtQiFT9kyId8ZKcYdXaIW9OVdw3Gh3tQyUwDucfkVhqcs52D6NZjyE2aWZ4/d1V4kWRO/LMgo=" matrix: # Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete. + - MODE=lint - MODE=e2e - MODE=saucelabs_required - MODE=browserstack_required @@ -52,23 +53,12 @@ matrix: - env: "MODE=saucelabs_optional" - env: "MODE=browserstack_optional" -before_install: - - ./scripts/ci/install_dart.sh - - install: - npm install - npm run typings before_script: - # Necessary to run test on Travis CI that require a graphical interface. - # See https://docs.travis-ci.com/user/gui-and-headless-browsers - - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start" - mkdir -p $LOGS_DIR - - npm run tslint - - "node ./scripts/ci/forbidden-identifiers.js" - script: - ./scripts/ci/build-and-test.sh diff --git a/package.json b/package.json index ae1566bcd64..f772b02664a 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "url": "https://github.com/angular/material2.git" }, "scripts": { + "ci:forbidden-identifiers": "node ./scripts/ci/forbidden-identifiers.js", "build": "ng build", "dartanalyzer": "cd dist/dart && pub install && cd ../.. && ts-node scripts/ci/dart_analyzer", "demo-app": "ng serve", diff --git a/scripts/ci/build-and-test.sh b/scripts/ci/build-and-test.sh index 6be170ef521..18208de6f30 100755 --- a/scripts/ci/build-and-test.sh +++ b/scripts/ci/build-and-test.sh @@ -11,7 +11,10 @@ source scripts/ci/sources/mode.sh source scripts/ci/sources/tunnel.sh # Setup environment. -is_dart && source scripts/ci/sources/env_dart.sh +if is_dart; then + ./scripts/ci/install_dart.sh + source scripts/ci/sources/env_dart.sh +fi start_tunnel npm run build @@ -20,9 +23,12 @@ echo is_dart && pub install wait_for_tunnel -if is_dart; then +if is_lint; then + npm run tslint + npm run ci:forbidden-identifiers +elif is_dart; then npm run dartanalyzer -elif [[ "$MODE" = e2e* ]]; then +elif is_e2e; then ng serve & sleep 20 npm run e2e diff --git a/scripts/ci/forbidden-identifiers.js b/scripts/ci/forbidden-identifiers.js index 854d07215a8..69a4a0738fb 100644 --- a/scripts/ci/forbidden-identifiers.js +++ b/scripts/ci/forbidden-identifiers.js @@ -4,30 +4,142 @@ * It will search for blocked statements, which have been added in the commits and fail if present. */ -var child_process = require('child_process'); -var blocked_statements = [ - 'ddescribe', - 'fdescribe', - 'iit', - 'fit', - 'xdescribe', - 'xit', - 'debugger;' +const child_process = require('child_process'); +const fs = require('fs'); + +const exec = function(cmd) { + return new Promise(function(resolve, reject) { + child_process.exec(cmd, function(err, stdout /*, stderr */) { + if (err) { + reject(err); + } else { + resolve(stdout); + } + }); + }); +}; + + +const blocked_statements = [ + '\\bddescribe\\(', + '\\bfdescribe\\(', + '\\biit\\(', + '\\bfit\\(', + '\\bxdescribe\\(', + '\\bxit\\(', + '\\bdebugger;' ]; -var blockedRegex = new RegExp('\\+.*(' + blocked_statements.join('|') + ').*$', 'mg'); -var commit_range = process.env.TRAVIS_COMMIT_RANGE || 'HEAD~1'; +const blockedRegex = new RegExp(blocked_statements.join('|'), 'g'); -var diff = child_process.execSync(`git diff --unified=0 ${commit_range} ./src ./e2e`).toString(); +/** + * Find the fork point between HEAD of the current branch, and master. + * @return {Promise} A promise which resolves with the fork SHA (or reject). + */ +function findForkPoint() { + return exec('git merge-base --fork-point HEAD master') + .then(function(stdout) { + return stdout.split('\n')[0]; + }); +} -if (blockedRegex.test(diff)) { +/** + * Get the commit range to evaluate when this script is run. + * @return {Promise} A commit range of the form ref1...ref2. + */ +function getCommitRange() { + if (process.env['TRAVIS_COMMIT_RANGE']) { + return Promise.resolve(process.env['TRAVIS_COMMIT_RANGE']); + } else { + return findForkPoint().then((forkPointSha) => `${forkPointSha}...HEAD`); + } +} - console.warn('Warning: You are using a statement in your commit, which is not allowed.\n' + - 'Blocked Statements are: ' + blocked_statements.join(', ') + '\n' + - 'Please remove them, and the CI will continue.'); +/** + * List all the files that have been changed or added in the last commit range. + * @returns {Promise>} Resolves with a list of files that are + * added or changed. + */ +function findChangedFiles() { + return getCommitRange() + .then(function(range) { + return exec(`git diff --name-status ${range} ./src ./e2e`); + }) + .then(function(rawDiff) { + // Ignore deleted files. + return rawDiff.split('\n') + .filter(function(line) { + // Status: C## => Copied (##% confident) + // R## => Renamed (##% confident) + // D => Deleted + // M => Modified + // A => Added + return line.match(/([CR][0-9]*|[AM])\s+/); + }) + .map(function(line) { + return line.split(/\s+/, 2)[1]; + }); + }); +} - process.exit(1); -} else { - console.log('Info: The commits have been analyzed and are valid!'); -} \ No newline at end of file +// Find all files, check for errors, and output every errors found. +findChangedFiles() + .then(function(fileList) { + // Only match .js or .ts, and remove .d.ts files. + return fileList.filter(function(name) { + return name.match(/\.[jt]s$/) && !name.match(/\.d\.ts$/); + }); + }) + .then(function(fileList) { + // Read every file and return a Promise that will contain an array of + // Object of the form { fileName, content }. + return Promise.all(fileList.map(function(fileName) { + return { + fileName: fileName, + content: fs.readFileSync(fileName, { encoding: 'utf-8' }) + }; + })); + }) + .then(function(diffList) { + // Reduce the diffList to an array of errors. The array will be empty if no errors + // were found. + return diffList.reduce(function(errors, diffEntry) { + let fileName = diffEntry.fileName; + let content = diffEntry.content.split('\n'); + let ln = 0; + + // Get all the lines that start with `+`. + content.forEach(function(line) { + ln++; + + let m = line.match(blockedRegex); + if (m) { + // Accumulate all errors at once. + errors.push({ + fileName: fileName, + lineNumber: ln, + statement: m[0] + }); + } + }); + return errors; + }, []); + }) + .then(function(errors) { + if (errors.length > 0) { + console.error('Error: You are using a statement in your commit, which is not allowed.'); + errors.forEach(function(entry) { + console.error(` ${entry.fileName}@${entry.lineNumber}, Statement: ${entry.statement}.\n`); + }); + + process.exit(1); + } + }) + .catch(function(err) { + // An error occured in this script. Output the error and the stack. + console.error('An error occured during execution:'); + console.error(err); + console.error(err.stack); + process.exit(2); + }); diff --git a/scripts/ci/sources/mode.sh b/scripts/ci/sources/mode.sh index 9408f9865ab..50c3a975ce4 100644 --- a/scripts/ci/sources/mode.sh +++ b/scripts/ci/sources/mode.sh @@ -3,4 +3,12 @@ source ./scripts/ci/sources/tunnel.sh is_dart() { [[ "$MODE" = dart* ]] +} + +is_e2e() { + [[ "$MODE" = e2e ]] +} + +is_lint() { + [[ "$MODE" = lint ]] } \ No newline at end of file