diff --git a/.gitignore b/.gitignore index 2dec7a3109f..76469371e96 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,13 @@ /node_modules /bower_components +# Dart +/.pub +/.packages +/packages +/pubspec.lock + + # IDEs /.idea diff --git a/.travis.yml b/.travis.yml index de7947c92d2..3feb1329d3e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ node_js: cache: directories: - node_modules + - $HOME/.pub-cache env: global: @@ -19,6 +20,8 @@ env: - BROWSER_STACK_USERNAME=angularteam1 - BROWSER_STACK_ACCESS_KEY=BWCd4SynLzdDcv8xtzsB - ARCH=linux-x64 + - DART_DEV_VERSION=latest + - DART_STABLE_VERSION=latest # Token for tsd to increase github rate limit # See https://github.com/DefinitelyTyped/tsd#tsdrc # This does not use http://docs.travis-ci.com/user/environment-variables/#Secure-Variables @@ -32,6 +35,7 @@ env: # Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete. - MODE=saucelabs_required - MODE=browserstack_required + - MODE=dart_required DART_CHANNEL=stable DART_VERSION=$DART_STABLE_VERSION - MODE=saucelabs_optional - MODE=browserstack_optional @@ -40,6 +44,10 @@ matrix: - env: "MODE=saucelabs_optional" - env: "MODE=browserstack_optional" +before_install: + - ./scripts/ci/install_dart.sh + + install: - npm install @@ -52,7 +60,7 @@ before_script: script: -- ./scripts/ci/build-and-test.sh ${MODE} +- ./scripts/ci/build-and-test.sh cache: directories: diff --git a/docs/dart/writing-compatible-typescript.md b/docs/dart/writing-compatible-typescript.md new file mode 100644 index 00000000000..1e492604339 --- /dev/null +++ b/docs/dart/writing-compatible-typescript.md @@ -0,0 +1,40 @@ +# Writing Dart Compatible TypeScript + +When writing TypeScript files in Material 2, it is important to keep in mind that these files will be automatically compiled and verified in Dart. There is valid TypeScript that will work in Angular 2, but will be transpiled to invalid Dart code. + +Because of this, there's certain things to keep in mind. + +## Requirements + +This document refers to the following concepts, but do not explain them: + +* The Dart language: https://www.dartlang.org +* The `pub` Dart packages management: https://pub.dartlang.org/ +* The `angular-cli` toolchain: https://github.com/angular/angular-cli + +## Dart Toolchain + +When running `ng build` (or `ng server` which also builds), a Broccoli tree will automatically compile and format your TypeScript code into Dart. These files live along the JavaScript output of the build process, and also are moved into a special directory structure that is compatible with `pub`. + +For example, let say you have a `md-awesome` component for Material, which contains the `awesome.ts` implementation. This file is contained in the source code as `src/components/awesome/awesome.ts`. Building this component (assuming no specs or `scss`) will result in the following files: + +* `dist/components/awesome/awesome.ts`. The original copy. +* `dist/components/awesome/awesome.js`. The Javascript code. +* `dist/components/awesome/awesome.js.map`. Sourcemap. +* `dist/components/awesome/awesome.dart`. The Dart code, transpiled by `ts2dart` and formatted by `dartfmt`. +* `dist/dart/lib/components/awesome/awesome.dart`. The same file, copied from above into a directory structure compatible with `pub`. + +If you have transpilation errors (ie. your TypeScript is invalid in Dart), these will fail the `ng build` step now and will output the errors in the console. + +## Gotchas + +Here's a list of gotchas to keep in mind when writing TypeScript code that will be compiled to Dart: + +* **Unused imports are enforced in Dart.** Importing a file but not using it is an error, not a warning. +* **`void` is not a proper type for templates.** This is important when creating `EventEmitter<>` or other templates. Instead of `void`, use `Object` and pass in `null`. +* **There's no global execution.** Every executed code need to be in a function. +* **Unit tests need to live inside a `main()` function.** Because of the point above, `describe()` is technically executed code and has to live in a function. Additionally, spec files are programs in Dart and as such much have a `void main() {}` function containing the test code. +* **Boolean expressions are required to be boolean.** There's no type coercion or truthiness concept in Dart. Code like this: `if (!aNumber) {}` must be refactored to be `if (aNumber != 0) {}`. +* **Accessing any platform primitive must be done through a Facade.** For example, Promises or DOM manipulation. Facades are going to be provided on a need-to-have basis. +* **Union types are a no-go.** Dart has them on its roadmap, but for now we must avoid them. +* **Dart files cannot have the same name as a reserved keyword.** For example, `for.dart`, `switch.dart` or `class.dart` are all invalid names. Since the TypeScript files have the same name when transpiled to Dart, they also have the same restriction. \ No newline at end of file diff --git a/ember-cli-build.js b/ember-cli-build.js index 6e538ea255c..03ded4383eb 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -4,11 +4,13 @@ var path = require('path'); // Import the require hook. Enables us to require TS files natively. require('ts-node/register'); +const detect = require('./tools/build/dart').detect; var mergeTrees = require('broccoli-merge-trees'); var Angular2App = require('angular-cli/lib/broccoli/angular2-app'); var BroccoliSass = require('broccoli-sass'); var broccoliAutoprefixer = require('broccoli-autoprefixer'); var BroccoliTs2Dart = require('./tools/broccoli/broccoli-ts2dart').default; +var dartfmt = require('./tools/broccoli/broccoli-dartfmt').default; var autoprefixerOptions = require('./build/autoprefixer-options'); @@ -17,21 +19,39 @@ module.exports = function(defaults) { var demoCssTree = getCssTree('demo-app'); var componentCssTree = getCssTree('components'); var angularAppTree = new Angular2App(defaults); - var dartAppTree = new BroccoliTs2Dart('src/', { - generateLibraryName: true, - generateSourceMap: false, - translateBuiltins: true, - }); + var dartAppTree = getDartTree('src/'); return mergeTrees([ angularAppTree.toTree(), componentCssTree, demoAppCssTree, demoCssTree, - dartAppTree, - ]); + ].concat(dartAppTree || [])); }; +/** Gets the Dart tree - Transpile Dart files and format them afterward. */ +function getDartTree(root) { + const ts2dart = new BroccoliTs2Dart('src/', { + generateLibraryName: true, + generateSourceMap: false, + translateBuiltins: true, + }); + + const dartSDK = detect(); + if (dartSDK) { + // If Dart isn't found, detect() will throw an error. + return dartfmt(ts2dart, { dartSDK }); + } else { + console.warn('---------------------------------------'); + console.warn('You do not have the Dart SDK installed.'); + console.warn('In order to contribute to this repo, please refer to'); + console.warn('https://github.com/angular/material2/blob/master/CONTRIBUTING.md'); + console.warn(''); + console.warn('You can still build and serve the demo app without dart support.'); + return null; + } +} + /** Gets the tree for all of the components' CSS. */ function getCssTree(folder) { var srcPath = `src/${folder}/`; diff --git a/package.json b/package.json index 33227fcf7e5..289fa6d59ff 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ }, "scripts": { "build": "ng build", + "dartanalyzer": "ts-node scripts/ci/dart_analyzer", "demo-app": "cd src && ng serve", "test": "karma start test/karma.conf.js" }, @@ -34,17 +35,23 @@ "broccoli-sass": "^0.7.0", "browserstacktunnel-wrapper": "^1.4.2", "ember-cli-inject-live-reload": "^1.3.0", + "fs-extra": "^0.26.5", + "glob": "^6.0.4", "jasmine-core": "^2.3.4", + "js-yaml": "^3.5.2", "karma": "^0.13.15", "karma-browserstack-launcher": "^0.1.7", "karma-chrome-launcher": "^0.2.1", + "karma-dart": "^0.3.0", "karma-firefox-launcher": "^0.1.7", "karma-jasmine": "^0.3.6", "karma-sauce-launcher": "^0.2.14", + "strip-ansi": "^3.0.0", "symlink-or-copy": "^1.0.1", "ts-node": "^0.5.5", "ts2dart": "^0.7.20", "tslint": "^3.2.2", - "typescript": "^1.7.5" + "typescript": "^1.7.5", + "which": "^1.2.4" } } diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 00000000000..62017320c5f --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,16 @@ +name: material2 +environment: + sdk: '>=1.12.0-dev.5.10 <2.0.0' +dependencies: + observe: '^0.13.1' + angular2: '2.0.0-beta.0' +dev_dependencies: + guinness: '^0.1.18' + intl: '^0.12.4' + unittest: '^0.11.5+4' + quiver: '^0.21.4' +transformers: +- angular2: + platform_directives: 'package:angular2/src/common/directives.dart#CORE_DIRECTIVES' + entry_points: + - web/src/demo-app/demo-app.dart diff --git a/scripts/ci/README.md b/scripts/ci/README.md new file mode 100644 index 00000000000..fac0837b184 --- /dev/null +++ b/scripts/ci/README.md @@ -0,0 +1,12 @@ +# Continuous Integration Scripts + +This directory contains scripts that are related to CI only. + +### `build-and-test.sh` + +The main script. Setup the tests environment, build the files using the `angular-cli` tool and run the tests. + +### `install_dart.sh` + +Only run in Dart environments. Install Dart locally on the CI. + diff --git a/scripts/ci/build-and-test.sh b/scripts/ci/build-and-test.sh index 0df4122b8dd..b510bc82c11 100755 --- a/scripts/ci/build-and-test.sh +++ b/scripts/ci/build-and-test.sh @@ -1,57 +1,27 @@ #!/usr/bin/env bash -set -ex echo "======= Starting build-and-test.sh ========================================" # Go to project dir -SCRIPT_DIR=$(dirname $0) -cd ${SCRIPT_DIR}/../.. +cd $(dirname $0)/../.. +# Include sources. +source scripts/ci/sources/mode.sh +source scripts/ci/sources/tunnel.sh -start_tunnel() { - case "$MODE" in - saucelabs*) - ./scripts/sauce/sauce_connect_setup.sh - ;; - browserstack*) - ./scripts/browserstack/start_tunnel.sh - ;; - *) - ;; - esac -} - -wait_for_tunnel() { - case "$MODE" in - saucelabs*) - ./scripts/sauce/sauce_connect_block.sh - ;; - browserstack*) - ./scripts/browserstack/waitfor_tunnel.sh - ;; - *) - ;; - esac - sleep 10 -} - -teardown_tunnel() { - case "$MODE" in - saucelabs*) - ./scripts/sauce/sauce_connect_teardown.sh - ;; - browserstack*) - # ./scripts/browserstack/teardown_tunnel.sh - ;; - *) - ;; - esac -} - +# Setup environment. +is_dart && source scripts/ci/sources/env_dart.sh start_tunnel -wait_for_tunnel npm run build -karma start test/karma.conf.js --single-run --no-auto-watch --reporters='dots' +is_dart && pub install + +wait_for_tunnel +if is_dart; then + # npm run dartanalyzer + echo 'TODO(hans): Implement dartanalyzer' +else + karma start test/karma.conf.js --single-run --no-auto-watch --reporters='dots' +fi teardown_tunnel diff --git a/scripts/ci/dart_analyzer.ts b/scripts/ci/dart_analyzer.ts new file mode 100644 index 00000000000..9ec134aea8b --- /dev/null +++ b/scripts/ci/dart_analyzer.ts @@ -0,0 +1,12 @@ +import {analyze} from '../../tools/build/dart_analyzer'; +import {detect} from '../../tools/build/dart'; + + +const command = detect()['ANALYZER']; + +analyze('dist/dart', command, true) + .then(() => { + console.log('Done.'); + }, (err) => { + console.error('Error:', err); + }); diff --git a/scripts/ci/install_dart.sh b/scripts/ci/install_dart.sh new file mode 100755 index 00000000000..2a0457fb490 --- /dev/null +++ b/scripts/ci/install_dart.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -e -x + + +if [ -z "${DART_CHANNEL}" -o -z "${DART_VERSION}" -o -z "${ARCH}" ]; then + echo "No Dart necessary. Not installing." + exit 0 +fi + +AVAILABLE_DART_VERSION=$(curl "https://storage.googleapis.com/dart-archive/channels/${DART_CHANNEL}/release/${DART_VERSION}/VERSION" | python -c \ + 'import sys, json; print(json.loads(sys.stdin.read())["version"])') + +echo Fetch Dart channel: ${DART_CHANNEL} + +URL_PREFIX=https://storage.googleapis.com/dart-archive/channels/${DART_CHANNEL}/release/${DART_VERSION} +DART_SDK_URL="$URL_PREFIX/sdk/dartsdk-$ARCH-release.zip" +DARTIUM_URL="$URL_PREFIX/dartium/dartium-$ARCH-release.zip" + +download_and_unzip() { + ZIPFILE=${1/*\//} + curl -O -L $1 && unzip -q $ZIPFILE && rm $ZIPFILE +} + +# TODO: do these downloads in parallel +download_and_unzip $DART_SDK_URL +download_and_unzip $DARTIUM_URL + +echo Fetched new dart version $(&2 + exit 3 + ;; + esac + + export NGDART_BASE_DIR=$(dirname $NGDART_SCRIPT_DIR) + + # Try to find the SDK alongside the dart command first. + if [[ -z "$DART_SDK" ]]; then + DART=$(which dart) || true + if [[ -x "$DART" ]]; then + DART_SDK="${DART/dart-sdk\/*/dart-sdk}" + if [[ ! -e "$DART_SDK" ]]; then + unset DART DART_SDK + fi + fi + fi + # Fallback: Assume it's alongside the current directory (e.g. Travis). + if [[ -z "$DART_SDK" ]]; then + DART_SDK="$(pwd)/dart-sdk" + fi + + : "${DART:=$DART_SDK/bin/dart}" + + if [[ ! -x "$DART" ]]; then + echo Unable to locate the dart binary / SDK. Exiting >&2 + exit 3 + fi + + if [[ -z "$DARTIUM" ]]; then + dartiumRoot="$DART_SDK/../chromium" + if [[ -e "$dartiumRoot" ]]; then + case "$PLATFORM" in + (Linux) export DARTIUM="$dartiumRoot/chrome" ;; + (Darwin) export DARTIUM="$dartiumRoot/Chromium.app/Contents/MacOS/Chromium" ;; + (*) echo Unsupported platform $PLATFORM. Exiting ... >&2 ; exit 3 ;; + esac + fi + fi + + case "$PLATFORM" in + (Linux) export DART_SDK_LIB_SEARCH_PATH="$DART_SDK" ;; + (Darwin) export DART_SDK_LIB_SEARCH_PATH="$DART_SDK/libexec" ;; + (*) echo Unsupported platform $PLATFORM. Exiting ... >&2 ; exit 3 ;; + esac + + export DART_SDK + export DARTSDK + export DART + export PUB=${PUB:-"$DART_SDK/bin/pub"} + if [ -z "$PUB_CACHE" ]; then + export PUB_CACHE=$DART_SDK/pub-cache + fi + export DARTANALYZER=${DARTANALYZER:-"$DART_SDK/bin/dartanalyzer"} + export DARTDOC=${DARTDOC:-"$DART_SDK/bin/dartdoc"} + export DART_DOCGEN=${DART_DOCGEN:-"$DART_SDK/bin/docgen"} + export DART_VM_OPTIONS="--old_gen_heap_size=2048" + export DARTIUM_BIN=${DARTIUM_BIN:-"$DARTIUM"} + export CHROME_BIN=${CHROME_BIN:-"google-chrome"} + export PATH=$PATH:$DART_SDK/bin + + echo '*********' + echo '** ENV **' + echo '*********' + echo DART_SDK=$DART_SDK + echo DART_SDK_LIB_SEARCH_PATH=$DART_SDK_LIB_SEARCH_PATH + echo DART=$DART + echo PUB=$PUB + echo DARTANALYZER=$DARTANALYZER + echo DARTDOC=$DARTDOC + echo DART_DOCGEN=$DART_DOCGEN + echo DARTIUM_BIN=$DARTIUM_BIN + echo CHROME_BIN=$CHROME_BIN + echo PATH=$PATH + echo NGDART_BASE_DIR=$NGDART_BASE_DIR + echo NGDART_SCRIPT_DIR=$NGDART_SCRIPT_DIR + +fi diff --git a/scripts/ci/sources/mode.sh b/scripts/ci/sources/mode.sh new file mode 100644 index 00000000000..9408f9865ab --- /dev/null +++ b/scripts/ci/sources/mode.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +source ./scripts/ci/sources/tunnel.sh + +is_dart() { + [[ "$MODE" = dart* ]] +} \ No newline at end of file diff --git a/scripts/ci/sources/tunnel.sh b/scripts/ci/sources/tunnel.sh new file mode 100644 index 00000000000..2ec2034ad7a --- /dev/null +++ b/scripts/ci/sources/tunnel.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + + +start_tunnel() { + case "$MODE" in + saucelabs*) + ./scripts/sauce/sauce_connect_setup.sh + ;; + browserstack*) + ./scripts/browserstack/start_tunnel.sh + ;; + *) + ;; + esac +} + +wait_for_tunnel() { + case "$MODE" in + saucelabs*) + ./scripts/sauce/sauce_connect_block.sh + ;; + browserstack*) + ./scripts/browserstack/waitfor_tunnel.sh + ;; + *) + ;; + esac + sleep 10 +} + +teardown_tunnel() { + case "$MODE" in + saucelabs*) + ./scripts/sauce/sauce_connect_teardown.sh + ;; + browserstack*) + # ./scripts/browserstack/teardown_tunnel.sh + ;; + *) + ;; + esac +} + diff --git a/src/components/button/button.spec.ts b/src/components/button/button.spec.ts index 1f9d2b2c92b..247a35ba143 100644 --- a/src/components/button/button.spec.ts +++ b/src/components/button/button.spec.ts @@ -16,42 +16,44 @@ import {By} from 'angular2/platform/browser'; import {MdButton} from './button'; import {AsyncTestFn, FunctionWithParamTokens} from 'angular2/testing'; +export function main() { + describe('MdButton', () => { + let builder: TestComponentBuilder; -describe('MdButton', () => { - let builder: TestComponentBuilder; + beforeEach(inject([TestComponentBuilder], (tcb:TestComponentBuilder) => { + builder = tcb; + })); - beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { builder = tcb; })); + describe('button[md-button]', () => { + it('should handle a click on the button', (done:() => void) => { + return builder.createAsync(TestApp).then((fixture) => { + let testComponent = fixture.debugElement.componentInstance; + let buttonDebugElement = fixture.debugElement.query(By.css('button')); - describe('button[md-button]', () => { - it('should handle a click on the button', (done: () => void) => { - return builder.createAsync(TestApp).then((fixture) => { - let testComponent = fixture.debugElement.componentInstance; - let buttonDebugElement = fixture.debugElement.query(By.css('button')); - - buttonDebugElement.nativeElement.click(); - expect(testComponent.clickCount).toBe(1); - done(); + buttonDebugElement.nativeElement.click(); + expect(testComponent.clickCount).toBe(1); + done(); + }); }); + + it('should not increment if disabled', (done:() => void) => { + return builder.createAsync(TestApp).then((fixture) => { + let testComponent = fixture.debugElement.componentInstance; + let buttonDebugElement = fixture.debugElement.query(By.css('button')); + + testComponent.isDisabled = true; + fixture.detectChanges(); + + buttonDebugElement.nativeElement.click(); + + expect(testComponent.clickCount).toBe(0); + done(); + }) + }); + }); - - it('should not increment if disabled', (done: () => void) => { - return builder.createAsync(TestApp).then((fixture) => { - let testComponent = fixture.debugElement.componentInstance; - let buttonDebugElement = fixture.debugElement.query(By.css('button')); - - testComponent.isDisabled = true; - fixture.detectChanges(); - - buttonDebugElement.nativeElement.click(); - - expect(testComponent.clickCount).toBe(0); - done(); - }) - }); - }); -}); - +} /** Shortcut function to use instead of `injectAsync` for less boilerplate on each `it`. */ function testAsync(fn: Function): FunctionWithParamTokens { diff --git a/src/components/sidenav/sidenav.spec.ts b/src/components/sidenav/sidenav.spec.ts index cec47fbb49f..8d698c6129d 100644 --- a/src/components/sidenav/sidenav.spec.ts +++ b/src/components/sidenav/sidenav.spec.ts @@ -21,7 +21,7 @@ import {Predicate} from "angular2/src/facade/collection"; function wait(msec: number) { - return new Promise(resolve => window.setTimeout(() => resolve(), msec)); + return new Promise(resolve => window.setTimeout(() => resolve(), msec)); } @@ -38,75 +38,117 @@ function waitOnEvent(fixture: ComponentFixture, } -describe('MdSidenav', () => { - let builder: TestComponentBuilder; +export function main() { + describe('MdSidenav', () => { + let builder: TestComponentBuilder; - beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { builder = tcb; })); + beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + builder = tcb; + })); - describe('methods', () => { - it('should be able to open and close', (done: any) => { - let testComponent: BasicTestApp; - let fixture: ComponentFixture; + describe('methods', () => { + it('should be able to open and close', (done: any) => { + let testComponent: BasicTestApp; + let fixture: ComponentFixture; - return builder.createAsync(BasicTestApp) - .then((f) => { - fixture = f; - testComponent = fixture.debugElement.componentInstance; + return builder.createAsync(BasicTestApp) + .then((f) => { + fixture = f; + testComponent = fixture.debugElement.componentInstance; - const openButtonElement = fixture.debugElement.query(By.css('.open')); - openButtonElement.nativeElement.click(); - }) - .then(() => wait(1)) - .then(() => { - expect(testComponent.openStartCount).toBe(1); - expect(testComponent.openCount).toBe(0); - }) - .then(() => waitOnEvent(fixture, By.directive(MdSidenav), 'onOpen')) - .then(() => { - expect(testComponent.openStartCount).toBe(1); - expect(testComponent.openCount).toBe(1); - expect(testComponent.closeStartCount).toBe(0); - expect(testComponent.closeCount).toBe(0); + const openButtonElement = fixture.debugElement.query(By.css('.open')); + openButtonElement.nativeElement.click(); + }) + .then(() => wait(1)) + .then(() => { + expect(testComponent.openStartCount).toBe(1); + expect(testComponent.openCount).toBe(0); + }) + .then(() => waitOnEvent(fixture, By.directive(MdSidenav), 'onOpen')) + .then(() => { + expect(testComponent.openStartCount).toBe(1); + expect(testComponent.openCount).toBe(1); + expect(testComponent.closeStartCount).toBe(0); + expect(testComponent.closeCount).toBe(0); - const sidenavElement = fixture.debugElement.query(By.css('md-sidenav')); - const sidenavBackdropElement = fixture.debugElement.query(By.css('.md-sidenav-backdrop')); - expect(window.getComputedStyle(sidenavElement.nativeElement).visibility).toEqual('visible'); - expect(window.getComputedStyle(sidenavBackdropElement.nativeElement).visibility).toEqual('visible'); + const sidenavElement = fixture.debugElement.query(By.css('md-sidenav')); + const sidenavBackdropElement = fixture.debugElement.query(By.css('.md-sidenav-backdrop')); + expect(window.getComputedStyle(sidenavElement.nativeElement).visibility).toEqual('visible'); + expect(window.getComputedStyle(sidenavBackdropElement.nativeElement).visibility).toEqual('visible'); - // Close it. - const closeButtonElement = fixture.debugElement.query(By.css('.close')); - closeButtonElement.nativeElement.click(); - }) - .then(() => wait(1)) - .then(() => { - expect(testComponent.openStartCount).toBe(1); - expect(testComponent.openCount).toBe(1); - expect(testComponent.closeStartCount).toBe(1); - expect(testComponent.closeCount).toBe(0); - }) - .then(() => waitOnEvent(fixture, By.directive(MdSidenav), 'onClose')) - .then(() => fixture.detectChanges()) - .then(() => { - expect(testComponent.openStartCount).toBe(1); - expect(testComponent.openCount).toBe(1); - expect(testComponent.closeStartCount).toBe(1); - expect(testComponent.closeCount).toBe(1); + // Close it. + const closeButtonElement = fixture.debugElement.query(By.css('.close')); + closeButtonElement.nativeElement.click(); + }) + .then(() => wait(1)) + .then(() => { + expect(testComponent.openStartCount).toBe(1); + expect(testComponent.openCount).toBe(1); + expect(testComponent.closeStartCount).toBe(1); + expect(testComponent.closeCount).toBe(0); + }) + .then(() => waitOnEvent(fixture, By.directive(MdSidenav), 'onClose')) + .then(() => fixture.detectChanges()) + .then(() => { + expect(testComponent.openStartCount).toBe(1); + expect(testComponent.openCount).toBe(1); + expect(testComponent.closeStartCount).toBe(1); + expect(testComponent.closeCount).toBe(1); - const sidenavElement = fixture.debugElement.query(By.css('md-sidenav')); - const sidenavBackdropElement = fixture.debugElement.query(By.css('.md-sidenav-backdrop')); - expect(window.getComputedStyle(sidenavElement.nativeElement).visibility).toEqual('hidden'); - expect(window.getComputedStyle(sidenavBackdropElement.nativeElement).visibility).toEqual('hidden'); - }) - .then(done, done.fail); - }, 8000); + const sidenavElement = fixture.debugElement.query(By.css('md-sidenav')); + const sidenavBackdropElement = fixture.debugElement.query(By.css('.md-sidenav-backdrop')); + expect(window.getComputedStyle(sidenavElement.nativeElement).visibility).toEqual('hidden'); + expect(window.getComputedStyle(sidenavBackdropElement.nativeElement).visibility).toEqual('hidden'); + }) + .then(done, done.fail); + }, 8000); - it('open() and close() return a promise that resolves after the animation ended', - (done: any) => { + it('open() and close() return a promise that resolves after the animation ended', + (done: any) => { + let testComponent: BasicTestApp; + let fixture: ComponentFixture; + let sidenav: MdSidenav; + + let promise: Promise<{}>; + let called: boolean = false; + + return builder.createAsync(BasicTestApp) + .then((f) => { + fixture = f; + sidenav = fixture.debugElement.query(By.directive(MdSidenav)).componentInstance; + + promise = sidenav.open(); + promise.then(() => called = true); + }) + .then(() => wait(1)) + .then(() => fixture.detectChanges()) + .then(() => { + expect(called).toBe(false); + }) + .then(() => promise) + .then(() => expect(called).toBe(true)) + .then(() => { + // Close it now. + called = false; + promise = sidenav.close(); + promise.then(() => called = true); + }) + .then(() => wait(1)) + .then(() => fixture.detectChanges()) + .then(() => { + expect(called).toBe(false); + }) + .then(() => promise) + .then(() => expect(called).toBe(true)) + .then(done, done.fail); + }, 8000); + + it('open() twice returns the same promise', (done: any) => { let testComponent: BasicTestApp; let fixture: ComponentFixture; let sidenav: MdSidenav; - let promise: Promise; + let promise: Promise<{}>; let called: boolean = false; return builder.createAsync(BasicTestApp) @@ -115,151 +157,124 @@ describe('MdSidenav', () => { sidenav = fixture.debugElement.query(By.directive(MdSidenav)).componentInstance; promise = sidenav.open(); - promise.then(() => called = true); + expect(sidenav.open()).toBe(promise); }) .then(() => wait(1)) - .then(() => fixture.detectChanges()) .then(() => { - expect(called).toBe(false); + fixture.detectChanges(); + return promise; }) - .then(() => promise) - .then(() => expect(called).toBe(true)) .then(() => { - // Close it now. - called = false; promise = sidenav.close(); - promise.then(() => called = true); + expect(sidenav.close()).toBe(promise); }) - .then(() => wait(1)) - .then(() => fixture.detectChanges()) - .then(() => { - expect(called).toBe(false); - }) - .then(() => promise) - .then(() => expect(called).toBe(true)) .then(done, done.fail); - }, 8000); + }); - it('open() twice returns the same promise', (done: any) => { - let testComponent: BasicTestApp; - let fixture: ComponentFixture; - let sidenav: MdSidenav; + it('open() then close() cancel animations when called too fast', + (done: any) => { + let testComponent: BasicTestApp; + let fixture: ComponentFixture; + let sidenav: MdSidenav; - let promise: Promise; - let called: boolean = false; + let openPromise: Promise; + let closePromise: Promise; + let openCalled: boolean = false; + let openCancelled: boolean = false; + let closeCalled: boolean = false; - return builder.createAsync(BasicTestApp) - .then((f) => { - fixture = f; - sidenav = fixture.debugElement.query(By.directive(MdSidenav)).componentInstance; + return builder.createAsync(BasicTestApp) + .then((f) => { + fixture = f; + sidenav = fixture.debugElement.query(By.directive(MdSidenav)).componentInstance; - promise = sidenav.open(); - expect(sidenav.open()).toBe(promise); - }) - .then(() => wait(1)) - .then(() => { - fixture.detectChanges(); - return promise; - }) - .then(() => { - promise = sidenav.close(); - expect(sidenav.close()).toBe(promise); - }) - .then(done, done.fail); + openPromise = sidenav.open().then(() => { + openCalled = true; + }, + () => { + openCancelled = true; + }); + }) + .then(() => wait(1)) + .then(() => fixture.detectChanges()) + // We need to wait for the browser to start the transition. + .then(() => wait(50)) + .then(() => { + closePromise = sidenav.close().then(() => { + closeCalled = true; + }, done.fail); + return wait(1); + }) + .then(() => { + fixture.detectChanges(); + return closePromise; + }) + .then(() => { + expect(openCalled).toBe(false); + expect(openCancelled).toBe(true); + expect(closeCalled).toBe(true); + }) + .then(done, done.fail); + }, 8000); + + it('close() then open() cancel animations when called too fast', + (done: any) => { + let testComponent: BasicTestApp; + let fixture: ComponentFixture; + let sidenav: MdSidenav; + + let openPromise: Promise; + let closePromise: Promise; + let closeCalled: boolean = false; + let closeCancelled: boolean = false; + let openCalled: boolean = false; + + return builder.createAsync(BasicTestApp) + .then((f) => { + fixture = f; + sidenav = fixture.debugElement.query(By.directive(MdSidenav)).componentInstance; + + /** First, open it. */ + openPromise = sidenav.open(); + }) + .then(() => wait(1)) + .then(() => { + fixture.detectChanges(); + return openPromise; + }) + .then(() => { + // Then close and check behavior. + closePromise = sidenav.close().then(() => { + closeCalled = true; + }, + () => { + closeCancelled = true; + }); + }) + .then(() => wait(1)) + .then(() => fixture.detectChanges()) + // We need to wait for the browser to start the transition. + .then(() => wait(50)) + .then(() => { + openPromise = sidenav.open().then(() => { + openCalled = true; + }, done.fail); + return wait(1); + }) + .then(() => { + fixture.detectChanges(); + return openPromise; + }) + .then(() => { + expect(closeCalled).toBe(false); + expect(closeCancelled).toBe(true); + expect(openCalled).toBe(true); + }) + .then(done, done.fail); + }, 8000); }); - - it('open() then close() cancel animations when called too fast', - (done: any) => { - let testComponent: BasicTestApp; - let fixture: ComponentFixture; - let sidenav: MdSidenav; - - let openPromise: Promise; - let closePromise: Promise; - let openCalled: boolean = false; - let openCancelled: boolean = false; - let closeCalled: boolean = false; - - return builder.createAsync(BasicTestApp) - .then((f) => { - fixture = f; - sidenav = fixture.debugElement.query(By.directive(MdSidenav)).componentInstance; - - openPromise = sidenav.open().then(() => { openCalled = true; }, - () => { openCancelled = true; }); - }) - .then(() => wait(1)) - .then(() => fixture.detectChanges()) - // We need to wait for the browser to start the transition. - .then(() => wait(50)) - .then(() => { - closePromise = sidenav.close().then(() => { closeCalled = true; }, done.fail); - return wait(1); - }) - .then(() => { - fixture.detectChanges(); - return closePromise; - }) - .then(() => { - expect(openCalled).toBe(false); - expect(openCancelled).toBe(true); - expect(closeCalled).toBe(true); - }) - .then(done, done.fail); - }, 8000); - - it('close() then open() cancel animations when called too fast', - (done: any) => { - let testComponent: BasicTestApp; - let fixture: ComponentFixture; - let sidenav: MdSidenav; - - let openPromise: Promise; - let closePromise: Promise; - let closeCalled: boolean = false; - let closeCancelled: boolean = false; - let openCalled: boolean = false; - - return builder.createAsync(BasicTestApp) - .then((f) => { - fixture = f; - sidenav = fixture.debugElement.query(By.directive(MdSidenav)).componentInstance; - - /** First, open it. */ - openPromise = sidenav.open(); - }) - .then(() => wait(1)) - .then(() => { - fixture.detectChanges(); - return openPromise; - }) - .then(() => { - // Then close and check behavior. - closePromise = sidenav.close().then(() => { closeCalled = true; }, - () => { closeCancelled = true; }); - }) - .then(() => wait(1)) - .then(() => fixture.detectChanges()) - // We need to wait for the browser to start the transition. - .then(() => wait(50)) - .then(() => { - openPromise = sidenav.open().then(() => { openCalled = true; }, done.fail); - return wait(1); - }) - .then(() => { - fixture.detectChanges(); - return openPromise; - }) - .then(() => { - expect(closeCalled).toBe(false); - expect(closeCancelled).toBe(true); - expect(openCalled).toBe(true); - }) - .then(done, done.fail); - }, 8000); }); -}); - +} /** Test component that contains an MdSidenavLayout and one MdSidenav. */ @Component({ diff --git a/src/components/sidenav/sidenav.ts b/src/components/sidenav/sidenav.ts index 8dbdb9fba80..496cbebc164 100644 --- a/src/components/sidenav/sidenav.ts +++ b/src/components/sidenav/sidenav.ts @@ -61,16 +61,16 @@ export class MdSidenav { @Input('opened') private opened_: boolean; /** Event emitted when the sidenav is being opened. Use this to synchronize animations. */ - @Output('open-start') onOpenStart = new EventEmitter(); + @Output('open-start') onOpenStart = new EventEmitter(); /** Event emitted when the sidenav is fully opened. */ - @Output('open') onOpen = new EventEmitter(); + @Output('open') onOpen = new EventEmitter(); /** Event emitted when the sidenav is being closed. Use this to synchronize animations. */ - @Output('close-start') onCloseStart = new EventEmitter(); + @Output('close-start') onCloseStart = new EventEmitter(); /** Event emitted when the sidenav is fully closed. */ - @Output('close') onClose = new EventEmitter(); + @Output('close') onClose = new EventEmitter(); /** @@ -91,7 +91,7 @@ export class MdSidenav { /** Open this sidenav, and return a Promise that will resolve when it's fully opened (or get * rejected if it didn't). */ - open(): Promise { + open(): Promise<{}> { return this.toggle(true); } @@ -99,7 +99,7 @@ export class MdSidenav { * Close this sidenav, and return a Promise that will resolve when it's fully closed (or get * rejected if it didn't). */ - close(): Promise { + close(): Promise<{}> { return this.toggle(false); } @@ -108,11 +108,11 @@ export class MdSidenav { * close() when it's closed. * @param isOpen */ - toggle(isOpen: boolean = !this.opened): Promise { + toggle(isOpen: boolean = !this.opened): Promise<{}> { // Shortcut it if we're already opened. if (isOpen === this.opened) { if (!this.transition_) { - return Promise.resolve(); + return Promise.resolve(null); } else { return isOpen ? this.openPromise_ : this.closePromise_; } @@ -132,7 +132,7 @@ export class MdSidenav { if (isOpen) { if (!this.openPromise_) { - this.openPromise_ = new Promise((resolve, reject) => { + this.openPromise_ = new Promise<{}>((resolve, reject) => { this.openPromiseResolve_ = resolve; this.openPromiseReject_ = reject; }); @@ -140,7 +140,7 @@ export class MdSidenav { return this.openPromise_; } else { if (!this.closePromise_) { - this.closePromise_ = new Promise((resolve, reject) => { + this.closePromise_ = new Promise<{}>((resolve, reject) => { this.closePromiseResolve_ = resolve; this.closePromiseReject_ = reject; }); @@ -225,10 +225,10 @@ export class MdSidenav { } private transition_: boolean = false; - private openPromise_: Promise; + private openPromise_: Promise<{}>; private openPromiseResolve_: () => void; private openPromiseReject_: () => void; - private closePromise_: Promise; + private closePromise_: Promise<{}>; private closePromiseResolve_: () => void; private closePromiseReject_: () => void; } diff --git a/src/directives/dir/dir.ts b/src/directives/dir/dir.ts index 89d0247bd0f..a17406ed8f9 100644 --- a/src/directives/dir/dir.ts +++ b/src/directives/dir/dir.ts @@ -16,7 +16,7 @@ import {OneOf} from "../../core/annotations/one-of"; export class Dir { @Input('dir') @OneOf(['ltr', 'rtl']) private dir_: string = 'ltr'; - @Output() dirChange = new EventEmitter(); + @Output() dirChange = new EventEmitter<{}>(); @HostBinding('attr.dir') get dir(): string { diff --git a/src/main.dart b/src/main.dart new file mode 100644 index 00000000000..00f69757865 --- /dev/null +++ b/src/main.dart @@ -0,0 +1,7 @@ +import "package:angular2/platform/browser.dart" show bootstrap; +import "./demo-app/demo-app.dart" show DemoApp; +import "package:angular2/router" show ROUTER_PROVIDERS; + +void main() { + bootstrap(DemoApp, [ROUTER_PROVIDERS]); +} diff --git a/test/karma-test-shim.js b/test/karma-test-shim.js index 5e556f7d47c..aa8cf881ae3 100644 --- a/test/karma-test-shim.js +++ b/test/karma-test-shim.js @@ -1,6 +1,6 @@ Error.stackTraceLimit = Infinity; -jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; +jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000; __karma__.loaded = function() {}; @@ -49,7 +49,13 @@ System.import('angular2/platform/browser').then(function(browser_adapter) { Object.keys(window.__karma__.files) .filter(isSpecFile) .map(function(moduleName) { - return System.import(moduleName); + return System.import(moduleName).then(function(module) { + if (module.hasOwnProperty('main')) { + return module.main(); + } else { + return module; + } + }); })); }).then(function() { __karma__.start(); diff --git a/test/karma.config.ts b/test/karma.config.ts index 9dc60e814c7..2d33658705b 100644 --- a/test/karma.config.ts +++ b/test/karma.config.ts @@ -14,9 +14,9 @@ export function config(config) { plugins: [ require('karma-jasmine'), require('karma-browserstack-launcher'), + require('karma-sauce-launcher'), require('karma-chrome-launcher'), require('karma-firefox-launcher'), - require('karma-sauce-launcher'), ], files: [ {pattern: 'node_modules/es6-shim/es6-shim.js', included: true, watched: true}, diff --git a/test/packages b/test/packages new file mode 120000 index 00000000000..a16c4050155 --- /dev/null +++ b/test/packages @@ -0,0 +1 @@ +../packages \ No newline at end of file diff --git a/tools/broccoli/broccoli-dartfmt.ts b/tools/broccoli/broccoli-dartfmt.ts index 7872c81a4d2..c418d9baa5b 100644 --- a/tools/broccoli/broccoli-dartfmt.ts +++ b/tools/broccoli/broccoli-dartfmt.ts @@ -26,7 +26,6 @@ class DartFormatter implements DiffingBroccoliPlugin { constructor(public inputPath: string, public cachePath: string, options) { if (!options.dartSDK) throw new Error("Missing Dart SDK"); this.DARTFMT = options.dartSDK.DARTFMT; - this.verbose = options.logs.dartfmt; } rebuild(treeDiff: DiffResult): Promise { diff --git a/tools/build/dart.ts b/tools/build/dart.ts new file mode 100644 index 00000000000..34f123d070a --- /dev/null +++ b/tools/build/dart.ts @@ -0,0 +1,51 @@ +/// +// TODO(hansl): Get rid of this file. It's unneeded. + +const which = require('which'); +const child_process = require('child_process'); + + +export type DartSdkExecutableMap = { + ANALYZER: string, + DARTDOCGEN: string, + DARTFMT: string, + PUB: string, + VM: string, +}; + +/** + * Return a map of {EXECUTABLE => Path} for the Dart SDK. + * The executables are different on Windows / Unix. Returns null if the Dart SDK cannot be + * found. + */ +export function detect(): DartSdkExecutableMap { + let sdk: DartSdkExecutableMap; + try { + const dartExecutable = which.sync('dart'); + if (process.platform === 'win32') { + sdk = { + ANALYZER: 'dartanalyzer.bat', + DARTDOCGEN: 'dartdoc.bat', + DARTFMT: 'dartfmt.bat', + PUB: 'pub.bat', + VM: 'dart.exe' + }; + } else { + sdk = { + ANALYZER: 'dartanalyzer', + DARTDOCGEN: 'dartdoc', + DARTFMT: 'dartfmt', + PUB: 'pub', + VM: 'dart' + }; + } + console.log('Dart SDK detected:', dartExecutable); + console.log('- dart: ' + child_process.spawnSync(sdk.VM, ['--version']).stderr.toString().replace(/\n/g, '')); + console.log('- pub: ' + child_process.spawnSync(sdk.PUB, ['--version']).stdout.toString().replace(/\n/g, '')); + + return sdk; + } catch (e) { + console.log('Dart SDK is not available.'); + return null; + } +} diff --git a/tools/build/dart_analyzer.ts b/tools/build/dart_analyzer.ts new file mode 100644 index 00000000000..08112e5c69c --- /dev/null +++ b/tools/build/dart_analyzer.ts @@ -0,0 +1,249 @@ +/// +/// + +import {travisFoldStart} from '../travis/fold'; + +import readline = require('readline'); +import child_process = require('child_process'); +import path = require('path'); +import fs = require('fs'); + +const glob = require('glob'); +const yaml = require('js-yaml'); + + +const tempFile = '_analyzer.dart'; + + +/** + * Call the Dart Analyzer on every dart files below. + * Specialized for Travis. + * + * @param dir The directory to scan. + * @param command The command to use for dartanalyzer. + * @param use_ddc Enable strong static type checking (see https://goo.gl/DqcBsw). + */ +export function analyze(dir: string, command: string, use_ddc: boolean = false): Promise { + var travisFoldEnd = travisFoldStart(`dartanalyzer-${use_ddc ? 'ddc' : ''}-${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})); + + var analyzeFile = ['library _analyzer;']; + libFiles.concat(testFiles).concat(webFiles).forEach((fileName, index) => { + if (fileName !== tempFile && fileName.indexOf("/packages/") === -1) { + if (fileName.indexOf('lib') == 0) { + fileName = 'package:' + packageName + '/' + + path.relative('lib', fileName).replace(/\\/g, '/'); + } + analyzeFile.push('import "' + fileName + '" as mod' + index + ';'); + } + }); + fs.writeFileSync(path.join(dir, tempFile), analyzeFile.join('\n')); + + return new Promise((resolve, reject) => { + analyze_(command, dir, resolve, use_ddc); + }); +} + + +function analyze_(command, dirName, done, useDdc) { + // TODO remove --package-warnings once dartanalyzer handles transitive libraries + 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); + + var stream = child_process.spawn(command, args, { + // inherit stdin and stderr, but filter stdout + stdio: [process.stdin, process.stdout, 'pipe'], + 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. + var rl = + readline.createInterface({input: stream.stderr, output: process.stdout, terminal: false}); + var hintCount = 0; + var errorCount = 0; + var warningCount = 0; + rl.on('line', function(line) { + if (line == "find: > bin [: No such file or directory") { + // Skip bad output from Dart SDK .bat files on Windows + return; + } + var parsedLine = _AnalyzerOutputLine.parse(line); + if (!parsedLine) { + errorCount++; + console.log('Unexpected output: ' + line); + return; + } + // TODO remove once dartanalyzer handles transitive libraries + // skip errors in third-party packages + if (parsedLine.sourcePath.indexOf(dirName) == -1) { + return; + } + if (parsedLine.shouldIgnore()) { + return; + } + + if (parsedLine.isHint) { + hintCount++; + } else if (parsedLine.isWarning) { + warningCount++; + } else { + errorCount++; + } + console.log(dirName + ':' + parsedLine); + }); + stream.on('close', function() { + var error; + var report = []; + if (errorCount > 0) { + report.push(errorCount + ' error(s)'); + } + if (warningCount > 0) { + report.push(warningCount + ' warning(s)'); + } + if (hintCount > 0) { + report.push(hintCount + ' hint(s)'); + } + if (report.length > 0) { + error = 'Dartanalyzer showed ' + report.join(', '); + } + done(error); + }); + stream.on('error', function(error) { done(error); }); +} + + +// See https://github.com/dart-lang/analyzer_cli/blob/master/lib/src/error_formatter.dart +class _AnalyzerOutputLine { + severity: string; + errorType: string; + errorCode: string; + sourcePath: string; + lineNum: number; + colNum: string; + asciiLineLength: number; + errorMsg: string; + + isError: boolean; + isHint: boolean; + isWarning: boolean; + + constructor(result: any[]) { + this.severity = result[1]; + this.errorType = result[2]; + this.errorCode = result[3]; + this.sourcePath = result[4]; + this.lineNum = parseInt(result[5], 10); + this.colNum = result[6]; + this.asciiLineLength = parseInt(result[7], 10); + this.errorMsg = result[8]; + + this.isError = Boolean(this.severity.match(/ERROR/i)); + this.isHint = Boolean(this.severity.match(/INFO/i)); + this.isWarning = Boolean(this.severity.match(/WARNING/i)); + } + + static parse(line: string) { + const result = this.analyzerParseRegExp_.exec(line); + return result ? new this(result) : null; + } + + private static 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). */ + private static cache_ = {}; + + private static ERR_NO_SOURCE = '(Could not find source line).'; + + toString() { + 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 + '] type: ' + this.errorType + ' ' + + this.errorMsg + ' (' + this.sourcePath + ', line ' + this.lineNum + + ', col ' + this.colNum + ')' + lineText; + } + + shouldIgnore() { + if (this.errorCode.match(/UNUSED_IMPORT/i)) { + if (this.sourcePath.match(/_analyzer\.dart/)) { + return true; + } + // TODO remove it once ts2dart properly generates abstract getters + if (this.errorMsg.match(/unimplemented/)) { + return true; + } + } + + if (this.errorCode.match(/DEPRECATED_MEMBER_USE/i)) { + return true; + } + + // TODO: https://github.com/angular/ts2dart/issues/168 + if (this.errorCode.match(/UNUSED_CATCH_STACK/i)) { + return true; + } + + // TODOs shouldn't break our build... + if (this.errorCode.match(/TODO/i)) { + return true; + } + + // Don't worry about hints in generated files. + if (this.isHint && this.sourcePath.match(/generated/i)) { + return true; + } + return false; + } + + // Reads the source file for the Analyzer output, caching it for future use. + _getSourceLine() { + 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; + } +}; + diff --git a/tools/travis/fold.ts b/tools/travis/fold.ts new file mode 100644 index 00000000000..066cc823d7f --- /dev/null +++ b/tools/travis/fold.ts @@ -0,0 +1,16 @@ + +function encode(str: string) { + return str.replace(/\W/g, '-').replace(/-$/, ''); +} + +export function travisFoldStart(name: string): () => void { + if (process.env['TRAVIS']) { + console.log('travis_fold:start:' + encode(name)); + } + + return () => { + if (process.env['TRAVIS']) { + console.log('travis_fold:end:' + encode(name)); + } + }; +}