From 83b32a0a0aa8452e410e7b3154d974e026f8847f Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Mon, 26 Feb 2018 11:34:57 +0000 Subject: [PATCH 001/604] build(aio): render heading anchors on the right This approach simplifies the styling needed considerably. Previously, we had to make room on the left for heading that are in visual containers. Also we had to apply a `float:right` when on narrow screens as the gutter not available then. This float didn't render nicely if the heading text was longer than could be rendered on a single line. Closes #22131 --- .../styles/2-modules/_heading-anchors.scss | 36 ++----------------- .../post-processors/autolink-headings.js | 2 +- .../post-processors/autolink-headings.spec.js | 6 ++-- .../templates/api/base.template.html | 4 +-- 4 files changed, 8 insertions(+), 40 deletions(-) diff --git a/aio/src/styles/2-modules/_heading-anchors.scss b/aio/src/styles/2-modules/_heading-anchors.scss index 704691c6e2..e3a169fae4 100644 --- a/aio/src/styles/2-modules/_heading-anchors.scss +++ b/aio/src/styles/2-modules/_heading-anchors.scss @@ -1,48 +1,18 @@ .sidenav-content { h1, h2, h3, h4, h5, h6 { - &.no-anchor .header-link { - display: none; - } - .header-link { - box-sizing: border-box; color: $mediumgray; - display: inline-block; - margin-left: -42px; - padding: 0 8px; + margin-left: 8px; text-decoration: none; user-select: none; - vertical-align: middle; visibility: hidden; - width: 40px; - - @media (max-width: 600px) { - float: right; - margin-left: 0; - } + display: inline-block; + vertical-align: text-top; } &:hover .header-link { visibility: visible; } } - - .l-sub-section { - h1, h2, h3, h4, h5, h6 { - a { - padding-right: 64px; - margin-left: -74px; - } - } - } - - .alert { - h1, h2, h3, h4, h5, h6 { - a { - padding-right: 80px; - margin-left: -90px; - } - } - } } diff --git a/aio/tools/transforms/angular-base-package/post-processors/autolink-headings.js b/aio/tools/transforms/angular-base-package/post-processors/autolink-headings.js index 7daa72623e..27e1117cc2 100644 --- a/aio/tools/transforms/angular-base-package/post-processors/autolink-headings.js +++ b/aio/tools/transforms/angular-base-package/post-processors/autolink-headings.js @@ -20,7 +20,7 @@ const hasClass = (node, cls) => { const link = options => tree => visit(tree, node => { if (is(node, HEADINGS) && has(node, 'id') && !hasClass(node, NO_ANCHOR_CLASS)) { - node.children.unshift({ + node.children.push({ type: 'element', tagName: 'a', properties: Object.assign(clone(options.properties), {href: `#${node.properties.id}`}), diff --git a/aio/tools/transforms/angular-base-package/post-processors/autolink-headings.spec.js b/aio/tools/transforms/angular-base-package/post-processors/autolink-headings.spec.js index 05ac0a8dc5..4b22faae43 100644 --- a/aio/tools/transforms/angular-base-package/post-processors/autolink-headings.spec.js +++ b/aio/tools/transforms/angular-base-package/post-processors/autolink-headings.spec.js @@ -20,9 +20,9 @@ describe('autolink-headings postprocessor', () => {

Heading with encoded chars &

`; const processedContent = ` -

Heading 1

-

Heading with bold

-

Heading with encoded chars &

+

Heading 1

+

Heading with bold

+

Heading with encoded chars &

`; const docs = [{docType: 'a', renderedContent: originalContent}]; diff --git a/aio/tools/transforms/templates/api/base.template.html b/aio/tools/transforms/templates/api/base.template.html index df0c1279bd..14f2edfaf7 100644 --- a/aio/tools/transforms/templates/api/base.template.html +++ b/aio/tools/transforms/templates/api/base.template.html @@ -30,9 +30,7 @@ {% if doc.experimental !== undefined %}{% endif %} {% if doc.stable !== undefined %}{% endif %} -

- {$ doc.name $} -

+

{$ doc.name $}

{$ version $}
From 8a85888773580527cbb9eddd2cc042931e3bdd47 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Fri, 23 Feb 2018 15:18:21 +0200 Subject: [PATCH 002/604] fix(upgrade): correctly destroy nested downgraded component (#22400) Previously, when a downgraded component was destroyed in a way that did not trigger the `$destroy` event on the element (e.g. when a parent element was removed from the DOM by Angular, not AngularJS), the `ComponentRef` was not destroyed and unregistered. This commit fixes it by listening for the `$destroy` event on both the element and the scope. Fixes #22392 PR Close #22400 --- .../src/common/downgrade_component_adapter.ts | 14 +++-- .../src/dynamic/upgrade_ng1_adapter.ts | 2 + .../downgrade_component_adapter_spec.ts | 8 ++- packages/upgrade/test/dynamic/test_helpers.ts | 5 ++ packages/upgrade/test/dynamic/upgrade_spec.ts | 47 +++++++++++++++- .../integration/downgrade_component_spec.ts | 55 ++++++++++++++++++- 6 files changed, 121 insertions(+), 10 deletions(-) diff --git a/packages/upgrade/src/common/downgrade_component_adapter.ts b/packages/upgrade/src/common/downgrade_component_adapter.ts index 4e44bfcf9f..6ba0238675 100644 --- a/packages/upgrade/src/common/downgrade_component_adapter.ts +++ b/packages/upgrade/src/common/downgrade_component_adapter.ts @@ -209,12 +209,16 @@ export class DowngradeComponentAdapter { registerCleanup() { const destroyComponentRef = this.wrapCallback(() => this.componentRef.destroy()); + let destroyed = false; - this.element.on !('$destroy', () => { - this.componentScope.$destroy(); - this.componentRef.injector.get(TestabilityRegistry) - .unregisterApplication(this.componentRef.location.nativeElement); - destroyComponentRef(); + this.element.on !('$destroy', () => this.componentScope.$destroy()); + this.componentScope.$on('$destroy', () => { + if (!destroyed) { + destroyed = true; + this.componentRef.injector.get(TestabilityRegistry) + .unregisterApplication(this.componentRef.location.nativeElement); + destroyComponentRef(); + } }); } diff --git a/packages/upgrade/src/dynamic/upgrade_ng1_adapter.ts b/packages/upgrade/src/dynamic/upgrade_ng1_adapter.ts index 06d9ed44dc..81f21e6a1c 100644 --- a/packages/upgrade/src/dynamic/upgrade_ng1_adapter.ts +++ b/packages/upgrade/src/dynamic/upgrade_ng1_adapter.ts @@ -263,6 +263,8 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck { if (this.controllerInstance && isFunction(this.controllerInstance.$onDestroy)) { this.controllerInstance.$onDestroy(); } + + this.componentScope.$destroy(); } setComponentProperty(name: string, value: any) { diff --git a/packages/upgrade/test/common/downgrade_component_adapter_spec.ts b/packages/upgrade/test/common/downgrade_component_adapter_spec.ts index d7110815ba..c428c24bd8 100644 --- a/packages/upgrade/test/common/downgrade_component_adapter_spec.ts +++ b/packages/upgrade/test/common/downgrade_component_adapter_spec.ts @@ -85,15 +85,21 @@ withEachNg1Version(() => { let element: angular.IAugmentedJQuery; class mockScope implements angular.IScope { + private destroyListeners: (() => void)[] = []; + $new() { return this; } $watch(exp: angular.Ng1Expression, fn?: (a1?: any, a2?: any) => void) { return () => {}; } $on(event: string, fn?: (event?: any, ...args: any[]) => void) { + if (event === '$destroy' && fn) { + this.destroyListeners.push(fn); + } return () => {}; } $destroy() { - return () => {}; + let listener: (() => void)|undefined; + while ((listener = this.destroyListeners.shift())) listener(); } $apply(exp?: angular.Ng1Expression) { return () => {}; diff --git a/packages/upgrade/test/dynamic/test_helpers.ts b/packages/upgrade/test/dynamic/test_helpers.ts index 284ef8bb6f..7723ecc361 100644 --- a/packages/upgrade/test/dynamic/test_helpers.ts +++ b/packages/upgrade/test/dynamic/test_helpers.ts @@ -12,6 +12,11 @@ import {$ROOT_SCOPE} from '@angular/upgrade/src/common/constants'; export * from '../common/test_helpers'; +export function $apply(adapter: UpgradeAdapterRef, exp: angular.Ng1Expression) { + const $rootScope = adapter.ng1Injector.get($ROOT_SCOPE) as angular.IRootScopeService; + $rootScope.$apply(exp); +} + export function $digest(adapter: UpgradeAdapterRef) { const $rootScope = adapter.ng1Injector.get($ROOT_SCOPE) as angular.IRootScopeService; $rootScope.$digest(); diff --git a/packages/upgrade/test/dynamic/upgrade_spec.ts b/packages/upgrade/test/dynamic/upgrade_spec.ts index ae3044b89a..48d3412834 100644 --- a/packages/upgrade/test/dynamic/upgrade_spec.ts +++ b/packages/upgrade/test/dynamic/upgrade_spec.ts @@ -6,13 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectorRef, Component, EventEmitter, Input, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, NgZone, OnChanges, SimpleChange, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core'; +import {ChangeDetectorRef, Component, EventEmitter, Input, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, NgZone, OnChanges, OnDestroy, SimpleChange, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core'; import {async, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import * as angular from '@angular/upgrade/src/common/angular1'; import {UpgradeAdapter, UpgradeAdapterRef} from '@angular/upgrade/src/dynamic/upgrade_adapter'; -import {$digest, html, multiTrim, withEachNg1Version} from './test_helpers'; +import {$apply, $digest, html, multiTrim, withEachNg1Version} from './test_helpers'; declare global { export var inject: Function; @@ -582,6 +582,49 @@ withEachNg1Version(() => { }); })); + it('should properly run cleanup with multiple levels of nesting', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + let destroyed = false; + + @Component( + {selector: 'ng2-outer', template: '
'}) + class Ng2OuterComponent { + @Input() destroyIt = false; + } + + @Component({selector: 'ng2-inner', template: 'test'}) + class Ng2InnerComponent implements OnDestroy { + ngOnDestroy() { destroyed = true; } + } + + @NgModule({ + imports: [BrowserModule], + declarations: + [Ng2InnerComponent, Ng2OuterComponent, adapter.upgradeNg1Component('ng1')], + schemas: [NO_ERRORS_SCHEMA], + }) + class Ng2Module { + } + + const ng1Module = + angular.module('ng1', []) + .directive('ng1', () => ({template: ''})) + .directive('ng2Inner', adapter.downgradeNg2Component(Ng2InnerComponent)) + .directive('ng2Outer', adapter.downgradeNg2Component(Ng2OuterComponent)); + + const element = html(''); + + adapter.bootstrap(element, [ng1Module.name]).ready(ref => { + expect(element.textContent).toBe('test'); + expect(destroyed).toBe(false); + + $apply(ref, 'destroyIt = true'); + + expect(element.textContent).toBe(''); + expect(destroyed).toBe(true); + }); + })); + it('should fallback to the root ng2.injector when compiled outside the dom', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const ng1Module = angular.module('ng1', []); diff --git a/packages/upgrade/test/static/integration/downgrade_component_spec.ts b/packages/upgrade/test/static/integration/downgrade_component_spec.ts index 6a7b4affcc..2f977c78e8 100644 --- a/packages/upgrade/test/static/integration/downgrade_component_spec.ts +++ b/packages/upgrade/test/static/integration/downgrade_component_spec.ts @@ -6,11 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectorRef, Compiler, Component, ComponentFactoryResolver, EventEmitter, Injector, Input, NgModule, NgModuleRef, OnChanges, OnDestroy, SimpleChanges, destroyPlatform} from '@angular/core'; +import {ChangeDetectorRef, Compiler, Component, ComponentFactoryResolver, Directive, ElementRef, EventEmitter, Injector, Input, NgModule, NgModuleRef, OnChanges, OnDestroy, SimpleChanges, destroyPlatform} from '@angular/core'; import {async, fakeAsync, tick} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; -import {UpgradeModule, downgradeComponent} from '@angular/upgrade/static'; +import {UpgradeComponent, UpgradeModule, downgradeComponent} from '@angular/upgrade/static'; import * as angular from '@angular/upgrade/static/src/common/angular1'; import {$apply, bootstrap, html, multiTrim, withEachNg1Version} from '../test_helpers'; @@ -461,6 +461,57 @@ withEachNg1Version(() => { }); })); + it('should properly run cleanup with multiple levels of nesting', async(() => { + let destroyed = false; + + @Component({ + selector: 'ng2-outer', + template: '
', + }) + class Ng2OuterComponent { + @Input() destroyIt = false; + } + + @Component({selector: 'ng2-inner', template: 'test'}) + class Ng2InnerComponent implements OnDestroy { + ngOnDestroy() { destroyed = true; } + } + + @Directive({selector: 'ng1'}) + class Ng1ComponentFacade extends UpgradeComponent { + constructor(elementRef: ElementRef, injector: Injector) { + super('ng1', elementRef, injector); + } + } + + @NgModule({ + imports: [BrowserModule, UpgradeModule], + declarations: [Ng1ComponentFacade, Ng2InnerComponent, Ng2OuterComponent], + entryComponents: [Ng2InnerComponent, Ng2OuterComponent], + }) + class Ng2Module { + ngDoBootstrap() {} + } + + const ng1Module = + angular.module('ng1', []) + .directive('ng1', () => ({template: ''})) + .directive('ng2Inner', downgradeComponent({component: Ng2InnerComponent})) + .directive('ng2Outer', downgradeComponent({component: Ng2OuterComponent})); + + const element = html(''); + + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { + expect(element.textContent).toBe('test'); + expect(destroyed).toBe(false); + + $apply(upgrade, 'destroyIt = true'); + + expect(element.textContent).toBe(''); + expect(destroyed).toBe(true); + }); + })); + it('should work when compiled outside the dom (by fallback to the root ng2.injector)', async(() => { From e95b61d42a3cacc8206417ef15387c865a059618 Mon Sep 17 00:00:00 2001 From: Stephen Fluin Date: Mon, 13 Nov 2017 08:46:11 -0800 Subject: [PATCH 003/604] docs: fix community tab in GitHub by copying CoC --- CODE_OF_CONDUCT.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..7ea7f3dda0 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,12 @@ +# Contributor Code of Conduct +## Version 0.3b-angular + +As contributors and maintainers of the Angular project, we pledge to respect everyone who contributes by posting issues, updating documentation, submitting pull requests, providing feedback in comments, and any other activities. + +Communication through any of Angular's channels (GitHub, Gitter, IRC, mailing lists, Google+, Twitter, etc.) must be constructive and never resort to personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. + +We promise to extend courtesy and respect to everyone involved in this project regardless of gender, gender identity, sexual orientation, disability, age, race, ethnicity, religion, or level of experience. We expect anyone contributing to the Angular project to do the same. + +If any member of the community violates this code of conduct, the maintainers of the Angular project may take action, removing issues, comments, and PRs or blocking accounts as deemed appropriate. + +If you are subject to or witness unacceptable behavior, or have any other concerns, please email us at [conduct@angular.io](mailto:conduct@angular.io). From 5170ffe844738b17f278fc502fa8a2078c86c4ee Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Sun, 25 Feb 2018 22:55:30 +0100 Subject: [PATCH 004/604] build: update api golden files (#22402) `ts-api-guardion` has been updated to accept new TypeScript syntax PR Close #22402 --- tools/public_api_guard/platform-browser/platform-browser.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/public_api_guard/platform-browser/platform-browser.d.ts b/tools/public_api_guard/platform-browser/platform-browser.d.ts index 21a6f2d586..5fb21c9625 100644 --- a/tools/public_api_guard/platform-browser/platform-browser.d.ts +++ b/tools/public_api_guard/platform-browser/platform-browser.d.ts @@ -70,7 +70,7 @@ export declare class HammerGestureConfig { } /** @experimental */ -export declare function makeStateKey(key: string): StateKey; /** @experimental */ export declare class Meta { From 930ecacd8611af7d8e8b533fe796a2839edae20c Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Tue, 27 Feb 2018 08:05:09 -0800 Subject: [PATCH 005/604] build: update ts-api-guardian version (#22402) PR Close #22402 --- package.json | 2 +- yarn.lock | 55 +++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 69dbd70833..6515cb834e 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "source-map": "0.5.7", "source-map-support": "0.4.18", "systemjs": "0.18.10", - "ts-api-guardian": "0.2.2", + "ts-api-guardian": "^0.3.0", "tsickle": "0.26.0", "tslint": "5.7.0", "tslint-eslint-rules": "4.1.1", diff --git a/yarn.lock b/yarn.lock index 40a5ae4e5e..e3ff22494d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -288,6 +288,12 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" +ansi-styles@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" + dependencies: + color-convert "^1.9.0" + any-promise@^1.0.0, any-promise@^1.1.0, any-promise@~1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -1001,6 +1007,14 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" +chalk@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796" + dependencies: + ansi-styles "^3.2.0" + escape-string-regexp "^1.0.5" + supports-color "^5.2.0" + char-spinner@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/char-spinner/-/char-spinner-1.0.1.tgz#e6ea67bd247e107112983b7ab0479ed362800081" @@ -1161,6 +1175,16 @@ coffee-script@1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.3.3.tgz#150d6b4cb522894369efed6a2101c20bc7f4a4f4" +color-convert@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" + dependencies: + color-name "^1.1.1" + +color-name@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + colors@0.6.0-1: version "0.6.0-1" resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.0-1.tgz#6dbb68ceb8bc60f2b313dcc5ce1599f06d19e67a" @@ -1943,7 +1967,7 @@ didyoumean@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.1.tgz#e92edfdada6537d484d73c0172fd1eba0c4976ff" -diff@^2.0.2, diff@^2.2.3: +diff@^2.0.2: version "2.2.3" resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99" @@ -1951,6 +1975,10 @@ diff@^3.2.0: version "3.3.1" resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" +diff@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" + doctrine@^0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-0.7.2.tgz#7cb860359ba3be90e040b26b729ce4bfa654c523" @@ -3364,6 +3392,10 @@ has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + has-gulplog@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce" @@ -6839,6 +6871,12 @@ supports-color@^3.1.0: dependencies: has-flag "^1.0.0" +supports-color@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.2.0.tgz#b0d5333b1184dd3666cbe5aa0b45c5ac7ac17a4a" + dependencies: + has-flag "^3.0.0" + symbol-observable@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" @@ -7101,14 +7139,13 @@ try-require@^1.0.0: version "1.2.1" resolved "https://registry.yarnpkg.com/try-require/-/try-require-1.2.1.tgz#34489a2cac0c09c1cc10ed91ba011594d4333be2" -ts-api-guardian@0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/ts-api-guardian/-/ts-api-guardian-0.2.2.tgz#b23bbb2865d0c4aee161730a74f59a00c4f06c8b" +ts-api-guardian@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/ts-api-guardian/-/ts-api-guardian-0.3.0.tgz#c5fbba9991840328f5ed0c89765a23e707645aa3" dependencies: - chalk "^1.1.3" - diff "^2.2.3" + chalk "^2.3.1" + diff "^3.4.0" minimist "^1.2.0" - typescript "2.0.10" tsickle@0.26.0: version "0.26.0" @@ -7213,10 +7250,6 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@2.0.10: - version "2.0.10" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.0.10.tgz#ccdd4ed86fd5550a407101a0814012e1b3fac3dd" - typescript@2.6.x: version "2.6.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" From e454c5a98e98559bed49330c8c468223c80f2f5a Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Tue, 20 Feb 2018 13:31:31 -0800 Subject: [PATCH 006/604] refactor(ivy): store creationMode in LView.flags (#22417) PR Close #22417 --- packages/core/src/render3/instructions.ts | 10 +++++----- packages/core/src/render3/interfaces/view.ts | 11 +++++++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index ecde36c922..b2b977f1f2 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -12,7 +12,7 @@ import {assertEqual, assertLessThan, assertNotEqual, assertNotNull, assertNull, import {LContainer, TContainer} from './interfaces/container'; import {CssSelector, LProjection} from './interfaces/projection'; import {LQueries} from './interfaces/query'; -import {LView, LifecycleStage, TData, TView} from './interfaces/view'; +import {LView, LViewFlags, LifecycleStage, TData, TView} from './interfaces/view'; import {LContainerNode, LElementNode, LNode, LNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node'; import {assertNodeType} from './node_assert'; @@ -159,7 +159,7 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null) data = newView && newView.data; bindingIndex = newView && newView.bindingStartIndex || 0; tData = newView && newView.tView.data; - creationMode = newView && newView.creationMode; + creationMode = newView && (newView.flags & LViewFlags.CreationMode) === 1; cleanup = newView && newView.cleanup; renderer = newView && newView.renderer; @@ -183,7 +183,7 @@ export function leaveView(newView: LView): void { executeHooks( currentView.data, currentView.tView.viewHooks, currentView.tView.viewCheckHooks, creationMode); - currentView.creationMode = false; + currentView.flags &= ~LViewFlags.CreationMode; // Clear creationMode bit in view flags currentView.lifecycleStage = LifecycleStage.INIT; currentView.tView.firstTemplatePass = false; enterView(newView, null); @@ -194,7 +194,8 @@ export function createLView( context: any | null): LView { const newView = { parent: currentView, - id: viewId, // -1 for component views + id: viewId, // -1 for component views + flags: LViewFlags.CreationMode, node: null !, // until we initialize it in createNode. data: [], tView: tView, @@ -204,7 +205,6 @@ export function createLView( tail: null, next: null, bindingStartIndex: null, - creationMode: true, template: template, context: context, dynamicViewCount: 0, diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 89d3264755..252ff9387d 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -25,14 +25,16 @@ import {Renderer3} from './renderer'; */ export interface LView { /** - * Whether or not the view is in creationMode. + * Flags for this view. + * + * First bit: Whether or not the view is in creationMode. * * This must be stored in the view rather than using `data` as a marker so that * we can properly support embedded views. Otherwise, when exiting a child view * back into the parent view, `data` will be defined and `creationMode` will be * improperly reported as false. */ - creationMode: boolean; + flags: LViewFlags; /** * The parent view is needed when we exit the view and must restore the previous @@ -180,6 +182,11 @@ export interface LView { queries: LQueries|null; } +/** Flags associated with an LView (see LView.flags) */ +export enum LViewFlags { + CreationMode = 0b001 +} + /** Interface necessary to work with view tree traversal */ export interface LViewOrLContainer { next: LView|LContainer|null; From 8c358844dd3d3b78c61be62d3a46581002a6b8ea Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Fri, 23 Feb 2018 13:17:20 -0800 Subject: [PATCH 007/604] feat(ivy): support OnPush change detection (#22417) PR Close #22417 --- packages/core/src/render3/component.ts | 64 +---- packages/core/src/render3/definition.ts | 5 +- packages/core/src/render3/index.ts | 6 +- packages/core/src/render3/instructions.ts | 169 +++++++++++-- .../core/src/render3/interfaces/definition.ts | 5 + packages/core/src/render3/interfaces/view.ts | 31 ++- .../hello_world/bundle.golden_symbols.json | 9 +- .../test/render3/change_detection_spec.ts | 222 ++++++++++++++++++ .../compiler_canonical_spec.ts | 61 ++++- packages/core/test/render3/component_spec.ts | 4 +- packages/core/test/render3/di_spec.ts | 4 +- 11 files changed, 470 insertions(+), 110 deletions(-) create mode 100644 packages/core/test/render3/change_detection_spec.ts diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index 6a43cb3917..3ba8945603 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -13,11 +13,11 @@ import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_facto import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref'; import {assertNotNull} from './assert'; -import {NG_HOST_SYMBOL, createError, createLView, createTView, directiveCreate, enterView, getDirectiveInstance, hostElement, leaveView, locateHostElement, renderComponentOrTemplate} from './instructions'; +import {CLEAN_PROMISE, NG_HOST_SYMBOL, _getComponentHostLElementNode, createError, createLView, createTView, detectChanges, directiveCreate, enterView, getDirectiveInstance, hostElement, leaveView, locateHostElement, scheduleChangeDetection} from './instructions'; import {ComponentDef, ComponentType} from './interfaces/definition'; import {LElementNode} from './interfaces/node'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; -import {RootContext} from './interfaces/view'; +import {LViewFlags, RootContext} from './interfaces/view'; import {notImplemented, stringify} from './util'; @@ -169,12 +169,6 @@ export const NULL_INJECTOR: Injector = { } }; -/** - * A permanent marker promise which signifies that the current CD tree is - * clean. - */ -const CLEAN_PROMISE = Promise.resolve(null); - /** * Bootstraps a Component into an existing host element and returns an instance * of the component. @@ -204,7 +198,7 @@ export function renderComponent( const oldView = enterView( createLView( -1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), createTView(), - null, rootContext), + null, rootContext, componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways), null !); try { // Create element node at index 0 in data array @@ -221,51 +215,6 @@ export function renderComponent( return component; } -/** - * Synchronously perform change detection on a component (and possibly its sub-components). - * - * This function triggers change detection in a synchronous way on a component. There should - * be very little reason to call this function directly since a preferred way to do change - * detection is to {@link markDirty} the component and wait for the scheduler to call this method - * at some future point in time. This is because a single user action often results in many - * components being invalidated and calling change detection on each component synchronously - * would be inefficient. It is better to wait until all components are marked as dirty and - * then perform single change detection across all of the components - * - * @param component The component which the change detection should be performed on. - */ -export function detectChanges(component: T): void { - const hostNode = _getComponentHostLElementNode(component); - ngDevMode && assertNotNull(hostNode.data, 'Component host node should be attached to an LView'); - renderComponentOrTemplate(hostNode, hostNode.view, component); -} - -/** - * Mark the component as dirty (needing change detection). - * - * Marking a component dirty will schedule a change detection on this - * component at some point in the future. Marking an already dirty - * component as dirty is a noop. Only one outstanding change detection - * can be scheduled per component tree. (Two components bootstrapped with - * separate `renderComponent` will have separate schedulers) - * - * When the root component is bootstrapped with `renderComponent` a scheduler - * can be provided. - * - * @param component Component to mark as dirty. - */ -export function markDirty(component: T) { - const rootContext = getRootContext(component); - if (rootContext.clean == CLEAN_PROMISE) { - let res: null|((val: null) => void); - rootContext.clean = new Promise((r) => res = r); - rootContext.scheduler(() => { - detectChanges(rootContext.component); - res !(null); - rootContext.clean = CLEAN_PROMISE; - }); - } -} /** * Retrieve the root component of any component by walking the parent `LView` until @@ -285,13 +234,6 @@ function getRootContext(component: any): RootContext { return rootContext; } -function _getComponentHostLElementNode(component: T): LElementNode { - ngDevMode && assertNotNull(component, 'expecting component got null'); - const lElementNode = (component as any)[NG_HOST_SYMBOL] as LElementNode; - ngDevMode && assertNotNull(component, 'object is not a component'); - return lElementNode; -} - /** * Retrieve the host element of the component. * diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index 8fbdaf6f2d..5c47c59d30 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -7,6 +7,7 @@ */ import {SimpleChange} from '../change_detection/change_detection_util'; +import {ChangeDetectionStrategy} from '../change_detection/constants'; import {PipeTransform} from '../change_detection/pipe_transform'; import {OnChanges, SimpleChanges} from '../metadata/lifecycle_hooks'; import {RendererType2} from '../render/api'; @@ -55,7 +56,9 @@ export function defineComponent(componentDefinition: ComponentDefArgs): Co afterContentChecked: type.prototype.ngAfterContentChecked || null, afterViewInit: type.prototype.ngAfterViewInit || null, afterViewChecked: type.prototype.ngAfterViewChecked || null, - onDestroy: type.prototype.ngOnDestroy || null + onDestroy: type.prototype.ngOnDestroy || null, + onPush: (componentDefinition as ComponentDefArgs).changeDetection === + ChangeDetectionStrategy.OnPush }; const feature = componentDefinition.features; feature && feature.forEach((fn) => fn(def)); diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 5489b1b10b..855d7f3d16 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {createComponentRef, detectChanges, getHostElement, getRenderedText, markDirty, renderComponent, whenRendered} from './component'; +import {createComponentRef, getHostElement, getRenderedText, renderComponent, whenRendered} from './component'; import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, definePipe} from './definition'; import {InjectFlags} from './di'; import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './interfaces/definition'; @@ -64,6 +64,8 @@ export { embeddedViewStart as V, embeddedViewEnd as v, + detectChanges, + markDirty, } from './instructions'; export { @@ -109,11 +111,9 @@ export { defineComponent, defineDirective, definePipe, - detectChanges, createComponentRef, getHostElement, getRenderedText, - markDirty, renderComponent, whenRendered, }; diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index b2b977f1f2..c07c05108d 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -12,14 +12,14 @@ import {assertEqual, assertLessThan, assertNotEqual, assertNotNull, assertNull, import {LContainer, TContainer} from './interfaces/container'; import {CssSelector, LProjection} from './interfaces/projection'; import {LQueries} from './interfaces/query'; -import {LView, LViewFlags, LifecycleStage, TData, TView} from './interfaces/view'; +import {LView, LViewFlags, LifecycleStage, RootContext, TData, TView} from './interfaces/view'; import {LContainerNode, LElementNode, LNode, LNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node'; import {assertNodeType} from './node_assert'; import {appendChild, insertChild, insertView, appendProjectedNode, removeView, canInsertNativeNode} from './node_manipulation'; import {matchingSelectorIndex} from './node_selector_matcher'; import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType} from './interfaces/definition'; -import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer'; +import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer'; import {isDifferent, stringify} from './util'; import {executeHooks, executeContentHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks'; @@ -30,6 +30,13 @@ import {executeHooks, executeContentHooks, queueLifecycleHooks, queueInitHooks, */ export const NG_HOST_SYMBOL = '__ngHostLNode__'; +/** + * A permanent marker promise which signifies that the current CD tree is + * clean. + */ +const _CLEAN_PROMISE = Promise.resolve(null); + + /** * This property gets set before entering a template. * @@ -159,7 +166,7 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null) data = newView && newView.data; bindingIndex = newView && newView.bindingStartIndex || 0; tData = newView && newView.tView.data; - creationMode = newView && (newView.flags & LViewFlags.CreationMode) === 1; + creationMode = newView && (newView.flags & LViewFlags.CreationMode) === LViewFlags.CreationMode; cleanup = newView && newView.cleanup; renderer = newView && newView.renderer; @@ -183,7 +190,8 @@ export function leaveView(newView: LView): void { executeHooks( currentView.data, currentView.tView.viewHooks, currentView.tView.viewCheckHooks, creationMode); - currentView.flags &= ~LViewFlags.CreationMode; // Clear creationMode bit in view flags + // Views should be clean and in update mode after being checked, so these bits are cleared + currentView.flags &= ~(LViewFlags.CreationMode | LViewFlags.Dirty); currentView.lifecycleStage = LifecycleStage.INIT; currentView.tView.firstTemplatePass = false; enterView(newView, null); @@ -191,11 +199,11 @@ export function leaveView(newView: LView): void { export function createLView( viewId: number, renderer: Renderer3, tView: TView, template: ComponentTemplate| null, - context: any | null): LView { + context: any | null, flags: LViewFlags): LView { const newView = { parent: currentView, id: viewId, // -1 for component views - flags: LViewFlags.CreationMode, + flags: flags | LViewFlags.CreationMode, node: null !, // until we initialize it in createNode. data: [], tView: tView, @@ -326,7 +334,7 @@ export function renderTemplate( null, LNodeFlags.Element, hostNode, createLView( -1, providedRendererFactory.createRenderer(null, null), getOrCreateTView(template), - null, null)); + null, {}, LViewFlags.CheckAlways)); } const hostView = host.data !; ngDevMode && assertNotNull(hostView, 'Host node should have an LView defined in host.data.'); @@ -344,7 +352,8 @@ export function renderEmbeddedTemplate( previousOrParentNode = null !; let cm: boolean = false; if (viewNode == null) { - const view = createLView(-1, renderer, createTView(), template, context); + const view = + createLView(-1, renderer, createTView(), template, context, LViewFlags.CheckAlways); viewNode = createLNode(null, LNodeFlags.View, null, view); cm = true; } @@ -431,9 +440,10 @@ export function elementStart( let componentView: LView|null = null; if (isHostElement) { const tView = getOrCreateTView(hostComponentDef !.template); - componentView = addToViewTree(createLView( + const hostView = createLView( -1, rendererFactory.createRenderer(native, hostComponentDef !.rendererType), tView, - null, null)); + null, null, hostComponentDef !.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways); + componentView = addToViewTree(hostView); } // Only component views should be added to the view tree directly. Embedded views are @@ -583,8 +593,9 @@ export function locateHostElement( export function hostElement(rNode: RElement | null, def: ComponentDef) { resetApplicationState(); createLNode( - 0, LNodeFlags.Element, rNode, - createLView(-1, renderer, getOrCreateTView(def.template), null, null)); + 0, LNodeFlags.Element, rNode, createLView( + -1, renderer, getOrCreateTView(def.template), null, null, + def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways)); } @@ -602,15 +613,17 @@ export function listener(eventName: string, listener: EventListener, useCapture ngDevMode && assertPreviousIsParent(); const node = previousOrParentNode; const native = node.native as RElement; + const wrappedListener = wrapListenerWithDirtyLogic(currentView, listener); // In order to match current behavior, native DOM event listeners must be added for all // events (including outputs). + const cleanupFns = cleanup || (cleanup = currentView.cleanup = []); if (isProceduralRenderer(renderer)) { - const cleanupFn = renderer.listen(native, eventName, listener); - (cleanup || (cleanup = currentView.cleanup = [])).push(cleanupFn, null); + const cleanupFn = renderer.listen(native, eventName, wrappedListener); + cleanupFns.push(cleanupFn, null); } else { - native.addEventListener(eventName, listener, useCapture); - (cleanup || (cleanup = currentView.cleanup = [])).push(eventName, native, listener, useCapture); + native.addEventListener(eventName, wrappedListener, useCapture); + cleanupFns.push(eventName, native, wrappedListener, useCapture); } let tNode: TNode|null = node.tNode !; @@ -703,6 +716,7 @@ export function elementProperty(index: number, propName: string, value: T | N let dataValue: PropertyAliasValue|undefined; if (inputData && (dataValue = inputData[propName])) { setInputsForProperty(dataValue, value); + markDirtyIfOnPush(node); } else { const native = node.native; isProceduralRenderer(renderer) ? renderer.setProperty(native, propName, value) : @@ -1149,7 +1163,8 @@ export function embeddedViewStart(viewBlockId: number): boolean { } else { // When we create a new LView, we always reset the state of the instructions. const newView = createLView( - viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container), null, null); + viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container), null, null, + LViewFlags.CheckAlways); if (lContainer.queries) { newView.queries = lContainer.queries.enterView(lContainer.nextIndex); } @@ -1226,15 +1241,19 @@ export function directiveRefresh(directiveIndex: number, elementIndex: number ngDevMode && assertNodeType(element, LNodeFlags.Element); ngDevMode && assertNotNull(element.data, `Component's host node should have an LView attached.`); - ngDevMode && assertDataInRange(directiveIndex); - const directive = getDirectiveInstance(data[directiveIndex]); const hostView = element.data !; - const oldView = enterView(hostView, element); - try { - template(directive, creationMode); - } finally { - refreshDynamicChildren(); - leaveView(oldView); + + // Only CheckAlways components or dirty OnPush components should be checked + if (hostView.flags & (LViewFlags.CheckAlways | LViewFlags.Dirty)) { + ngDevMode && assertDataInRange(directiveIndex); + const directive = getDirectiveInstance(data[directiveIndex]); + const oldView = enterView(hostView, element); + try { + template(directive, creationMode); + } finally { + refreshDynamicChildren(); + leaveView(oldView); + } } } } @@ -1389,6 +1408,97 @@ export function addToViewTree(state: T): T { return state; } +/////////////////////////////// +//// Change detection +/////////////////////////////// + +/** If node is an OnPush component, marks its LView dirty. */ +export function markDirtyIfOnPush(node: LElementNode): void { + // Because data flows down the component tree, ancestors do not need to be marked dirty + if (node.data && !(node.data.flags & LViewFlags.CheckAlways)) { + node.data.flags |= LViewFlags.Dirty; + } +} + +/** + * Wraps an event listener so its host view and its ancestor views will be marked dirty + * whenever the event fires. Necessary to support OnPush components. + */ +export function wrapListenerWithDirtyLogic(view: LView, listener: EventListener): EventListener { + return function(e: Event) { + markViewDirty(view); + listener(e); + }; +} + +/** Marks current view and all ancestors dirty */ +function markViewDirty(view: LView): void { + let currentView: LView|null = view; + + while (currentView.parent != null) { + currentView.flags |= LViewFlags.Dirty; + currentView = currentView.parent; + } + currentView.flags |= LViewFlags.Dirty; + + ngDevMode && assertNotNull(currentView !.context, 'rootContext'); + scheduleChangeDetection(currentView !.context as RootContext); +} + + +/** Given a root context, schedules change detection at that root. */ +export function scheduleChangeDetection(rootContext: RootContext) { + if (rootContext.clean == _CLEAN_PROMISE) { + let res: null|((val: null) => void); + rootContext.clean = new Promise((r) => res = r); + rootContext.scheduler(() => { + detectChanges(rootContext.component); + res !(null); + rootContext.clean = _CLEAN_PROMISE; + }); + } +} + +/** + * Synchronously perform change detection on a component (and possibly its sub-components). + * + * This function triggers change detection in a synchronous way on a component. There should + * be very little reason to call this function directly since a preferred way to do change + * detection is to {@link markDirty} the component and wait for the scheduler to call this method + * at some future point in time. This is because a single user action often results in many + * components being invalidated and calling change detection on each component synchronously + * would be inefficient. It is better to wait until all components are marked as dirty and + * then perform single change detection across all of the components + * + * @param component The component which the change detection should be performed on. + */ +export function detectChanges(component: T): void { + const hostNode = _getComponentHostLElementNode(component); + ngDevMode && assertNotNull(hostNode.data, 'Component host node should be attached to an LView'); + renderComponentOrTemplate(hostNode, hostNode.view, component); +} + + +/** + * Mark the component as dirty (needing change detection). + * + * Marking a component dirty will schedule a change detection on this + * component at some point in the future. Marking an already dirty + * component as dirty is a noop. Only one outstanding change detection + * can be scheduled per component tree. (Two components bootstrapped with + * separate `renderComponent` will have separate schedulers) + * + * When the root component is bootstrapped with `renderComponent`, a scheduler + * can be provided. + * + * @param component Component to mark as dirty. + */ +export function markDirty(component: T) { + ngDevMode && assertNotNull(component, 'component'); + const lElementNode = _getComponentHostLElementNode(component); + markViewDirty(lElementNode.view); +} + /////////////////////////////// //// Bindings & interpolations /////////////////////////////// @@ -1649,3 +1759,12 @@ function assertDataInRange(index: number, arr?: any[]) { function assertDataNext(index: number) { assertEqual(data.length, index, 'index expected to be at the end of data'); } + +export function _getComponentHostLElementNode(component: T): LElementNode { + ngDevMode && assertNotNull(component, 'expecting component got null'); + const lElementNode = (component as any)[NG_HOST_SYMBOL] as LElementNode; + ngDevMode && assertNotNull(component, 'object is not a component'); + return lElementNode; +} + +export const CLEAN_PROMISE = _CLEAN_PROMISE; diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index d156135911..b1a9d067d4 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {ChangeDetectionStrategy} from '../../change_detection/constants'; import {PipeTransform} from '../../change_detection/pipe_transform'; import {RendererType2} from '../../render/api'; import {Type} from '../../type'; @@ -124,6 +125,9 @@ export interface ComponentDef extends DirectiveDef { * NOTE: only used with component directives. */ readonly rendererType: RendererType2|null; + + /** Whether or not this component's ChangeDetectionStrategy is OnPush */ + readonly onPush: boolean; } /** @@ -169,6 +173,7 @@ export interface ComponentDefArgs extends DirectiveDefArgs { template: ComponentTemplate; features?: ComponentDefFeature[]; rendererType?: RendererType2; + changeDetection?: ChangeDetectionStrategy; } export type DirectiveDefFeature = (directiveDef: DirectiveDef) => void; diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 252ff9387d..3796ad0d6c 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -24,16 +24,7 @@ import {Renderer3} from './renderer'; * don't have to edit the data array based on which views are present. */ export interface LView { - /** - * Flags for this view. - * - * First bit: Whether or not the view is in creationMode. - * - * This must be stored in the view rather than using `data` as a marker so that - * we can properly support embedded views. Otherwise, when exiting a child view - * back into the parent view, `data` will be defined and `creationMode` will be - * improperly reported as false. - */ + /** Flags for this view (see LViewFlags for definition of each bit). */ flags: LViewFlags; /** @@ -182,9 +173,23 @@ export interface LView { queries: LQueries|null; } -/** Flags associated with an LView (see LView.flags) */ -export enum LViewFlags { - CreationMode = 0b001 +/** Flags associated with an LView (saved in LView.flags) */ +export const enum LViewFlags { + /** + * Whether or not the view is in creationMode. + * + * This must be stored in the view rather than using `data` as a marker so that + * we can properly support embedded views. Otherwise, when exiting a child view + * back into the parent view, `data` will be defined and `creationMode` will be + * improperly reported as false. + */ + CreationMode = 0b001, + + /** Whether this view has default change detection strategy (checks always) or onPush */ + CheckAlways = 0b010, + + /** Whether or not this view is currently dirty (needing check) */ + Dirty = 0b100 } /** Interface necessary to work with view tree traversal */ diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index d5c3e8f52c..538b2c74d5 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -5,6 +5,9 @@ { "name": "EMPTY$1" }, + { + "name": "NG_HOST_SYMBOL" + }, { "name": "NO_CHANGE" }, @@ -38,6 +41,9 @@ { "name": "currentView" }, + { + "name": "detectChanges" + }, { "name": "domRendererFactory3" }, @@ -77,9 +83,6 @@ { "name": "refreshDynamicChildren" }, - { - "name": "renderComponentOrTemplate" - }, { "name": "renderEmbeddedTemplate" }, diff --git a/packages/core/test/render3/change_detection_spec.ts b/packages/core/test/render3/change_detection_spec.ts new file mode 100644 index 0000000000..143c476790 --- /dev/null +++ b/packages/core/test/render3/change_detection_spec.ts @@ -0,0 +1,222 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ChangeDetectionStrategy, DoCheck} from '../../src/core'; +import {getRenderedText} from '../../src/render3/component'; +import {defineComponent} from '../../src/render3/index'; +import {bind, detectChanges, directiveRefresh, elementEnd, elementProperty, elementStart, interpolation1, interpolation2, listener, text, textBinding} from '../../src/render3/instructions'; +import {containerEl, renderComponent, requestAnimationFrame} from './render_util'; + +describe('OnPush change detection', () => { + let comp: MyComponent; + + class MyComponent implements DoCheck { + /* @Input() */ + name = 'Nancy'; + doCheckCount = 0; + + ngDoCheck(): void { this.doCheckCount++; } + + onClick() {} + + static ngComponentDef = defineComponent({ + type: MyComponent, + tag: 'my-comp', + factory: () => comp = new MyComponent(), + /** + * {{ doCheckCount }} - {{ name }} + * + */ + template: (ctx: MyComponent, cm: boolean) => { + if (cm) { + text(0); + elementStart(1, 'button'); + { + listener('click', () => { ctx.onClick(); }); + } + elementEnd(); + } + textBinding(0, interpolation2('', ctx.doCheckCount, ' - ', ctx.name, '')); + }, + changeDetection: ChangeDetectionStrategy.OnPush, + inputs: {name: 'name'} + }); + } + + class MyApp { + name: string = 'Nancy'; + + static ngComponentDef = defineComponent({ + type: MyApp, + tag: 'my-app', + factory: () => new MyApp(), + /** */ + template: (ctx: MyApp, cm: boolean) => { + if (cm) { + elementStart(0, MyComponent); + elementEnd(); + } + elementProperty(0, 'name', bind(ctx.name)); + MyComponent.ngComponentDef.h(1, 0); + directiveRefresh(1, 0); + } + }); + } + + it('should check OnPush components on initialization', () => { + const myApp = renderComponent(MyApp); + expect(getRenderedText(myApp)).toEqual('1 - Nancy'); + }); + + it('should call doCheck even when OnPush components are not dirty', () => { + const myApp = renderComponent(MyApp); + + detectChanges(myApp); + expect(comp.doCheckCount).toEqual(2); + + detectChanges(myApp); + expect(comp.doCheckCount).toEqual(3); + }); + + it('should skip OnPush components in update mode when they are not dirty', () => { + const myApp = renderComponent(MyApp); + + detectChanges(myApp); + // doCheckCount is 2, but 1 should be rendered since it has not been marked dirty. + expect(getRenderedText(myApp)).toEqual('1 - Nancy'); + + detectChanges(myApp); + // doCheckCount is 3, but 1 should be rendered since it has not been marked dirty. + expect(getRenderedText(myApp)).toEqual('1 - Nancy'); + }); + + it('should check OnPush components in update mode when inputs change', () => { + const myApp = renderComponent(MyApp); + + myApp.name = 'Bess'; + detectChanges(myApp); + expect(getRenderedText(myApp)).toEqual('2 - Bess'); + + myApp.name = 'George'; + detectChanges(myApp); + expect(getRenderedText(myApp)).toEqual('3 - George'); + + detectChanges(myApp); + expect(getRenderedText(myApp)).toEqual('3 - George'); + }); + + it('should check OnPush components in update mode when component events occur', () => { + const myApp = renderComponent(MyApp); + expect(getRenderedText(myApp)).toEqual('1 - Nancy'); + + const button = containerEl.querySelector('button') !; + button.click(); + requestAnimationFrame.flush(); + expect(getRenderedText(myApp)).toEqual('2 - Nancy'); + + detectChanges(myApp); + expect(getRenderedText(myApp)).toEqual('2 - Nancy'); + }); + + it('should not check OnPush components in update mode when parent events occur', () => { + class ButtonParent { + noop() {} + + static ngComponentDef = defineComponent({ + type: ButtonParent, + tag: 'button-parent', + factory: () => new ButtonParent(), + /** + * + * + */ + template: (ctx: ButtonParent, cm: boolean) => { + if (cm) { + elementStart(0, MyComponent); + elementEnd(); + elementStart(2, 'button', ['id', 'parent']); + { listener('click', () => ctx.noop()); } + elementEnd(); + } + MyComponent.ngComponentDef.h(1, 0); + directiveRefresh(1, 0); + } + }); + } + const buttonParent = renderComponent(ButtonParent); + expect(getRenderedText(buttonParent)).toEqual('1 - Nancy'); + + const button = containerEl.querySelector('button#parent') !; + (button as HTMLButtonElement).click(); + requestAnimationFrame.flush(); + expect(getRenderedText(buttonParent)).toEqual('1 - Nancy'); + }); + + it('should check parent OnPush components in update mode when child events occur', () => { + let parent: ButtonParent; + + class ButtonParent implements DoCheck { + doCheckCount = 0; + ngDoCheck(): void { this.doCheckCount++; } + + static ngComponentDef = defineComponent({ + type: ButtonParent, + tag: 'button-parent', + factory: () => parent = new ButtonParent(), + /** {{ doCheckCount }} - */ + template: (ctx: ButtonParent, cm: boolean) => { + if (cm) { + text(0); + elementStart(1, MyComponent); + elementEnd(); + } + textBinding(0, interpolation1('', ctx.doCheckCount, ' - ')); + MyComponent.ngComponentDef.h(2, 1); + directiveRefresh(2, 1); + }, + changeDetection: ChangeDetectionStrategy.OnPush + }); + } + + class MyButtonApp { + static ngComponentDef = defineComponent({ + type: MyButtonApp, + tag: 'my-button-app', + factory: () => new MyButtonApp(), + /** */ + template: (ctx: MyButtonApp, cm: boolean) => { + if (cm) { + elementStart(0, ButtonParent); + elementEnd(); + } + ButtonParent.ngComponentDef.h(1, 0); + directiveRefresh(1, 0); + } + }); + } + + const myButtonApp = renderComponent(MyButtonApp); + expect(parent !.doCheckCount).toEqual(1); + expect(comp !.doCheckCount).toEqual(1); + expect(getRenderedText(myButtonApp)).toEqual('1 - 1 - Nancy'); + + detectChanges(myButtonApp); + expect(parent !.doCheckCount).toEqual(2); + // parent isn't checked, so child doCheck won't run + expect(comp !.doCheckCount).toEqual(1); + expect(getRenderedText(myButtonApp)).toEqual('1 - 1 - Nancy'); + + const button = containerEl.querySelector('button'); + button !.click(); + requestAnimationFrame.flush(); + expect(parent !.doCheckCount).toEqual(3); + expect(comp !.doCheckCount).toEqual(2); + expect(getRenderedText(myButtonApp)).toEqual('3 - 2 - Nancy'); + }); + +}); diff --git a/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts b/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts index a3c13167d0..e69069ff23 100644 --- a/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts +++ b/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; +import {ChangeDetectionStrategy, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; import * as $r3$ from '../../../src/core_render3_private_export'; import {renderComponent, toHtml} from '../render_util'; @@ -316,6 +316,65 @@ describe('compiler specification', () => { expect(renderComp(MyApp)).toEqual(`
`); }); + it('should support onPush components', () => { + type $MyApp$ = MyApp; + type $MyComp$ = MyComp; + + @Component({ + selector: 'my-comp', + template: ` + {{ name }} + `, + changeDetection: ChangeDetectionStrategy.OnPush + }) + class MyComp { + @Input() name: string; + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComp, + tag: 'my-comp', + factory: function MyComp_Factory() { return new MyComp(); }, + template: function MyComp_Template(ctx: $MyComp$, cm: $boolean$) { + if (cm) { + $r3$.ɵT(0); + } + $r3$.ɵt(0, $r3$.ɵb(ctx.name)); + }, + inputs: {name: 'name'}, + changeDetection: ChangeDetectionStrategy.OnPush + }); + // /NORMATIVE + } + + @Component({ + selector: 'my-app', + template: ` + + ` + }) + class MyApp { + name = 'some name'; + + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, MyComp); + $r3$.ɵe(); + } + $r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name)); + MyComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + } + + expect(renderComp(MyApp)).toEqual(`some name`); + }); + xit('should support structural directives', () => { type $MyComponent$ = MyComponent; diff --git a/packages/core/test/render3/component_spec.ts b/packages/core/test/render3/component_spec.ts index ee769c47b2..83989080ea 100644 --- a/packages/core/test/render3/component_spec.ts +++ b/packages/core/test/render3/component_spec.ts @@ -9,9 +9,9 @@ import {withBody} from '@angular/core/testing'; import {DoCheck, ViewEncapsulation} from '../../src/core'; -import {detectChanges, getRenderedText, whenRendered} from '../../src/render3/component'; +import {getRenderedText, whenRendered} from '../../src/render3/component'; import {defineComponent, markDirty} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, directiveRefresh, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, text, textBinding} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, detectChanges, directiveRefresh, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, text, textBinding} from '../../src/render3/instructions'; import {createRendererType2} from '../../src/view/index'; import {getRendererFactory2} from './imported_renderer2'; diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 5e08c25542..8184d55c31 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -14,6 +14,7 @@ import {PublicFeature, defineDirective, inject, injectElementRef, injectTemplate import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, text, textBinding} from '../../src/render3/instructions'; import {LInjector} from '../../src/render3/interfaces/injector'; import {LNodeFlags} from '../../src/render3/interfaces/node'; +import {LViewFlags} from '../../src/render3/interfaces/view'; import {renderComponent, renderToHtml} from './render_util'; @@ -320,7 +321,8 @@ describe('di', () => { describe('getOrCreateNodeInjector', () => { it('should handle initial undefined state', () => { - const contentView = createLView(-1, null !, createTView(), null, null); + const contentView = + createLView(-1, null !, createTView(), null, null, LViewFlags.CheckAlways); const oldView = enterView(contentView, null !); try { const parent = createLNode(0, LNodeFlags.Element, null, null); From e75f0cee18d92c8d27462cbaa6606e6c3d52b306 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 23 Feb 2018 13:45:38 +0000 Subject: [PATCH 008/604] build(aio): deprecate `@howToUse` and `@whatItDoes` tags (#22401) See https://github.com/angular/angular/issues/22135#issuecomment-367632372 PR Close #22401 --- .../angular-api-package/tag-defs/howToUse.js | 11 +++++++++-- .../angular-api-package/tag-defs/whatItDoes.js | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/aio/tools/transforms/angular-api-package/tag-defs/howToUse.js b/aio/tools/transforms/angular-api-package/tag-defs/howToUse.js index f6073bb87a..aa5f3a2974 100644 --- a/aio/tools/transforms/angular-api-package/tag-defs/howToUse.js +++ b/aio/tools/transforms/angular-api-package/tag-defs/howToUse.js @@ -1,3 +1,10 @@ -module.exports = function() { - return {name: 'howToUse'}; +module.exports = function(log, createDocMessage) { + return { + name: 'howToUse', + transforms(doc, tag, value) { + log.warn(createDocMessage('Deprecated `@howToUse` tag found', doc)); + log.warn('PLEASE FIX by renaming to `@usageNotes.'); + return value; + } + }; }; diff --git a/aio/tools/transforms/angular-api-package/tag-defs/whatItDoes.js b/aio/tools/transforms/angular-api-package/tag-defs/whatItDoes.js index f11bb62a18..86715c6a5b 100644 --- a/aio/tools/transforms/angular-api-package/tag-defs/whatItDoes.js +++ b/aio/tools/transforms/angular-api-package/tag-defs/whatItDoes.js @@ -1,3 +1,10 @@ -module.exports = function() { - return {name: 'whatItDoes'}; +module.exports = function(log, createDocMessage) { + return { + name: 'whatItDoes', + transforms(doc, tag, value) { + log.warn(createDocMessage('Deprecated `@whatItDoes` tag found', doc)); + log.warn('PLEASE FIX by adding the content of this tag as the first paragraph of the `@description` tag.'); + return value; + } + }; }; From b924ce3a627b92491d30ef05d61f21f0e765280f Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 23 Feb 2018 13:47:26 +0000 Subject: [PATCH 009/604] build(aio): add processor to migrate legacy tags `@whatItDoes` and `@howToUse` (#22401) See https://github.com/angular/angular/issues/22135#issuecomment-367632372 PR Close #22401 --- .../transforms/angular-api-package/index.js | 1 + .../processors/migrateLegacyJSDocTags.js | 36 ++++++++++ .../processors/migrateLegacyJSDocTags.spec.js | 66 +++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 aio/tools/transforms/angular-api-package/processors/migrateLegacyJSDocTags.js create mode 100644 aio/tools/transforms/angular-api-package/processors/migrateLegacyJSDocTags.spec.js diff --git a/aio/tools/transforms/angular-api-package/index.js b/aio/tools/transforms/angular-api-package/index.js index 7302a9b9a6..289db0f15b 100644 --- a/aio/tools/transforms/angular-api-package/index.js +++ b/aio/tools/transforms/angular-api-package/index.js @@ -14,6 +14,7 @@ const { API_SOURCE_PATH, API_TEMPLATES_PATH, requireFolder } = require('../confi module.exports = new Package('angular-api', [basePackage, typeScriptPackage]) // Register the processors + .processor(require('./processors/migrateLegacyJSDocTags')) .processor(require('./processors/convertPrivateClassesToInterfaces')) .processor(require('./processors/generateApiListDoc')) .processor(require('./processors/addNotYetDocumentedProperty')) diff --git a/aio/tools/transforms/angular-api-package/processors/migrateLegacyJSDocTags.js b/aio/tools/transforms/angular-api-package/processors/migrateLegacyJSDocTags.js new file mode 100644 index 0000000000..cf3f10a322 --- /dev/null +++ b/aio/tools/transforms/angular-api-package/processors/migrateLegacyJSDocTags.js @@ -0,0 +1,36 @@ +module.exports = function migrateLegacyJSDocTags(log, createDocMessage) { + return { + $runAfter: ['tags-extracted'], + $runBefore: ['processing-docs'], + $process(docs) { + let migrated = false; + docs.forEach(doc => { + if (doc.howToUse) { + if (doc.usageNotes) { + throw new Error(createDocMessage('`@usageNotes` and the deprecated `@howToUse` are not allowed on the same doc', doc)); + } + log.debug(createDocMessage('Using deprecated `@howToUse` tag as though it was `@usageNotes` tag', doc)); + doc.usageNotes = doc.howToUse; + doc.howToUse = null; + migrated = true; + } + + if (doc.whatItDoes) { + log.debug(createDocMessage('Merging the content of `@whatItDoes` tag into the description.', doc)); + if (doc.description) { + doc.description = `${doc.whatItDoes}\n\n${doc.description}`; + } else { + doc.description = doc.whatItDoes; + } + doc.whatItDoes = null; + migrated = true; + } + }); + + if (migrated) { + log.warn('Some deprecated tags were migrated.'); + log.warn('This automatic handling will be removed in a future version of the doc generation.\n'); + } + } + }; +}; diff --git a/aio/tools/transforms/angular-api-package/processors/migrateLegacyJSDocTags.spec.js b/aio/tools/transforms/angular-api-package/processors/migrateLegacyJSDocTags.spec.js new file mode 100644 index 0000000000..e64a77dc81 --- /dev/null +++ b/aio/tools/transforms/angular-api-package/processors/migrateLegacyJSDocTags.spec.js @@ -0,0 +1,66 @@ +const testPackage = require('../../helpers/test-package'); +const processorFactory = require('./migrateLegacyJSDocTags'); +const log = require('dgeni/lib/mocks/log')(false); +const createDocMessage = require('dgeni-packages/base/services/createDocMessage')(); +const Dgeni = require('dgeni'); + +describe('migrateLegacyJSDocTags processor', () => { + + it('should be available on the injector', () => { + const dgeni = new Dgeni([testPackage('angular-api-package')]); + const injector = dgeni.configureInjector(); + const processor = injector.get('migrateLegacyJSDocTags'); + expect(processor.$process).toBeDefined(); + }); + + it('should run before the correct processor', () => { + const processor = processorFactory(log, createDocMessage); + expect(processor.$runBefore).toEqual(['processing-docs']); + }); + + it('should run after the correct processor', () => { + const processor = processorFactory(log, createDocMessage); + expect(processor.$runAfter).toEqual(['tags-extracted']); + }); + + it('should migrate `howToUse` property to `usageNotes` property', () => { + const processor = processorFactory(log, createDocMessage); + const docs = [ + { howToUse: 'this is how to use it' } + ]; + processor.$process(docs); + expect(docs[0].howToUse).toBe(null); + expect(docs[0].usageNotes).toEqual('this is how to use it'); + }); + + it('should migrate `whatItDoes` property to the `description`', () => { + const processor = processorFactory(log, createDocMessage); + const docs = [ + { whatItDoes: 'what it does' }, + { whatItDoes: 'what it does', description: 'the description' }, + { description: 'the description' } + ]; + processor.$process(docs); + expect(docs[0].whatItDoes).toBe(null); + expect(docs[0].description).toEqual('what it does'); + + expect(docs[1].whatItDoes).toBe(null); + expect(docs[1].description).toEqual('what it does\n\nthe description'); + + expect(docs[2].whatItDoes).toBeUndefined(); + expect(docs[2].description).toEqual('the description'); + }); + + it('should ignore docs that have neither `howToUse` nor `whatItDoes` properties', () => { + const processor = processorFactory(log, createDocMessage); + const docs = [ + { }, + { description: 'the description' } + ]; + processor.$process(docs); + expect(docs).toEqual([ + { }, + { description: 'the description' } + ]); + }); +}); \ No newline at end of file From 11264e2174e5bb8ab726a9fc445b1bb0a39d2102 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 27 Feb 2018 10:54:03 +0000 Subject: [PATCH 010/604] fix(aio): remove heading border (#22401) PR Close #22401 --- aio/src/styles/0-base/_typography.scss | 20 ++++--------------- .../styles/1-layouts/_marketing-layout.scss | 4 ---- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/aio/src/styles/0-base/_typography.scss b/aio/src/styles/0-base/_typography.scss index 764b67e154..8d0f38f361 100755 --- a/aio/src/styles/0-base/_typography.scss +++ b/aio/src/styles/0-base/_typography.scss @@ -12,16 +12,10 @@ h1 { font-size: 24px; font-weight: 500; margin: 8px 0px; -} -h1:after { - content: ""; - display: block; - height: 1px; - width: 40%; - margin: 24px 0px 10px; - background: $lightgray; - clear: both; + @media screen and (max-width: 600px) { + margin-top: 0; + } } h2 { @@ -60,13 +54,7 @@ h6 { clear: both; } -h1 { - @media screen and (max-width: 600px) { - margin: 0; - } -} - -h1:after, h2, h3, h4, h5, h6 { +h2, h3, h4, h5, h6 { @media screen and (max-width: 600px) { margin: 8px 0; } diff --git a/aio/src/styles/1-layouts/_marketing-layout.scss b/aio/src/styles/1-layouts/_marketing-layout.scss index cc2ca98ffb..f90c0ec4ec 100644 --- a/aio/src/styles/1-layouts/_marketing-layout.scss +++ b/aio/src/styles/1-layouts/_marketing-layout.scss @@ -20,10 +20,6 @@ transform: none; } - h1:after { - content: none; - } - .hero-title { display: inline-block; font-size: 28px; From b107131f8aec062db1f3abe0e77acf60dc8bf0d4 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 23 Feb 2018 13:50:47 +0000 Subject: [PATCH 011/604] build(aio): split the description property in API docs (#22401) * The first paragraph is now split off into the `shortDescription` property. * Usage of `howToUse` and `whatItDoes` have been updated. * The "Overview" heading for class is removed as it is self-evident * The original horizontal rule styling below the main heading is removed as not part of the new design Closes #22385 PR Close #22401 --- aio/src/styles/2-modules/_banner.scss | 25 ------- aio/src/styles/2-modules/_modules-dir.scss | 1 - .../transforms/angular-api-package/index.js | 5 ++ .../processors/splitDescription.js | 28 ++++++++ .../processors/splitDescription.spec.js | 71 +++++++++++++++++++ .../templates/api/class.template.html | 4 +- .../templates/api/export-base.template.html | 4 +- .../api/includes/class-overview.html | 1 - .../templates/api/includes/how-to-use.html | 6 -- .../templates/api/includes/usageNotes.html | 6 ++ .../templates/api/includes/what-it-does.html | 5 -- 11 files changed, 114 insertions(+), 42 deletions(-) delete mode 100644 aio/src/styles/2-modules/_banner.scss create mode 100644 aio/tools/transforms/angular-api-package/processors/splitDescription.js create mode 100644 aio/tools/transforms/angular-api-package/processors/splitDescription.spec.js delete mode 100644 aio/tools/transforms/templates/api/includes/how-to-use.html create mode 100644 aio/tools/transforms/templates/api/includes/usageNotes.html delete mode 100644 aio/tools/transforms/templates/api/includes/what-it-does.html diff --git a/aio/src/styles/2-modules/_banner.scss b/aio/src/styles/2-modules/_banner.scss deleted file mode 100644 index 1773713fc5..0000000000 --- a/aio/src/styles/2-modules/_banner.scss +++ /dev/null @@ -1,25 +0,0 @@ -/* BANNER */ - -.info-banner { - margin: 16px 0; - justify-content: center; - background: $white; - border: 1px solid rgba($lightgray, 0.5); - border-radius: 4px; - box-sizing: border-box; - padding: 16px; - background: $white; - height: auto; - overflow: visible; - - @media screen and (max-width: 600px) { - text-align: center; - } - - p, .text-body { - color: $darkgray; - line-height: 32px; - margin: 0; - text-align: center; - } -} \ No newline at end of file diff --git a/aio/src/styles/2-modules/_modules-dir.scss b/aio/src/styles/2-modules/_modules-dir.scss index a397b70782..1bb94dc4fb 100644 --- a/aio/src/styles/2-modules/_modules-dir.scss +++ b/aio/src/styles/2-modules/_modules-dir.scss @@ -5,7 +5,6 @@ @import 'alert'; @import 'api-pages'; @import 'api-list'; - @import 'banner'; @import 'buttons'; @import 'callout'; @import 'card'; diff --git a/aio/tools/transforms/angular-api-package/index.js b/aio/tools/transforms/angular-api-package/index.js index 289db0f15b..b4728d01ce 100644 --- a/aio/tools/transforms/angular-api-package/index.js +++ b/aio/tools/transforms/angular-api-package/index.js @@ -15,6 +15,7 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage]) // Register the processors .processor(require('./processors/migrateLegacyJSDocTags')) + .processor(require('./processors/splitDescription')) .processor(require('./processors/convertPrivateClassesToInterfaces')) .processor(require('./processors/generateApiListDoc')) .processor(require('./processors/addNotYetDocumentedProperty')) @@ -91,6 +92,10 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage]) parseTagsProcessor.tagDefinitions.concat(getInjectables(requireFolder(__dirname, './tag-defs'))); }) + .config(function(splitDescription, EXPORT_DOC_TYPES) { + // Only split the description on the API docs + splitDescription.docTypes = EXPORT_DOC_TYPES; + }) .config(function(computePathsProcessor, EXPORT_DOC_TYPES, generateApiListDoc) { diff --git a/aio/tools/transforms/angular-api-package/processors/splitDescription.js b/aio/tools/transforms/angular-api-package/processors/splitDescription.js new file mode 100644 index 0000000000..d2f86ac794 --- /dev/null +++ b/aio/tools/transforms/angular-api-package/processors/splitDescription.js @@ -0,0 +1,28 @@ +/** + * Split the descripton (of selected docs) into: + * * `shortDescription`: the first paragraph + * * `description`: the rest of the paragraphs + */ +module.exports = function splitDescription() { + return { + $runAfter: ['tags-extracted', 'migrateLegacyJSDocTags'], + $runBefore: ['processing-docs'], + docTypes: [], + $process(docs) { + docs.forEach(doc => { + if (this.docTypes.indexOf(doc.docType) !== -1 && doc.description !== undefined) { + const description = doc.description.trim(); + const endOfParagraph = description.search(/\n\s*\n/); + if (endOfParagraph === -1) { + doc.shortDescription = description; + doc.description = ''; + } else { + doc.shortDescription = description.substr(0, endOfParagraph).trim(); + doc.description = description.substr(endOfParagraph).trim(); + } + } + }); + } + }; +}; + diff --git a/aio/tools/transforms/angular-api-package/processors/splitDescription.spec.js b/aio/tools/transforms/angular-api-package/processors/splitDescription.spec.js new file mode 100644 index 0000000000..90b3b6b92b --- /dev/null +++ b/aio/tools/transforms/angular-api-package/processors/splitDescription.spec.js @@ -0,0 +1,71 @@ +const testPackage = require('../../helpers/test-package'); +const processorFactory = require('./splitDescription'); +const Dgeni = require('dgeni'); + +describe('splitDescription processor', () => { + + it('should be available on the injector', () => { + const dgeni = new Dgeni([testPackage('angular-api-package')]); + const injector = dgeni.configureInjector(); + const processor = injector.get('splitDescription'); + expect(processor.$process).toBeDefined(); + }); + + it('should run before the correct processor', () => { + const processor = processorFactory(); + expect(processor.$runBefore).toEqual(['processing-docs']); + }); + + it('should run after the correct processor', () => { + const processor = processorFactory(); + expect(processor.$runAfter).toEqual(['tags-extracted', 'migrateLegacyJSDocTags']); + }); + + it('should split the `description` property into the first paragraph and other paragraphs', () => { + const processor = processorFactory(); + processor.docTypes = ['test']; + const docs = [ + { docType: 'test' }, + { docType: 'test', description: '' }, + { docType: 'test', description: 'abc' }, + { docType: 'test', description: 'abc\n' }, + { docType: 'test', description: 'abc\n\n' }, + { docType: 'test', description: 'abc\ncde' }, + { docType: 'test', description: 'abc\ncde\n' }, + { docType: 'test', description: 'abc\n\ncde' }, + { docType: 'test', description: 'abc\n \ncde' }, + { docType: 'test', description: 'abc\n\n\ncde' }, + { docType: 'test', description: 'abc\n\ncde\nfgh' }, + { docType: 'test', description: 'abc\n\ncde\n\nfgh' }, + ]; + processor.$process(docs); + expect(docs).toEqual([ + { docType: 'test' }, + { docType: 'test', shortDescription: '', description: '' }, + { docType: 'test', shortDescription: 'abc', description: '' }, + { docType: 'test', shortDescription: 'abc', description: '' }, + { docType: 'test', shortDescription: 'abc', description: '' }, + { docType: 'test', shortDescription: 'abc\ncde', description: '' }, + { docType: 'test', shortDescription: 'abc\ncde', description: '' }, + { docType: 'test', shortDescription: 'abc', description: 'cde' }, + { docType: 'test', shortDescription: 'abc', description: 'cde' }, + { docType: 'test', shortDescription: 'abc', description: 'cde' }, + { docType: 'test', shortDescription: 'abc', description: 'cde\nfgh' }, + { docType: 'test', shortDescription: 'abc', description: 'cde\n\nfgh' }, + ]); + }); + + it('should ignore docs that do not match the specified doc types', () => { + const processor = processorFactory(); + processor.docTypes = ['test']; + const docs = [ + { docType: 'test', description: 'abc\n\ncde' }, + { docType: 'other', description: 'abc\n\ncde' } + ]; + processor.$process(docs); + expect(docs).toEqual([ + { docType: 'test', shortDescription: 'abc', description: 'cde' }, + { docType: 'other', description: 'abc\n\ncde' } + ]); + }); +}); \ No newline at end of file diff --git a/aio/tools/transforms/templates/api/class.template.html b/aio/tools/transforms/templates/api/class.template.html index 8367ca78fd..60445b3372 100644 --- a/aio/tools/transforms/templates/api/class.template.html +++ b/aio/tools/transforms/templates/api/class.template.html @@ -4,7 +4,7 @@ {% extends 'base.template.html' -%} {% block body %} -

{$ doc.whatItDoes | marked $}

+

{$ doc.shortDescription | marked $}

{% include "includes/security-notes.html" %} {% include "includes/deprecation.html" %} {% block overview %} @@ -25,5 +25,5 @@ {% block annotations %}{% include "includes/annotations.html" %}{% endblock %} {% endblock %} - {% include "includes/how-to-use.html" %} + {% include "includes/usageNotes.html" %} {% endblock %} diff --git a/aio/tools/transforms/templates/api/export-base.template.html b/aio/tools/transforms/templates/api/export-base.template.html index f1d3c5be92..b2e24aa799 100644 --- a/aio/tools/transforms/templates/api/export-base.template.html +++ b/aio/tools/transforms/templates/api/export-base.template.html @@ -1,10 +1,10 @@ {% extends 'base.template.html' -%} {% block body %} - {% include "includes/what-it-does.html" %} +

{$ doc.shortDescription | marked $}

{% include "includes/security-notes.html" %} {% include "includes/deprecation.html" %} {% block overview %}{% endblock %} - {% include "includes/how-to-use.html" %} + {% include "includes/usageNotes.html" %} {% block details %}{% endblock %} {% endblock %} diff --git a/aio/tools/transforms/templates/api/includes/class-overview.html b/aio/tools/transforms/templates/api/includes/class-overview.html index fdb6d8393d..1bd48c0dca 100644 --- a/aio/tools/transforms/templates/api/includes/class-overview.html +++ b/aio/tools/transforms/templates/api/includes/class-overview.html @@ -1,7 +1,6 @@ {% import "lib/memberHelpers.html" as memberHelper -%}
-

Overview

{$ doc.docType $} {$ doc.name $}{$ doc.typeParams | escape $}{$ memberHelper.renderHeritage(doc) $} {{$ memberHelper.renderMembers(doc) $} } diff --git a/aio/tools/transforms/templates/api/includes/how-to-use.html b/aio/tools/transforms/templates/api/includes/how-to-use.html deleted file mode 100644 index 13156b0b7a..0000000000 --- a/aio/tools/transforms/templates/api/includes/how-to-use.html +++ /dev/null @@ -1,6 +0,0 @@ -{% if doc.howToUse %} -
-

How To Use

- {$ doc.howToUse | marked $} -
-{% endif %} diff --git a/aio/tools/transforms/templates/api/includes/usageNotes.html b/aio/tools/transforms/templates/api/includes/usageNotes.html new file mode 100644 index 0000000000..88b2d89853 --- /dev/null +++ b/aio/tools/transforms/templates/api/includes/usageNotes.html @@ -0,0 +1,6 @@ +{% if doc.usageNotes %} +
+

Usage Notes

+ {$ doc.usageNotes | marked $} +
+{% endif %} diff --git a/aio/tools/transforms/templates/api/includes/what-it-does.html b/aio/tools/transforms/templates/api/includes/what-it-does.html deleted file mode 100644 index b492b932a7..0000000000 --- a/aio/tools/transforms/templates/api/includes/what-it-does.html +++ /dev/null @@ -1,5 +0,0 @@ -{%- if doc.whatItDoes %} -
- {$ doc.whatItDoes | marked $} -
-{% endif %} \ No newline at end of file From 11f30fc351286e6cfb1c43810217d12de5beb58d Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 23 Feb 2018 14:16:04 +0000 Subject: [PATCH 012/604] style(aio): add newline between test blocks (#22401) PR Close #22401 --- aio/tools/transforms/authors-package/index.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/aio/tools/transforms/authors-package/index.spec.js b/aio/tools/transforms/authors-package/index.spec.js index 091124515a..fea8bfe4de 100644 --- a/aio/tools/transforms/authors-package/index.spec.js +++ b/aio/tools/transforms/authors-package/index.spec.js @@ -12,6 +12,7 @@ describe('authors-package (integration tests)', () => { originalJasmineTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; }); + afterAll(() => jasmine.DEFAULT_TIMEOUT_INTERVAL = originalJasmineTimeout); beforeEach(() => { From 7d65356ae30394274d00ed5e53095c29318bb5cd Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Mon, 26 Feb 2018 21:58:34 +0000 Subject: [PATCH 013/604] build(aio): add `@usageNotes` tag def for API docs (#22401) PR Close #22401 --- .../transforms/angular-api-package/tag-defs/usageNotes.js | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 aio/tools/transforms/angular-api-package/tag-defs/usageNotes.js diff --git a/aio/tools/transforms/angular-api-package/tag-defs/usageNotes.js b/aio/tools/transforms/angular-api-package/tag-defs/usageNotes.js new file mode 100644 index 0000000000..2fe75c0977 --- /dev/null +++ b/aio/tools/transforms/angular-api-package/tag-defs/usageNotes.js @@ -0,0 +1,5 @@ +module.exports = function() { + return { + name: 'usageNotes' + }; +}; From f8749bfb702615bbc2d63c8176a1a8b94a4536a0 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Fri, 23 Feb 2018 09:04:55 -0800 Subject: [PATCH 014/604] fix(core): export inject() from @angular/core (#22389) inject() supports the ngInjectableDef-based configuration of the injector (otherwise known as tree-shakeable services). It was missing from the exported API of @angular/core, this PR adds it. The test added here is correct in theory, but may pass accidentally due to the decorator side-effect replacing the inject() call at runtime. An upcoming compiler PR will strip reified decorators from the output entirely. Fixes #22388 PR Close #22389 --- .../bazel/injectable_def/app/src/dep.ts | 41 +++++++++++++++++++ .../bazel/injectable_def/app/src/token.ts | 18 +++++--- .../bazel/injectable_def/app/test/app_spec.ts | 11 +++++ packages/core/src/core_private_export.ts | 1 + packages/core/src/di.ts | 2 +- packages/core/src/di/injector.ts | 14 +++++++ packages/examples/core/di/ts/injector_spec.ts | 40 +++++++++++++++++- tools/public_api_guard/core/core.d.ts | 3 ++ 8 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/dep.ts diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/dep.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/dep.ts new file mode 100644 index 0000000000..a02cbe6e2e --- /dev/null +++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/dep.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Component, Injectable, NgModule} from '@angular/core'; +import {BrowserModule} from '@angular/platform-browser'; +import {ServerModule} from '@angular/platform-server'; + +@Injectable() +export class NormalService { +} + +@Component({ + selector: 'dep-app', + template: '{{found}}', +}) +export class AppComponent { + found: boolean; + constructor(service: ShakeableService) { this.found = !!service.normal; } +} + +@NgModule({ + imports: [ + BrowserModule.withServerTransition({appId: 'id-app'}), + ServerModule, + ], + declarations: [AppComponent], + bootstrap: [AppComponent], + providers: [NormalService], +}) +export class DepAppModule { +} + +@Injectable({scope: DepAppModule}) +export class ShakeableService { + constructor(readonly normal: NormalService) {} +} \ No newline at end of file diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/token.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/token.ts index e557195ded..b458c405c6 100644 --- a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/token.ts +++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/token.ts @@ -6,11 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, Inject, InjectionToken, NgModule, forwardRef} from '@angular/core'; +import {Component, Inject, Injectable, InjectionToken, NgModule, forwardRef, inject} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {ServerModule} from '@angular/platform-server'; -export interface IService { readonly data: string; } +export interface IService { readonly dep: {readonly data: string;}; } @NgModule({}) export class TokenModule { @@ -18,7 +18,7 @@ export class TokenModule { export const TOKEN = new InjectionToken('test', { scope: TokenModule, - factory: () => new Service(), + factory: () => new Service(inject(Dep)), }); @@ -28,7 +28,7 @@ export const TOKEN = new InjectionToken('test', { }) export class AppComponent { data: string; - constructor(@Inject(TOKEN) service: IService) { this.data = service.data; } + constructor(@Inject(TOKEN) service: IService) { this.data = service.dep.data; } } @NgModule({ @@ -37,10 +37,18 @@ export class AppComponent { ServerModule, TokenModule, ], + providers: [forwardRef(() => Dep)], declarations: [AppComponent], bootstrap: [AppComponent], }) export class TokenAppModule { } -export class Service { readonly data = 'fromToken'; } \ No newline at end of file +@Injectable() +export class Dep { + readonly data = 'fromToken'; +} + +export class Service { + constructor(readonly dep: Dep) {} +} diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/app_spec.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/app_spec.ts index 907941877c..74da00c714 100644 --- a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/app_spec.ts +++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/app_spec.ts @@ -9,6 +9,7 @@ import {enableProdMode} from '@angular/core'; import {renderModuleFactory} from '@angular/platform-server'; import {BasicAppModuleNgFactory} from 'app_built/src/basic.ngfactory'; +import {DepAppModuleNgFactory} from 'app_built/src/dep.ngfactory'; import {HierarchyAppModuleNgFactory} from 'app_built/src/hierarchy.ngfactory'; import {RootAppModuleNgFactory} from 'app_built/src/root.ngfactory'; import {SelfAppModuleNgFactory} from 'app_built/src/self.ngfactory'; @@ -66,4 +67,14 @@ describe('ngInjectableDef Bazel Integration', () => { done(); }); }); + + it('can inject dependencies', done => { + renderModuleFactory(DepAppModuleNgFactory, { + document: '', + url: '/', + }).then(html => { + expect(html).toMatch(/>true<\//); + done(); + }); + }); }); diff --git a/packages/core/src/core_private_export.ts b/packages/core/src/core_private_export.ts index bf0a7dbe54..26e714858f 100644 --- a/packages/core/src/core_private_export.ts +++ b/packages/core/src/core_private_export.ts @@ -12,6 +12,7 @@ export {devModeEqual as ɵdevModeEqual} from './change_detection/change_detectio export {isListLikeIterable as ɵisListLikeIterable} from './change_detection/change_detection_util'; export {ChangeDetectorStatus as ɵChangeDetectorStatus, isDefaultChangeDetectionStrategy as ɵisDefaultChangeDetectionStrategy} from './change_detection/constants'; export {Console as ɵConsole} from './console'; +export {setCurrentInjector as ɵsetCurrentInjector} from './di/injector'; export {ComponentFactory as ɵComponentFactory} from './linker/component_factory'; export {CodegenComponentFactoryResolver as ɵCodegenComponentFactoryResolver} from './linker/component_factory_resolver'; export {ReflectionCapabilities as ɵReflectionCapabilities} from './reflection/reflection_capabilities'; diff --git a/packages/core/src/di.ts b/packages/core/src/di.ts index 79e4993603..756904237a 100644 --- a/packages/core/src/di.ts +++ b/packages/core/src/di.ts @@ -17,7 +17,7 @@ export {defineInjectable, Injectable, InjectableDecorator, InjectableProvider, I export {forwardRef, resolveForwardRef, ForwardRefFn} from './di/forward_ref'; -export {InjectFlags, Injector} from './di/injector'; +export {inject, InjectFlags, Injector} from './di/injector'; export {ReflectiveInjector} from './di/reflective_injector'; export {StaticProvider, ValueProvider, ExistingProvider, FactoryProvider, Provider, TypeProvider, ClassProvider} from './di/provider'; export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider'; diff --git a/packages/core/src/di/injector.ts b/packages/core/src/di/injector.ts index 0758464957..e428aa9a96 100644 --- a/packages/core/src/di/injector.ts +++ b/packages/core/src/di/injector.ts @@ -410,6 +410,20 @@ export function setCurrentInjector(injector: Injector | null): Injector|null { return former; } +/** + * Injects a token from the currently active injector. + * + * This function must be used in the context of a factory function such as one defined for an + * `InjectionToken`, and will throw an error if not called from such a context. For example: + * + * {@example core/di/ts/injector_spec.ts region='ShakeableInjectionToken'} + * + * Within such a factory function `inject` is utilized to request injection of a dependency, instead + * of providing an additional array of dependencies as was common to do with `useFactory` providers. + * `inject` is faster and more type-safe. + * + * @experimental + */ export function inject( token: Type| InjectionToken, notFoundValue?: undefined, flags?: InjectFlags): T; export function inject( diff --git a/packages/examples/core/di/ts/injector_spec.ts b/packages/examples/core/di/ts/injector_spec.ts index ab57f44d0e..729f5123e0 100644 --- a/packages/examples/core/di/ts/injector_spec.ts +++ b/packages/examples/core/di/ts/injector_spec.ts @@ -6,7 +6,25 @@ * found in the LICENSE file at https://angular.io/license */ -import {InjectionToken, Injector, ReflectiveInjector} from '@angular/core'; +import {APP_ROOT_SCOPE, InjectFlags, InjectionToken, Injector, ReflectiveInjector, Type, inject, ɵsetCurrentInjector as setCurrentInjector} from '@angular/core'; + +class MockRootScopeInjector implements Injector { + constructor(readonly parent: Injector) {} + + get( + token: Type|InjectionToken, defaultValue?: any, + flags: InjectFlags = InjectFlags.Default): T { + if ((token as any).ngInjectableDef && (token as any).ngInjectableDef.scope === APP_ROOT_SCOPE) { + const old = setCurrentInjector(this); + try { + return (token as any).ngInjectableDef.factory(); + } finally { + setCurrentInjector(old); + } + } + return this.parent.get(token, defaultValue, flags); + } +} { describe('injector metadata examples', () => { @@ -37,5 +55,25 @@ import {InjectionToken, Injector, ReflectiveInjector} from '@angular/core'; expect(url).toBe('http://localhost'); // #enddocregion }); + + it('injects a tree-shaekable InjectionToken', () => { + class MyDep {} + const injector = new MockRootScopeInjector(ReflectiveInjector.resolveAndCreate([MyDep])); + + // #docregion ShakeableInjectionToken + class MyService { + constructor(readonly myDep: MyDep) {} + } + + const MY_SERVICE_TOKEN = new InjectionToken('Manually constructed MyService', { + scope: APP_ROOT_SCOPE, + factory: () => new MyService(inject(MyDep)), + }); + + const instance = injector.get(MY_SERVICE_TOKEN); + expect(instance instanceof MyService).toBeTruthy(); + expect(instance.myDep instanceof MyDep).toBeTruthy(); + // #enddocregion + }); }); } diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index 8f82abbeea..835512649c 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -447,6 +447,9 @@ export interface HostDecorator { /** @stable */ export declare const HostListener: HostListenerDecorator; +/** @experimental */ +export declare function inject(token: Type | InjectionToken, notFoundValue?: undefined, flags?: InjectFlags): T; + /** @stable */ export declare const Inject: InjectDecorator; From c82cef8bc6181fc09eb414ceeb4a422ab69f175d Mon Sep 17 00:00:00 2001 From: Kapunahele Wong Date: Mon, 12 Feb 2018 16:34:38 -0500 Subject: [PATCH 015/604] docs: fix dynamic component loader example (#22181) closes #21903 PR Close #22181 --- .../src/app/ad-banner.component.ts | 15 +++++++-------- .../src/app/app.component.ts | 2 +- aio/content/guide/dynamic-component-loader.md | 4 ++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/aio/content/examples/dynamic-component-loader/src/app/ad-banner.component.ts b/aio/content/examples/dynamic-component-loader/src/app/ad-banner.component.ts index 1e59a89204..63cb0ba0ff 100644 --- a/aio/content/examples/dynamic-component-loader/src/app/ad-banner.component.ts +++ b/aio/content/examples/dynamic-component-loader/src/app/ad-banner.component.ts @@ -1,12 +1,12 @@ // #docregion -import { Component, Input, AfterViewInit, ViewChild, ComponentFactoryResolver, OnDestroy } from '@angular/core'; +import { Component, Input, OnInit, ViewChild, ComponentFactoryResolver, OnDestroy } from '@angular/core'; import { AdDirective } from './ad.directive'; import { AdItem } from './ad-item'; import { AdComponent } from './ad.component'; @Component({ - selector: 'app-add-banner', + selector: 'app-ad-banner', // #docregion ad-host template: `
@@ -17,16 +17,15 @@ import { AdComponent } from './ad.component'; // #enddocregion ad-host }) // #docregion class -export class AdBannerComponent implements AfterViewInit, OnDestroy { +export class AdBannerComponent implements OnInit, OnDestroy { @Input() ads: AdItem[]; - currentAddIndex: number = -1; + currentAdIndex: number = -1; @ViewChild(AdDirective) adHost: AdDirective; - subscription: any; interval: any; constructor(private componentFactoryResolver: ComponentFactoryResolver) { } - ngAfterViewInit() { + ngOnInit() { this.loadComponent(); this.getAds(); } @@ -36,8 +35,8 @@ export class AdBannerComponent implements AfterViewInit, OnDestroy { } loadComponent() { - this.currentAddIndex = (this.currentAddIndex + 1) % this.ads.length; - let adItem = this.ads[this.currentAddIndex]; + this.currentAdIndex = (this.currentAdIndex + 1) % this.ads.length; + let adItem = this.ads[this.currentAdIndex]; let componentFactory = this.componentFactoryResolver.resolveComponentFactory(adItem.component); diff --git a/aio/content/examples/dynamic-component-loader/src/app/app.component.ts b/aio/content/examples/dynamic-component-loader/src/app/app.component.ts index 40fe96a276..1a5fa94dbc 100644 --- a/aio/content/examples/dynamic-component-loader/src/app/app.component.ts +++ b/aio/content/examples/dynamic-component-loader/src/app/app.component.ts @@ -8,7 +8,7 @@ import { AdItem } from './ad-item'; selector: 'app-root', template: `
- +
` }) diff --git a/aio/content/guide/dynamic-component-loader.md b/aio/content/guide/dynamic-component-loader.md index ecd6fe0a24..c2e3d77748 100644 --- a/aio/content/guide/dynamic-component-loader.md +++ b/aio/content/guide/dynamic-component-loader.md @@ -109,9 +109,9 @@ Take it step by step. First, it picks an ad. The `loadComponent()` method chooses an ad using some math. -First, it sets the `currentAddIndex` by taking whatever it +First, it sets the `currentAdIndex` by taking whatever it currently is plus one, dividing that by the length of the `AdItem` array, and -using the _remainder_ as the new `currentAddIndex` value. Then, it uses that +using the _remainder_ as the new `currentAdIndex` value. Then, it uses that value to select an `adItem` from the array. From b6c941053e4f51bb793754790f18d0b5b64c3029 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Wed, 28 Feb 2018 06:51:40 -0800 Subject: [PATCH 016/604] feat(bazel): ng_package adds package.json props (#22499) We now add the 'main', 'module', 'es2015', and 'typings' properties, pointing to where the packaging tool lays them out. Fixes #22416 PR Close #22499 --- packages/bazel/src/ng_package/packager.ts | 41 +++++++++++-- packages/bazel/test/ng_package/BUILD.bazel | 13 +++++ .../bazel/test/ng_package/example/BUILD.bazel | 25 ++++++++ .../bazel/test/ng_package/example/index.ts | 1 + .../bazel/test/ng_package/example/mymodule.ts | 5 ++ .../test/ng_package/example/package.json | 3 + .../ng_package/example/secondary/BUILD.bazel | 11 ++++ .../ng_package/example/secondary/index.ts | 1 + .../ng_package/example/secondary/package.json | 3 + .../example/secondary/secondarymodule.ts | 5 ++ .../test/ng_package/example/some-file.txt | 1 + .../test/ng_package/example_package.spec.ts | 58 +++++++++++++++++++ packages/core/BUILD.bazel | 5 +- 13 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 packages/bazel/test/ng_package/example/BUILD.bazel create mode 100644 packages/bazel/test/ng_package/example/index.ts create mode 100644 packages/bazel/test/ng_package/example/mymodule.ts create mode 100644 packages/bazel/test/ng_package/example/package.json create mode 100644 packages/bazel/test/ng_package/example/secondary/BUILD.bazel create mode 100644 packages/bazel/test/ng_package/example/secondary/index.ts create mode 100644 packages/bazel/test/ng_package/example/secondary/package.json create mode 100644 packages/bazel/test/ng_package/example/secondary/secondarymodule.ts create mode 100644 packages/bazel/test/ng_package/example/some-file.txt create mode 100644 packages/bazel/test/ng_package/example_package.spec.ts diff --git a/packages/bazel/src/ng_package/packager.ts b/packages/bazel/src/ng_package/packager.ts index 3ca06ee1f0..2b0ec0cd8a 100644 --- a/packages/bazel/src/ng_package/packager.ts +++ b/packages/bazel/src/ng_package/packager.ts @@ -29,12 +29,39 @@ function main(args: string[]): number { let primaryEntryPoint: string|null = null; const secondaryEntryPoints = new Set(); - function replaceVersionPlaceholders(filePath: string) { + function replaceVersionPlaceholders(filePath: string, content: string) { if (stampData) { const version = shx.grep('BUILD_SCM_VERSION', stampData).split(' ')[1].trim(); - return shx.sed(/0.0.0-PLACEHOLDER/, version, filePath); + return content.replace(/0.0.0-PLACEHOLDER/g, version); } - return shx.cat(filePath); + return content; + } + + /** + * Inserts properties into the package.json file(s) in the package so that + * they point to all the right generated artifacts. + * + * @param filePath file being copied + * @param content current file content + */ + function amendPackageJson(filePath: string, content: string) { + if (path.basename(filePath) === 'package.json') { + const parsedPackage = JSON.parse(content); + let nameParts = parsedPackage['name'].split('/'); + // for scoped packages, we don't care about the scope segment of the path + if (nameParts[0].startsWith('@')) nameParts = nameParts.splice(1); + let rel = Array(nameParts.length - 1).fill('..').join('/'); + if (!rel) { + rel = '.'; + } + const indexFile = nameParts[nameParts.length - 1]; + parsedPackage['main'] = `${rel}/bundles/${nameParts.join('-')}.umd.js`; + parsedPackage['module'] = `${rel}/esm5/${indexFile}.js`; + parsedPackage['es2015'] = `${rel}/esm2015/${indexFile}.js`; + parsedPackage['typings'] = `./${indexFile}.d.ts`; + return JSON.stringify(parsedPackage, null, 2); + } + return content; } function writeFesm(file: string, baseDir: string) { @@ -103,11 +130,15 @@ function main(args: string[]): number { }); for (const src of srcs) { - replaceVersionPlaceholders(src).to(path.join(out, path.relative(srcDir, src))); + let content = fs.readFileSync(src, {encoding: 'utf-8'}); + content = replaceVersionPlaceholders(src, content); + content = amendPackageJson(src, content); + fs.writeFileSync(path.join(out, path.relative(srcDir, src)), content); } allsrcs.filter(filter('.bundle_index.metadata.json')).forEach((f: string) => { - replaceVersionPlaceholders(f).to(moveBundleIndex(f)); + fs.writeFileSync( + moveBundleIndex(f), replaceVersionPlaceholders(f, fs.readFileSync(f, {encoding: 'utf-8'}))); }); const licenseBanner = licenseFile ? fs.readFileSync(licenseFile, {encoding: 'utf-8'}) : ''; diff --git a/packages/bazel/test/ng_package/BUILD.bazel b/packages/bazel/test/ng_package/BUILD.bazel index 2e6aa5224a..5c1a4b3127 100644 --- a/packages/bazel/test/ng_package/BUILD.bazel +++ b/packages/bazel/test/ng_package/BUILD.bazel @@ -34,3 +34,16 @@ jasmine_node_test( srcs = [":common_spec_lib"], data = ["//packages/common:npm_package"], ) + +ts_library( + name = "example_spec_lib", + testonly = True, + srcs = ["example_package.spec.ts"], + deps = ["//packages:types"], +) + +jasmine_node_test( + name = "example_package", + srcs = [":example_spec_lib"], + data = ["//packages/bazel/test/ng_package/example:npm_package"], +) diff --git a/packages/bazel/test/ng_package/example/BUILD.bazel b/packages/bazel/test/ng_package/example/BUILD.bazel new file mode 100644 index 0000000000..0123582051 --- /dev/null +++ b/packages/bazel/test/ng_package/example/BUILD.bazel @@ -0,0 +1,25 @@ +package(default_visibility = ["//packages/bazel/test:__subpackages__"]) + +load("//packages/bazel:index.bzl", "ng_module", "ng_package") + +ng_module( + name = "example", + srcs = glob(["*.ts"]), + deps = ["//packages/bazel/test/ng_package/example/secondary"], +) + +ng_package( + name = "npm_package", + srcs = [ + "package.json", + "some-file.txt", + "//packages/bazel/test/ng_package/example/secondary:package.json", + ], + entry_point = "packages/bazel/test/ng_package/example/index.js", + secondary_entry_points = ["secondary"], + deps = [ + ":example", + # FIXME(#22419) + "//packages/bazel/test/ng_package/example/secondary", + ], +) diff --git a/packages/bazel/test/ng_package/example/index.ts b/packages/bazel/test/ng_package/example/index.ts new file mode 100644 index 0000000000..397c6d1b9b --- /dev/null +++ b/packages/bazel/test/ng_package/example/index.ts @@ -0,0 +1 @@ +export * from './mymodule'; \ No newline at end of file diff --git a/packages/bazel/test/ng_package/example/mymodule.ts b/packages/bazel/test/ng_package/example/mymodule.ts new file mode 100644 index 0000000000..5934a259f4 --- /dev/null +++ b/packages/bazel/test/ng_package/example/mymodule.ts @@ -0,0 +1,5 @@ +import {NgModule} from '@angular/core'; + +@NgModule({}) +export class MyModule { +} \ No newline at end of file diff --git a/packages/bazel/test/ng_package/example/package.json b/packages/bazel/test/ng_package/example/package.json new file mode 100644 index 0000000000..8389bd69d6 --- /dev/null +++ b/packages/bazel/test/ng_package/example/package.json @@ -0,0 +1,3 @@ +{ + "name": "example" +} \ No newline at end of file diff --git a/packages/bazel/test/ng_package/example/secondary/BUILD.bazel b/packages/bazel/test/ng_package/example/secondary/BUILD.bazel new file mode 100644 index 0000000000..def3a382ed --- /dev/null +++ b/packages/bazel/test/ng_package/example/secondary/BUILD.bazel @@ -0,0 +1,11 @@ +package(default_visibility = ["//packages/bazel/test:__subpackages__"]) + +load("//packages/bazel:index.bzl", "ng_module") + +exports_files(["package.json"]) + +ng_module( + name = "secondary", + srcs = glob(["*.ts"]), + deps = ["//packages/core"], +) diff --git a/packages/bazel/test/ng_package/example/secondary/index.ts b/packages/bazel/test/ng_package/example/secondary/index.ts new file mode 100644 index 0000000000..7f7c05e9f8 --- /dev/null +++ b/packages/bazel/test/ng_package/example/secondary/index.ts @@ -0,0 +1 @@ +export * from './secondarymodule'; diff --git a/packages/bazel/test/ng_package/example/secondary/package.json b/packages/bazel/test/ng_package/example/secondary/package.json new file mode 100644 index 0000000000..7aed970b84 --- /dev/null +++ b/packages/bazel/test/ng_package/example/secondary/package.json @@ -0,0 +1,3 @@ +{ + "name": "example/secondary" +} diff --git a/packages/bazel/test/ng_package/example/secondary/secondarymodule.ts b/packages/bazel/test/ng_package/example/secondary/secondarymodule.ts new file mode 100644 index 0000000000..66e03758cc --- /dev/null +++ b/packages/bazel/test/ng_package/example/secondary/secondarymodule.ts @@ -0,0 +1,5 @@ +import {NgModule} from '@angular/core'; + +@NgModule({}) +export class SecondaryModule { +} diff --git a/packages/bazel/test/ng_package/example/some-file.txt b/packages/bazel/test/ng_package/example/some-file.txt new file mode 100644 index 0000000000..4809e652ee --- /dev/null +++ b/packages/bazel/test/ng_package/example/some-file.txt @@ -0,0 +1 @@ +This file is just copied into the package. diff --git a/packages/bazel/test/ng_package/example_package.spec.ts b/packages/bazel/test/ng_package/example_package.spec.ts new file mode 100644 index 0000000000..c0233ce472 --- /dev/null +++ b/packages/bazel/test/ng_package/example_package.spec.ts @@ -0,0 +1,58 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as shx from 'shelljs'; + +const UTF8 = { + encoding: 'utf-8' +}; + +shx.cd(path.join( + process.env['TEST_SRCDIR'], 'angular', 'packages', 'bazel', 'test', 'ng_package', 'example', + 'npm_package')); + +describe('ng_package', () => { + it('should have right bundle files', () => { + expect(shx.ls('-R', 'bundles').stdout.split('\n').filter(n => !!n).sort()).toEqual([ + 'example-secondary.umd.js', + 'example-secondary.umd.js.map', + 'example-secondary.umd.min.js', + 'example-secondary.umd.min.js.map', + 'example.umd.js', + 'example.umd.js.map', + 'example.umd.min.js', + 'example.umd.min.js.map', + ]); + }); + it('should have right fesm files', () => { + const expected = [ + 'example.js', + 'example.js.map', + 'secondary.js', + 'secondary.js.map', + ]; + expect(shx.ls('-R', 'esm5').stdout.split('\n').filter(n => !!n).sort()).toEqual(expected); + expect(shx.ls('-R', 'esm2015').stdout.split('\n').filter(n => !!n).sort()).toEqual(expected); + }); + it('should have main entry point package.json properties set', () => { + const packageJson = JSON.parse(fs.readFileSync('package.json', UTF8)); + expect(packageJson['main']).toBe('./bundles/example.umd.js'); + expect(packageJson['module']).toBe('./esm5/example.js'); + expect(packageJson['es2015']).toBe('./esm2015/example.js'); + expect(packageJson['typings']).toBe('./example.d.ts'); + }); + it('should have secondary entry point package.json properties set', () => { + const packageJson = JSON.parse(fs.readFileSync(path.join('secondary', 'package.json'), UTF8)); + expect(packageJson['main']).toBe('../bundles/example-secondary.umd.js'); + expect(packageJson['module']).toBe('../esm5/secondary.js'); + expect(packageJson['es2015']).toBe('../esm2015/secondary.js'); + expect(packageJson['typings']).toBe('./secondary.d.ts'); + }); +}); diff --git a/packages/core/BUILD.bazel b/packages/core/BUILD.bazel index 2f0f1d111f..49d0e0710b 100644 --- a/packages/core/BUILD.bazel +++ b/packages/core/BUILD.bazel @@ -19,7 +19,10 @@ ng_module( ng_package( name = "npm_package", - srcs = glob(["**/*.externs.js"] + ["**/package.json"]) + ["//packages/core/testing:npm_package_assets"], + srcs = glob([ + "**/*.externs.js", + "**/package.json", + ]) + ["//packages/core/testing:npm_package_assets"], entry_point = "packages/core/index.js", secondary_entry_points = ["testing"], deps = [ From aabe16c08c9886ff2ee0bed212034bf29336ecce Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Wed, 28 Feb 2018 09:12:39 -0800 Subject: [PATCH 017/604] fix(bazel): ng_package includes transitive .d.ts and flatModuleMetadata (#22499) Fixes #22419 PR Close #22499 --- packages/bazel/src/ng_module.bzl | 7 +++- packages/bazel/src/ng_package/ng_package.bzl | 4 +- packages/bazel/src/ng_package/packager.ts | 37 ++++++++++--------- .../bazel/test/ng_package/example/BUILD.bazel | 6 +-- .../bazel/test/ng_package/example/index.ts | 8 ++++ .../bazel/test/ng_package/example/mymodule.ts | 9 +++++ .../ng_package/example/secondary/index.ts | 8 ++++ .../example/secondary/secondarymodule.ts | 10 +++++ .../test/ng_package/example_package.spec.ts | 10 +++++ 9 files changed, 73 insertions(+), 26 deletions(-) diff --git a/packages/bazel/src/ng_module.bzl b/packages/bazel/src/ng_module.bzl index bcc9533c87..99c5b79dae 100644 --- a/packages/bazel/src/ng_module.bzl +++ b/packages/bazel/src/ng_module.bzl @@ -312,8 +312,11 @@ def ng_module_impl(ctx, ts_compile_actions): # and only under Bazel if hasattr(ctx.executable, "_index_bundler"): bundle_index_metadata = _write_bundle_index(ctx) - # note, not recursive - providers["angular"]["flat_module_metadata"] = depset(bundle_index_metadata) + providers["angular"]["flat_module_metadata"] = depset(bundle_index_metadata, + transitive = [ + d.angular.flat_module_metadata + for d in ctx.attr.deps + if hasattr(d, "angular")]) return providers diff --git a/packages/bazel/src/ng_package/ng_package.bzl b/packages/bazel/src/ng_package/ng_package.bzl index 9c2d89809b..83f6fff158 100644 --- a/packages/bazel/src/ng_package/ng_package.bzl +++ b/packages/bazel/src/ng_package/ng_package.bzl @@ -172,7 +172,9 @@ def _ng_package_impl(ctx): progress_message = "Angular Packaging: building npm package for %s" % ctx.label.name, mnemonic = "AngularPackage", inputs = esm5_sources.to_list() + - ctx.files.deps + + depset(transitive = [d.typescript.transitive_declarations + for d in ctx.attr.deps + if hasattr(d, "typescript")]).to_list() + ctx.files.srcs + other_inputs, outputs = [npm_package_directory], diff --git a/packages/bazel/src/ng_package/packager.ts b/packages/bazel/src/ng_package/packager.ts index 2b0ec0cd8a..4c5a3c89b6 100644 --- a/packages/bazel/src/ng_package/packager.ts +++ b/packages/bazel/src/ng_package/packager.ts @@ -45,23 +45,20 @@ function main(args: string[]): number { * @param content current file content */ function amendPackageJson(filePath: string, content: string) { - if (path.basename(filePath) === 'package.json') { - const parsedPackage = JSON.parse(content); - let nameParts = parsedPackage['name'].split('/'); - // for scoped packages, we don't care about the scope segment of the path - if (nameParts[0].startsWith('@')) nameParts = nameParts.splice(1); - let rel = Array(nameParts.length - 1).fill('..').join('/'); - if (!rel) { - rel = '.'; - } - const indexFile = nameParts[nameParts.length - 1]; - parsedPackage['main'] = `${rel}/bundles/${nameParts.join('-')}.umd.js`; - parsedPackage['module'] = `${rel}/esm5/${indexFile}.js`; - parsedPackage['es2015'] = `${rel}/esm2015/${indexFile}.js`; - parsedPackage['typings'] = `./${indexFile}.d.ts`; - return JSON.stringify(parsedPackage, null, 2); + const parsedPackage = JSON.parse(content); + let nameParts = parsedPackage['name'].split('/'); + // for scoped packages, we don't care about the scope segment of the path + if (nameParts[0].startsWith('@')) nameParts = nameParts.splice(1); + let rel = Array(nameParts.length - 1).fill('..').join('/'); + if (!rel) { + rel = '.'; } - return content; + const indexFile = nameParts[nameParts.length - 1]; + parsedPackage['main'] = `${rel}/bundles/${nameParts.join('-')}.umd.js`; + parsedPackage['module'] = `${rel}/esm5/${indexFile}.js`; + parsedPackage['es2015'] = `${rel}/esm2015/${indexFile}.js`; + parsedPackage['typings'] = `./${indexFile}.d.ts`; + return JSON.stringify(parsedPackage, null, 2); } function writeFesm(file: string, baseDir: string) { @@ -132,8 +129,12 @@ function main(args: string[]): number { for (const src of srcs) { let content = fs.readFileSync(src, {encoding: 'utf-8'}); content = replaceVersionPlaceholders(src, content); - content = amendPackageJson(src, content); - fs.writeFileSync(path.join(out, path.relative(srcDir, src)), content); + if (path.basename(src) === 'package.json') { + content = amendPackageJson(src, content); + } + const outputPath = path.join(out, path.relative(srcDir, src)); + shx.mkdir('-p', path.dirname(outputPath)); + fs.writeFileSync(outputPath, content); } allsrcs.filter(filter('.bundle_index.metadata.json')).forEach((f: string) => { diff --git a/packages/bazel/test/ng_package/example/BUILD.bazel b/packages/bazel/test/ng_package/example/BUILD.bazel index 0123582051..73b26c1562 100644 --- a/packages/bazel/test/ng_package/example/BUILD.bazel +++ b/packages/bazel/test/ng_package/example/BUILD.bazel @@ -17,9 +17,5 @@ ng_package( ], entry_point = "packages/bazel/test/ng_package/example/index.js", secondary_entry_points = ["secondary"], - deps = [ - ":example", - # FIXME(#22419) - "//packages/bazel/test/ng_package/example/secondary", - ], + deps = [":example"], ) diff --git a/packages/bazel/test/ng_package/example/index.ts b/packages/bazel/test/ng_package/example/index.ts index 397c6d1b9b..374f4edd11 100644 --- a/packages/bazel/test/ng_package/example/index.ts +++ b/packages/bazel/test/ng_package/example/index.ts @@ -1 +1,9 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + export * from './mymodule'; \ No newline at end of file diff --git a/packages/bazel/test/ng_package/example/mymodule.ts b/packages/bazel/test/ng_package/example/mymodule.ts index 5934a259f4..a34eb53014 100644 --- a/packages/bazel/test/ng_package/example/mymodule.ts +++ b/packages/bazel/test/ng_package/example/mymodule.ts @@ -1,4 +1,13 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + import {NgModule} from '@angular/core'; +import {a} from './secondary/secondarymodule'; @NgModule({}) export class MyModule { diff --git a/packages/bazel/test/ng_package/example/secondary/index.ts b/packages/bazel/test/ng_package/example/secondary/index.ts index 7f7c05e9f8..768e94c8b0 100644 --- a/packages/bazel/test/ng_package/example/secondary/index.ts +++ b/packages/bazel/test/ng_package/example/secondary/index.ts @@ -1 +1,9 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + export * from './secondarymodule'; diff --git a/packages/bazel/test/ng_package/example/secondary/secondarymodule.ts b/packages/bazel/test/ng_package/example/secondary/secondarymodule.ts index 66e03758cc..17e4c1a7ae 100644 --- a/packages/bazel/test/ng_package/example/secondary/secondarymodule.ts +++ b/packages/bazel/test/ng_package/example/secondary/secondarymodule.ts @@ -1,5 +1,15 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + import {NgModule} from '@angular/core'; @NgModule({}) export class SecondaryModule { } + +export const a = 1; diff --git a/packages/bazel/test/ng_package/example_package.spec.ts b/packages/bazel/test/ng_package/example_package.spec.ts index c0233ce472..8692c1c7e9 100644 --- a/packages/bazel/test/ng_package/example_package.spec.ts +++ b/packages/bazel/test/ng_package/example_package.spec.ts @@ -41,6 +41,16 @@ describe('ng_package', () => { expect(shx.ls('-R', 'esm5').stdout.split('\n').filter(n => !!n).sort()).toEqual(expected); expect(shx.ls('-R', 'esm2015').stdout.split('\n').filter(n => !!n).sort()).toEqual(expected); }); + it('should have right secondary sources', () => { + const expected = [ + 'index.d.ts', + 'package.json', + 'secondary.d.ts', + 'secondary.metadata.json', + 'secondarymodule.d.ts', + ]; + expect(shx.ls('-R', 'secondary').stdout.split('\n').filter(n => !!n).sort()).toEqual(expected); + }); it('should have main entry point package.json properties set', () => { const packageJson = JSON.parse(fs.readFileSync('package.json', UTF8)); expect(packageJson['main']).toBe('./bundles/example.umd.js'); From 9eaf1bbe677f8fd3ae5197e8be8c96c00edef546 Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Mon, 26 Feb 2018 16:58:15 -0800 Subject: [PATCH 018/604] feat(ivy): support injecting ChangeDetectorRef (#22469) PR Close #22469 --- .../core/src/core_render3_private_export.ts | 1 + packages/core/src/render3/component.ts | 96 +----- packages/core/src/render3/di.ts | 80 +++-- packages/core/src/render3/index.ts | 2 +- packages/core/src/render3/instructions.ts | 19 +- .../core/src/render3/interfaces/injector.ts | 7 + packages/core/src/render3/view_ref.ts | 84 ++++++ .../compiler_canonical_spec.ts | 49 +++- packages/core/test/render3/di_spec.ts | 276 +++++++++++++++++- 9 files changed, 491 insertions(+), 123 deletions(-) create mode 100644 packages/core/src/render3/view_ref.ts diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index ce3eb7b51c..fedee6c755 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -17,6 +17,7 @@ export { inject as ɵinject, injectTemplateRef as ɵinjectTemplateRef, injectViewContainerRef as ɵinjectViewContainerRef, + injectChangeDetectorRef as ɵinjectChangeDetectorRef, InjectFlags as ɵInjectFlags, PublicFeature as ɵPublicFeature, NgOnChangesFeature as ɵNgOnChangesFeature, diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index 3ba8945603..5c53ef6a34 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -10,16 +10,14 @@ // correctly implementing its interfaces for backwards compatibility. import {Injector} from '../di/injector'; import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory'; -import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref'; import {assertNotNull} from './assert'; -import {CLEAN_PROMISE, NG_HOST_SYMBOL, _getComponentHostLElementNode, createError, createLView, createTView, detectChanges, directiveCreate, enterView, getDirectiveInstance, hostElement, leaveView, locateHostElement, scheduleChangeDetection} from './instructions'; +import {CLEAN_PROMISE, _getComponentHostLElementNode, createLView, createTView, detectChanges, directiveCreate, enterView, getDirectiveInstance, hostElement, initChangeDetectorIfExisting, leaveView, locateHostElement, scheduleChangeDetection} from './instructions'; import {ComponentDef, ComponentType} from './interfaces/definition'; -import {LElementNode} from './interfaces/node'; -import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; +import {RElement, RendererFactory3, domRendererFactory3} from './interfaces/renderer'; import {LViewFlags, RootContext} from './interfaces/view'; -import {notImplemented, stringify} from './util'; - +import {stringify} from './util'; +import {createViewRef} from './view_ref'; /** Options that control how the component should be bootstrapped. */ @@ -69,7 +67,7 @@ export interface CreateComponentOptions { export function createComponentRef( componentType: ComponentType, opts: CreateComponentOptions): viewEngine_ComponentRef { const component = renderComponent(componentType, opts); - const hostView = createViewRef(() => detectChanges(component), component); + const hostView = createViewRef(component); return { location: {nativeElement: getHostElement(component)}, injector: opts.injector || NULL_INJECTOR, @@ -83,84 +81,6 @@ export function createComponentRef( }; } -/** - * Creates an EmbeddedViewRef. - * - * @param detectChanges The detectChanges function for this view - * @param context The context for this view - * @returns The EmbeddedViewRef - */ -function createViewRef(detectChanges: () => void, context: T): EmbeddedViewRef { - return addDestroyable(new EmbeddedViewRef(detectChanges), context); -} - -class EmbeddedViewRef implements viewEngine_EmbeddedViewRef { - // TODO: rootNodes should be replaced when properly implemented - rootNodes = null !; - context: T; - destroyed: boolean; - - constructor(public detectChanges: () => void) {} - - // inherited from core/ChangeDetectorRef - markForCheck() { - if (ngDevMode) { - throw notImplemented(); - } - } - detach() { - if (ngDevMode) { - throw notImplemented(); - } - } - - checkNoChanges() { - if (ngDevMode) { - throw notImplemented(); - } - } - - reattach() { - if (ngDevMode) { - throw notImplemented(); - } - } - - destroy(): void {} - - onDestroy(cb: Function): void {} -} - -/** Interface for destroy logic. Implemented by addDestroyable. */ -interface DestroyRef { - context: T; - /** Whether or not this object has been destroyed */ - destroyed: boolean; - /** Destroy the instance and call all onDestroy callbacks. */ - destroy(): void; - /** Register callbacks that should be called onDestroy */ - onDestroy(cb: Function): void; -} - -/** - * Decorates an object with destroy logic (implementing the DestroyRef interface) - * and returns the enhanced object. - * - * @param obj The object to decorate - * @returns The object with destroy logic - */ -function addDestroyable(obj: any, context: C): T&DestroyRef { - let destroyFn: Function[]|null = null; - obj.destroyed = false; - obj.destroy = function() { - destroyFn && destroyFn.forEach((fn) => fn()); - this.destroyed = true; - }; - obj.onDestroy = (fn: Function) => (destroyFn || (destroyFn = [])).push(fn); - obj.context = context; - return obj; -} - // TODO: A hack to not pull in the NullInjector from @angular/core. export const NULL_INJECTOR: Injector = { @@ -202,10 +122,12 @@ export function renderComponent( null !); try { // Create element node at index 0 in data array - hostElement(hostNode, componentDef); + const elementNode = hostElement(hostNode, componentDef); // Create directive instance with n() and store at index 1 in data array (el is 0) + const instance = componentDef.n(); component = rootContext.component = - getDirectiveInstance(directiveCreate(1, componentDef.n(), componentDef)); + getDirectiveInstance(directiveCreate(1, instance, componentDef)); + initChangeDetectorIfExisting(elementNode.nodeInjector, instance); } finally { leaveView(oldView); } diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 8f33ee2013..27446d1503 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -8,6 +8,7 @@ // We are temporarily importing the existing viewEngine_from core so we can be sure we are // correctly implementing its interfaces for backwards compatibility. +import {ChangeDetectorRef as viewEngine_ChangeDetectorRef} from '../change_detection/change_detector_ref'; import {Injector} from '../di/injector'; import {ComponentFactory as viewEngine_ComponentFactory, ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory'; import {ElementRef as viewEngine_ElementRef} from '../linker/element_ref'; @@ -24,10 +25,10 @@ import {LInjector} from './interfaces/injector'; import {LContainerNode, LElementNode, LNode, LNodeFlags, LViewNode} from './interfaces/node'; import {QueryReadType} from './interfaces/query'; import {Renderer3} from './interfaces/renderer'; -import {LView} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {insertView} from './node_manipulation'; import {notImplemented, stringify} from './util'; +import {EmbeddedViewRef, ViewRef, addDestroyable, createViewRef} from './view_ref'; @@ -124,7 +125,8 @@ export function getOrCreateNodeInjectorForNode(node: LElementNode | LContainerNo injector: null, templateRef: null, viewContainerRef: null, - elementRef: null + elementRef: null, + changeDetectorRef: null }; } @@ -227,6 +229,55 @@ export function injectViewContainerRef(): viewEngine_ViewContainerRef { return getOrCreateContainerRef(getOrCreateNodeInjector()); } +/** Returns a ChangeDetectorRef (a.k.a. a ViewRef) */ +export function injectChangeDetectorRef(): viewEngine_ChangeDetectorRef { + return getOrCreateChangeDetectorRef(getOrCreateNodeInjector(), null); +} + +/** + * Creates a ViewRef and stores it on the injector as ChangeDetectorRef (public alias). + * Or, if it already exists, retrieves the existing instance. + * + * @returns The ChangeDetectorRef to use + */ +export function getOrCreateChangeDetectorRef( + di: LInjector, context: any): viewEngine_ChangeDetectorRef { + if (di.changeDetectorRef) return di.changeDetectorRef; + + const currentNode = di.node; + if (currentNode.data === null) { + // if data is null, this node is a regular element node (not a component) + return di.changeDetectorRef = getOrCreateHostChangeDetector(currentNode.view.node); + } else if ((currentNode.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.Element) { + // if it's an element node with data, it's a component and context will be set later + return di.changeDetectorRef = createViewRef(context); + } + return null !; +} + +/** Gets or creates ChangeDetectorRef for the closest host component */ +function getOrCreateHostChangeDetector(currentNode: LViewNode | LElementNode): + viewEngine_ChangeDetectorRef { + const hostNode = getClosestComponentAncestor(currentNode); + const hostInjector = hostNode.nodeInjector; + const existingRef = hostInjector && hostInjector.changeDetectorRef; + + return existingRef ? existingRef : + createViewRef(hostNode.view.data[hostNode.flags >> LNodeFlags.INDX_SHIFT]); +} + +/** + * If the node is an embedded view, traverses up the view tree to return the closest + * ancestor view that is attached to a component. If it's already a component node, + * returns itself. + */ +function getClosestComponentAncestor(node: LViewNode | LElementNode): LElementNode { + while ((node.flags & LNodeFlags.TYPE_MASK) === LNodeFlags.View) { + node = node.view.node; + } + return node as LElementNode; +} + /** * Searches for an instance of the given directive type up the injector tree and returns * that instance if found. @@ -527,29 +578,6 @@ class TemplateRef implements viewEngine_TemplateRef { createEmbeddedView(context: T): viewEngine_EmbeddedViewRef { let viewNode: LViewNode = renderEmbeddedTemplate(null, this._template, context, this._renderer); - return new EmbeddedViewRef(viewNode, this._template, context); + return addDestroyable(new EmbeddedViewRef(viewNode, this._template, context)); } } - -class EmbeddedViewRef implements viewEngine_EmbeddedViewRef { - context: T; - rootNodes: any[]; - /** - * @internal - */ - _lViewNode: LViewNode; - - constructor(viewNode: LViewNode, template: ComponentTemplate, context: T) { - this._lViewNode = viewNode; - this.context = context; - } - - destroy(): void { notImplemented(); } - destroyed: boolean; - onDestroy(callback: Function) { notImplemented(); } - markForCheck(): void { notImplemented(); } - detach(): void { notImplemented(); } - detectChanges(): void { notImplemented(); } - checkNoChanges(): void { notImplemented(); } - reattach(): void { notImplemented(); } -} diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 855d7f3d16..6391cb5a9b 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -11,7 +11,7 @@ import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, def import {InjectFlags} from './di'; import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './interfaces/definition'; -export {InjectFlags, QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, inject, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di'; +export {InjectFlags, QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, inject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di'; export {CssSelector} from './interfaces/projection'; diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index c07c05108d..04f545ba80 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -10,6 +10,7 @@ import './ng_dev_mode'; import {assertEqual, assertLessThan, assertNotEqual, assertNotNull, assertNull, assertSame} from './assert'; import {LContainer, TContainer} from './interfaces/container'; +import {LInjector} from './interfaces/injector'; import {CssSelector, LProjection} from './interfaces/projection'; import {LQueries} from './interfaces/query'; import {LView, LViewFlags, LifecycleStage, RootContext, TData, TView} from './interfaces/view'; @@ -22,6 +23,7 @@ import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveT import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer'; import {isDifferent, stringify} from './util'; import {executeHooks, executeContentHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks'; +import {ViewRef} from './view_ref'; /** * Directive (D) sets a property on all component instances using this constant as a key and the @@ -465,7 +467,9 @@ export function elementStart( if (hostComponentDef) { // TODO(mhevery): This assumes that the directives come in correct order, which // is not guaranteed. Must be refactored to take it into account. - directiveCreate(++index, hostComponentDef.n(), hostComponentDef, queryName); + const instance = hostComponentDef.n(); + directiveCreate(++index, instance, hostComponentDef, queryName); + initChangeDetectorIfExisting(node.nodeInjector, instance); } hack_declareDirectives(index, directiveTypes, localRefs); } @@ -473,6 +477,13 @@ export function elementStart( return native; } +/** Sets the context for a ChangeDetectorRef to the given instance. */ +export function initChangeDetectorIfExisting(injector: LInjector | null, instance: any): void { + if (injector && injector.changeDetectorRef != null) { + (injector.changeDetectorRef as ViewRef)._setComponentContext(instance); + } +} + /** * This function instantiates a directive with a correct queryName. It is a hack since we should * compute the query value only once and store it with the template (rather than on each invocation) @@ -589,10 +600,12 @@ export function locateHostElement( * * @param rNode Render host element. * @param def ComponentDef + * + * @returns LElementNode created */ -export function hostElement(rNode: RElement | null, def: ComponentDef) { +export function hostElement(rNode: RElement | null, def: ComponentDef): LElementNode { resetApplicationState(); - createLNode( + return createLNode( 0, LNodeFlags.Element, rNode, createLView( -1, renderer, getOrCreateTView(def.template), null, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways)); diff --git a/packages/core/src/render3/interfaces/injector.ts b/packages/core/src/render3/interfaces/injector.ts index ca533a339e..59d79c61f7 100644 --- a/packages/core/src/render3/interfaces/injector.ts +++ b/packages/core/src/render3/interfaces/injector.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {ChangeDetectorRef} from '../../change_detection/change_detector_ref'; import {Injector} from '../../di/injector'; import {ElementRef} from '../../linker/element_ref'; import {TemplateRef} from '../../linker/template_ref'; @@ -66,6 +67,12 @@ export interface LInjector { /** Stores the ElementRef so subsequent injections of the ElementRef get the same instance. */ elementRef: ElementRef|null; + + /** + * Stores the ChangeDetectorRef so subsequent injections of the ChangeDetectorRef get the + * same instance. + */ + changeDetectorRef: ChangeDetectorRef|null; } // Note: This hack is necessary so we don't erroneously get a circular dependency diff --git a/packages/core/src/render3/view_ref.ts b/packages/core/src/render3/view_ref.ts new file mode 100644 index 0000000000..fe9965d8b2 --- /dev/null +++ b/packages/core/src/render3/view_ref.ts @@ -0,0 +1,84 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_ViewRef} from '../linker/view_ref'; + +import {ComponentTemplate} from './interfaces/definition'; +import {LViewNode} from './interfaces/node'; +import {notImplemented} from './util'; + +export class ViewRef implements viewEngine_EmbeddedViewRef { + context: T; + rootNodes: any[]; + + constructor(context: T|null) { this.context = context !; } + + /** @internal */ + _setComponentContext(context: T) { this.context = context; } + + destroy(): void { notImplemented(); } + destroyed: boolean; + onDestroy(callback: Function) { notImplemented(); } + markForCheck(): void { notImplemented(); } + detach(): void { notImplemented(); } + detectChanges(): void { notImplemented(); } + checkNoChanges(): void { notImplemented(); } + reattach(): void { notImplemented(); } +} + + +export class EmbeddedViewRef extends ViewRef { + /** + * @internal + */ + _lViewNode: LViewNode; + + constructor(viewNode: LViewNode, template: ComponentTemplate, context: T) { + super(context); + this._lViewNode = viewNode; + } +} + +/** + * Creates a ViewRef bundled with destroy functionality. + * + * @param context The context for this view + * @returns The ViewRef + */ +export function createViewRef(context: T): ViewRef { + // TODO: add detectChanges back in when implementing ChangeDetectorRef.detectChanges + return addDestroyable(new ViewRef(context)); +} + +/** Interface for destroy logic. Implemented by addDestroyable. */ +export interface DestroyRef { + /** Whether or not this object has been destroyed */ + destroyed: boolean; + /** Destroy the instance and call all onDestroy callbacks. */ + destroy(): void; + /** Register callbacks that should be called onDestroy */ + onDestroy(cb: Function): void; +} + +/** + * Decorates an object with destroy logic (implementing the DestroyRef interface) + * and returns the enhanced object. + * + * @param obj The object to decorate + * @returns The object with destroy logic + */ +export function addDestroyable(obj: any): T&DestroyRef { + let destroyFn: Function[]|null = null; + obj.destroyed = false; + obj.destroy = function() { + destroyFn && destroyFn.forEach((fn) => fn()); + this.destroyed = true; + }; + obj.onDestroy = (fn: Function) => (destroyFn || (destroyFn = [])).push(fn); + return obj; +} diff --git a/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts b/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts index e69069ff23..b44e41b93b 100644 --- a/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts +++ b/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts @@ -6,9 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectionStrategy, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; import * as $r3$ from '../../../src/core_render3_private_export'; - import {renderComponent, toHtml} from '../render_util'; /** @@ -1313,6 +1312,52 @@ describe('compiler specification', () => { }); + it('should inject ChangeDetectorRef', () => { + type $MyComp$ = MyComp; + type $MyApp$ = MyApp; + + @Component({selector: 'my-comp', template: `{{ value }}`}) + class MyComp { + value: string; + constructor(public cdr: ChangeDetectorRef) { this.value = (cdr.constructor as any).name; } + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComp, + tag: 'my-comp', + factory: function MyComp_Factory() { return new MyComp($r3$.ɵinjectChangeDetectorRef()); }, + template: function MyComp_Template(ctx: $MyComp$, cm: $boolean$) { + if (cm) { + $r3$.ɵT(0); + } + $r3$.ɵt(0, $r3$.ɵb(ctx.value)); + } + }); + // /NORMATIVE + } + + class MyApp { + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + /** */ + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, MyComp); + $r3$.ɵe(); + } + MyComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + } + + const app = renderComponent(MyApp); + // ChangeDetectorRef is the token, ViewRef is historically the constructor + expect(toHtml(app)).toEqual('ViewRef'); + }); + describe('template variables', () => { interface ForOfContext { diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 8184d55c31..97c79c2bb7 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -6,17 +6,18 @@ * found in the LICENSE file at https://angular.io/license */ -import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core'; +import {ChangeDetectorRef, ElementRef, TemplateRef, ViewContainerRef} from '@angular/core'; import {defineComponent} from '../../src/render3/definition'; import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector} from '../../src/render3/di'; -import {PublicFeature, defineDirective, inject, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, text, textBinding} from '../../src/render3/instructions'; +import {NgOnChangesFeature, PublicFeature, defineDirective, inject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; +import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, directiveRefresh, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {LInjector} from '../../src/render3/interfaces/injector'; import {LNodeFlags} from '../../src/render3/interfaces/node'; import {LViewFlags} from '../../src/render3/interfaces/view'; +import {ViewRef} from '../../src/render3/view_ref'; -import {renderComponent, renderToHtml} from './render_util'; +import {renderComponent, renderToHtml, toHtml} from './render_util'; describe('di', () => { describe('no dependencies', () => { @@ -199,6 +200,273 @@ describe('di', () => { }); }); + describe('ChangeDetectorRef', () => { + let dir: Directive; + let dirSameInstance: DirectiveSameInstance; + let comp: MyComp; + + class MyComp { + constructor(public cdr: ChangeDetectorRef) {} + + static ngComponentDef = defineComponent({ + type: MyComp, + tag: 'my-comp', + factory: () => comp = new MyComp(injectChangeDetectorRef()), + template: function(ctx: MyComp, cm: boolean) { + if (cm) { + projectionDef(0); + projection(1, 0); + } + } + }); + } + + class Directive { + value: string; + constructor(public cdr: ChangeDetectorRef) { this.value = (cdr.constructor as any).name; } + static ngDirectiveDef = defineDirective({ + type: Directive, + factory: () => dir = new Directive(injectChangeDetectorRef()), + features: [PublicFeature], + exportAs: 'dir' + }); + } + + class DirectiveSameInstance { + constructor(public cdr: ChangeDetectorRef) {} + + static ngDirectiveDef = defineDirective({ + type: DirectiveSameInstance, + factory: () => dirSameInstance = new DirectiveSameInstance(injectChangeDetectorRef()) + }); + } + + const $e0_attrs$ = ['dir', '', 'dirSameInstance', '']; + + it('should inject current component ChangeDetectorRef into directives on components', () => { + class MyApp { + static ngComponentDef = defineComponent({ + type: MyApp, + tag: 'my-app', + factory: () => new MyApp(), + /** {{ dir.value }} */ + template: function(ctx: any, cm: boolean) { + if (cm) { + elementStart(0, MyComp, $e0_attrs$, [Directive, DirectiveSameInstance]); + elementEnd(); + text(4); + } + textBinding(4, bind(load(2).value)); + MyComp.ngComponentDef.h(1, 0); + Directive.ngDirectiveDef.h(2, 0); + DirectiveSameInstance.ngDirectiveDef.h(3, 0); + directiveRefresh(1, 0); + directiveRefresh(2, 0); + directiveRefresh(3, 0); + } + }); + } + + const app = renderComponent(MyApp); + // ChangeDetectorRef is the token, ViewRef has historically been the constructor + expect(toHtml(app)).toEqual('ViewRef'); + expect((comp !.cdr as ViewRef).context).toBe(comp); + + expect(dir !.cdr).toBe(comp !.cdr); + expect(dir !.cdr).toBe(dirSameInstance !.cdr); + }); + + it('should inject host component ChangeDetectorRef into directives on elements', () => { + + class MyApp { + constructor(public cdr: ChangeDetectorRef) {} + + static ngComponentDef = defineComponent({ + type: MyApp, + tag: 'my-app', + factory: () => new MyApp(injectChangeDetectorRef()), + /**
{{ dir.value }}
*/ + template: function(ctx: any, cm: boolean) { + if (cm) { + elementStart(0, 'div', $e0_attrs$, [Directive, DirectiveSameInstance]); + { text(3); } + elementEnd(); + } + textBinding(3, bind(load(1).value)); + Directive.ngDirectiveDef.h(1, 0); + DirectiveSameInstance.ngDirectiveDef.h(2, 0); + directiveRefresh(1, 0); + directiveRefresh(2, 0); + } + }); + } + + const app = renderComponent(MyApp); + expect(toHtml(app)).toEqual('
ViewRef
'); + expect((app !.cdr as ViewRef).context).toBe(app); + + expect(dir !.cdr).toBe(app.cdr); + expect(dir !.cdr).toBe(dirSameInstance !.cdr); + }); + + it('should inject host component ChangeDetectorRef into directives in ContentChildren', () => { + class MyApp { + constructor(public cdr: ChangeDetectorRef) {} + + static ngComponentDef = defineComponent({ + type: MyApp, + tag: 'my-app', + factory: () => new MyApp(injectChangeDetectorRef()), + /** + * + *
+ *
+ * {{ dir.value }} + */ + template: function(ctx: any, cm: boolean) { + if (cm) { + elementStart(0, MyComp); + { + elementStart(2, 'div', $e0_attrs$, [Directive, DirectiveSameInstance]); + elementEnd(); + } + elementEnd(); + text(5); + } + textBinding(5, bind(load(3).value)); + MyComp.ngComponentDef.h(1, 0); + Directive.ngDirectiveDef.h(3, 2); + DirectiveSameInstance.ngDirectiveDef.h(4, 2); + directiveRefresh(1, 0); + directiveRefresh(3, 2); + directiveRefresh(4, 2); + } + }); + } + + const app = renderComponent(MyApp); + expect(toHtml(app)) + .toEqual('
ViewRef'); + expect((app !.cdr as ViewRef).context).toBe(app); + + expect(dir !.cdr).toBe(app !.cdr); + expect(dir !.cdr).toBe(dirSameInstance !.cdr); + }); + + it('should inject host component ChangeDetectorRef into directives in embedded views', () => { + + class MyApp { + showing = true; + + constructor(public cdr: ChangeDetectorRef) {} + + static ngComponentDef = defineComponent({ + type: MyApp, + tag: 'my-app', + factory: () => new MyApp(injectChangeDetectorRef()), + /** + * % if (showing) { + *
{{ dir.value }}
+ * % } + */ + template: function(ctx: MyApp, cm: boolean) { + if (cm) { + container(0); + } + containerRefreshStart(0); + { + if (ctx.showing) { + if (embeddedViewStart(0)) { + elementStart(0, 'div', $e0_attrs$, [Directive, DirectiveSameInstance]); + { text(3); } + elementEnd(); + } + textBinding(3, bind(load(1).value)); + Directive.ngDirectiveDef.h(1, 0); + DirectiveSameInstance.ngDirectiveDef.h(2, 0); + directiveRefresh(1, 0); + directiveRefresh(2, 0); + } + embeddedViewEnd(); + } + containerRefreshEnd(); + } + }); + } + + const app = renderComponent(MyApp); + expect(toHtml(app)).toEqual('
ViewRef
'); + expect((app !.cdr as ViewRef).context).toBe(app); + + expect(dir !.cdr).toBe(app.cdr); + expect(dir !.cdr).toBe(dirSameInstance !.cdr); + }); + + it('should inject host component ChangeDetectorRef into directives on containers', () => { + class IfDirective { + /* @Input */ + myIf = true; + + constructor(public template: TemplateRef, public vcr: ViewContainerRef) {} + + ngOnChanges() { + if (this.myIf) { + this.vcr.createEmbeddedView(this.template); + } + } + + static ngDirectiveDef = defineDirective({ + type: IfDirective, + factory: () => new IfDirective(injectTemplateRef(), injectViewContainerRef()), + inputs: {myIf: 'myIf'}, + features: [PublicFeature, NgOnChangesFeature] + }); + } + + class MyApp { + showing = true; + + constructor(public cdr: ChangeDetectorRef) {} + + static ngComponentDef = defineComponent({ + type: MyApp, + tag: 'my-app', + factory: () => new MyApp(injectChangeDetectorRef()), + /**
{{ dir.value }}
*/ + template: function(ctx: MyApp, cm: boolean) { + if (cm) { + container(0, [IfDirective], C1); + } + containerRefreshStart(0); + { directiveRefresh(1, 0); } + containerRefreshEnd(); + + function C1(ctx1: any, cm1: boolean) { + if (cm1) { + elementStart(0, 'div', $e0_attrs$, [Directive, DirectiveSameInstance]); + { text(3); } + elementEnd(); + } + textBinding(3, bind(load(1).value)); + Directive.ngDirectiveDef.h(1, 0); + DirectiveSameInstance.ngDirectiveDef.h(2, 0); + directiveRefresh(1, 0); + directiveRefresh(2, 0); + } + } + }); + } + + const app = renderComponent(MyApp); + expect(toHtml(app)).toEqual('
ViewRef
'); + expect((app !.cdr as ViewRef).context).toBe(app); + + expect(dir !.cdr).toBe(app.cdr); + expect(dir !.cdr).toBe(dirSameInstance !.cdr); + }); + + }); + describe('inject', () => { describe('bloom filter', () => { let di: LInjector; From 288851c41e64bb53f5c095bbd2c18f2a3a866508 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Tue, 27 Feb 2018 15:04:35 -0800 Subject: [PATCH 019/604] release: add ng update package group metadata to angular (#22482) "ng update" supports having multiple packages as part of a group which should be updated together, meaning that e.g. calling "ng update @angular/core" would be equivalent to updating all packages of the group (that are part of the package.json already). In order to support the grouping feature, the package.json of the version the user is updating to needs to include an "ng-update" key that points to this metadata. The entire specification for the update workflow can be found here: https://github.com/hansl/devkit/blob/2e8b12a4ef53833dc006e8711acf95cf3a6cf24e/docs/specifications/update.md PR Close #22482 --- build.sh | 8 ++++++++ packages/animations/package.json | 3 +++ packages/common/package.json | 3 +++ packages/compiler-cli/package.json | 5 ++++- packages/compiler/package.json | 3 +++ packages/core/package.json | 3 +++ packages/forms/package.json | 3 +++ packages/http/package.json | 3 +++ packages/language-service/package.json | 3 +++ packages/platform-browser-dynamic/package.json | 3 +++ packages/platform-browser/package.json | 3 +++ packages/platform-server/package.json | 3 +++ packages/platform-webworker-dynamic/package.json | 3 +++ packages/platform-webworker/package.json | 3 +++ packages/router/package.json | 3 +++ packages/service-worker/package.json | 3 +++ packages/upgrade/package.json | 3 +++ 17 files changed, 57 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 07f9cb0d41..9b45f02815 100755 --- a/build.sh +++ b/build.sh @@ -33,6 +33,11 @@ TSC_PACKAGES=(compiler-cli NODE_PACKAGES=(compiler-cli benchpress) +NG_UPDATE_PACKAGE_GROUP=$( + echo \[\"${PACKAGES[@]}\"] | sed 's/ /", "/g' +) + + BUILD_ALL=true BUNDLE=true VERSION_PREFIX=$(node -p "require('./package.json').version") @@ -491,6 +496,9 @@ do rsync -am --include="package.json" --include="*/" --exclude=* ${SRC_DIR}/ ${NPM_DIR}/ rsync -am --include="*.externs.js" --include="*/" --exclude=* ${SRC_DIR}/ ${NPM_DIR}/ + # Replace the NG_UPDATE_PACKAGE_GROUP value with the JSON array of packages. + perl -p -i -e "s/\"NG_UPDATE_PACKAGE_GROUP\"/${NG_UPDATE_PACKAGE_GROUP}/g" ${NPM_DIR}/package.json < /dev/null 2> /dev/null + cp ${ROOT_DIR}/README.md ${NPM_DIR}/ fi diff --git a/packages/animations/package.json b/packages/animations/package.json index 278211f675..bcbb39c185 100644 --- a/packages/animations/package.json +++ b/packages/animations/package.json @@ -17,5 +17,8 @@ "repository": { "type": "git", "url": "https://github.com/angular/angular.git" + }, + "ng-update": { + "packageGroup": "NG_UPDATE_PACKAGE_GROUP" } } diff --git a/packages/common/package.json b/packages/common/package.json index bac145971e..af7fd8225c 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -19,5 +19,8 @@ "repository": { "type": "git", "url": "https://github.com/angular/angular.git" + }, + "ng-update": { + "packageGroup": "NG_UPDATE_PACKAGE_GROUP" } } diff --git a/packages/compiler-cli/package.json b/packages/compiler-cli/package.json index 9fc4537205..4b04290089 100644 --- a/packages/compiler-cli/package.json +++ b/packages/compiler-cli/package.json @@ -34,5 +34,8 @@ "bugs": { "url": "https://github.com/angular/angular/issues" }, - "homepage": "https://github.com/angular/angular/tree/master/packages/compiler-cli" + "homepage": "https://github.com/angular/angular/tree/master/packages/compiler-cli", + "ng-update": { + "packageGroup": "NG_UPDATE_PACKAGE_GROUP" + } } diff --git a/packages/compiler/package.json b/packages/compiler/package.json index f94e61dcaa..2569fa40d4 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -14,5 +14,8 @@ "repository": { "type": "git", "url": "https://github.com/angular/angular.git" + }, + "ng-update": { + "packageGroup": "NG_UPDATE_PACKAGE_GROUP" } } diff --git a/packages/core/package.json b/packages/core/package.json index 3161a3fedb..2d67e1d883 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -18,5 +18,8 @@ "repository": { "type": "git", "url": "https://github.com/angular/angular.git" + }, + "ng-update": { + "packageGroup": "NG_UPDATE_PACKAGE_GROUP" } } diff --git a/packages/forms/package.json b/packages/forms/package.json index d81fcefeb0..e81bb32fbb 100644 --- a/packages/forms/package.json +++ b/packages/forms/package.json @@ -20,5 +20,8 @@ "repository": { "type": "git", "url": "https://github.com/angular/angular.git" + }, + "ng-update": { + "packageGroup": "NG_UPDATE_PACKAGE_GROUP" } } diff --git a/packages/http/package.json b/packages/http/package.json index 78b2d28a6a..a034886252 100644 --- a/packages/http/package.json +++ b/packages/http/package.json @@ -19,5 +19,8 @@ "repository": { "type": "git", "url": "https://github.com/angular/angular.git" + }, + "ng-update": { + "packageGroup": "NG_UPDATE_PACKAGE_GROUP" } } diff --git a/packages/language-service/package.json b/packages/language-service/package.json index 8776c2d723..27fd0ce4a9 100644 --- a/packages/language-service/package.json +++ b/packages/language-service/package.json @@ -10,5 +10,8 @@ "repository": { "type": "git", "url": "https://github.com/angular/angular.git" + }, + "ng-update": { + "packageGroup": "NG_UPDATE_PACKAGE_GROUP" } } diff --git a/packages/platform-browser-dynamic/package.json b/packages/platform-browser-dynamic/package.json index cba46009cc..9e2facc331 100644 --- a/packages/platform-browser-dynamic/package.json +++ b/packages/platform-browser-dynamic/package.json @@ -20,5 +20,8 @@ "repository": { "type": "git", "url": "https://github.com/angular/angular.git" + }, + "ng-update": { + "packageGroup": "NG_UPDATE_PACKAGE_GROUP" } } diff --git a/packages/platform-browser/package.json b/packages/platform-browser/package.json index 4b3574cb82..1a990e6a21 100644 --- a/packages/platform-browser/package.json +++ b/packages/platform-browser/package.json @@ -18,5 +18,8 @@ "repository": { "type": "git", "url": "https://github.com/angular/angular.git" + }, + "ng-update": { + "packageGroup": "NG_UPDATE_PACKAGE_GROUP" } } diff --git a/packages/platform-server/package.json b/packages/platform-server/package.json index 50d9100062..208965b867 100644 --- a/packages/platform-server/package.json +++ b/packages/platform-server/package.json @@ -24,5 +24,8 @@ "repository": { "type": "git", "url": "https://github.com/angular/angular.git" + }, + "ng-update": { + "packageGroup": "NG_UPDATE_PACKAGE_GROUP" } } diff --git a/packages/platform-webworker-dynamic/package.json b/packages/platform-webworker-dynamic/package.json index 5a10203e7b..6a4f6f0096 100644 --- a/packages/platform-webworker-dynamic/package.json +++ b/packages/platform-webworker-dynamic/package.json @@ -21,5 +21,8 @@ "repository": { "type": "git", "url": "https://github.com/angular/angular.git" + }, + "ng-update": { + "packageGroup": "NG_UPDATE_PACKAGE_GROUP" } } diff --git a/packages/platform-webworker/package.json b/packages/platform-webworker/package.json index e3f8ae9803..e3a3740c50 100644 --- a/packages/platform-webworker/package.json +++ b/packages/platform-webworker/package.json @@ -19,5 +19,8 @@ "repository": { "type": "git", "url": "https://github.com/angular/angular.git" + }, + "ng-update": { + "packageGroup": "NG_UPDATE_PACKAGE_GROUP" } } diff --git a/packages/router/package.json b/packages/router/package.json index 6ec296847c..c2b6f4272c 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -28,5 +28,8 @@ "@angular/common": "0.0.0-PLACEHOLDER", "@angular/platform-browser": "0.0.0-PLACEHOLDER", "rxjs": "^5.5.0" + }, + "ng-update": { + "packageGroup": "NG_UPDATE_PACKAGE_GROUP" } } diff --git a/packages/service-worker/package.json b/packages/service-worker/package.json index e945daa756..5c33e5de79 100644 --- a/packages/service-worker/package.json +++ b/packages/service-worker/package.json @@ -21,5 +21,8 @@ }, "bin": { "ngsw-config": "./ngsw-config.js" + }, + "ng-update": { + "packageGroup": "NG_UPDATE_PACKAGE_GROUP" } } diff --git a/packages/upgrade/package.json b/packages/upgrade/package.json index 17eabf0591..5e92fad670 100644 --- a/packages/upgrade/package.json +++ b/packages/upgrade/package.json @@ -20,5 +20,8 @@ "repository": { "type": "git", "url": "https://github.com/angular/angular.git" + }, + "ng-update": { + "packageGroup": "NG_UPDATE_PACKAGE_GROUP" } } From e751a0a2bbde3f402908657c3b17505a68817154 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Wed, 28 Feb 2018 15:17:30 -0800 Subject: [PATCH 020/604] docs: add changelog for 6.0.0-beta.6 --- CHANGELOG.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 983b2ccd65..3d590aff0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,73 @@ + +# [6.0.0-beta.5](https://github.com/angular/angular/compare/6.0.0-beta.4...6.0.0-beta.5) (2018-02-28) + + +### Bug Fixes + +* **animations:** only use the WA-polyfill alongside AnimationBuilder ([#22143](https://github.com/angular/angular/issues/22143)) ([b2f366b](https://github.com/angular/angular/commit/b2f366b)), closes [#17496](https://github.com/angular/angular/issues/17496) +* **animations:** report correct totalTime value even during noOp animations ([#22225](https://github.com/angular/angular/issues/22225)) ([e1bf067](https://github.com/angular/angular/commit/e1bf067)) +* **bazel:** ng_package includes transitive .d.ts and flatModuleMetadata ([#22499](https://github.com/angular/angular/issues/22499)) ([aabe16c](https://github.com/angular/angular/commit/aabe16c)), closes [#22419](https://github.com/angular/angular/issues/22419) +* **common:** correct mapping of Observable methods ([#20518](https://github.com/angular/angular/issues/20518)) ([2639b4b](https://github.com/angular/angular/commit/2639b4b)), closes [#20516](https://github.com/angular/angular/issues/20516) +* **common:** then and else template might be set to null ([#22298](https://github.com/angular/angular/issues/22298)) ([8115edc](https://github.com/angular/angular/commit/8115edc)) +* **compiler-cli:** add missing entry point to package, update tsickle ([#22295](https://github.com/angular/angular/issues/22295)) ([28ac244](https://github.com/angular/angular/commit/28ac244)) +* **core:** export inject() from [@angular](https://github.com/angular)/core ([#22389](https://github.com/angular/angular/issues/22389)) ([f8749bf](https://github.com/angular/angular/commit/f8749bf)), closes [#22388](https://github.com/angular/angular/issues/22388) +* **core:** properly handle function without prototype in reflector ([#22284](https://github.com/angular/angular/issues/22284)) ([a7ebf5a](https://github.com/angular/angular/commit/a7ebf5a)), closes [#19978](https://github.com/angular/angular/issues/19978) +* **core:** require factory to be provided for shakeable InjectionToken ([#22207](https://github.com/angular/angular/issues/22207)) ([f755db7](https://github.com/angular/angular/commit/f755db7)), closes [#22205](https://github.com/angular/angular/issues/22205) +* **forms:** set state before emitting a value from ngModelChange ([#21514](https://github.com/angular/angular/issues/21514)) ([3e6a86f](https://github.com/angular/angular/commit/3e6a86f)), closes [#21513](https://github.com/angular/angular/issues/21513) +* **platform-server:** generate correct stylings for camel case names ([#22263](https://github.com/angular/angular/issues/22263)) ([40ba009](https://github.com/angular/angular/commit/40ba009)), closes [#19235](https://github.com/angular/angular/issues/19235) +* **router:** don't mutate route configs ([#22358](https://github.com/angular/angular/issues/22358)) ([45eff4c](https://github.com/angular/angular/commit/45eff4c)), closes [#22203](https://github.com/angular/angular/issues/22203) +* **router:** fix URL serialization so special characters are only encoded where needed ([#22337](https://github.com/angular/angular/issues/22337)) ([094666d](https://github.com/angular/angular/commit/094666d)), closes [#10280](https://github.com/angular/angular/issues/10280) +* **upgrade:** correctly destroy nested downgraded component ([#22400](https://github.com/angular/angular/issues/22400)) ([8a85888](https://github.com/angular/angular/commit/8a85888)), closes [#22392](https://github.com/angular/angular/issues/22392) +* **upgrade:** correctly handle `=` bindings in `[@angular](https://github.com/angular)/upgrade` ([#22167](https://github.com/angular/angular/issues/22167)) ([f089bf5](https://github.com/angular/angular/commit/f089bf5)) +* **upgrade:** fix empty transclusion content with AngularJS@>=1.5.8 ([#22167](https://github.com/angular/angular/issues/22167)) ([13ab91e](https://github.com/angular/angular/commit/13ab91e)), closes [#22175](https://github.com/angular/angular/issues/22175) + + +### Features + +* **bazel:** add an ng_package rule ([#22221](https://github.com/angular/angular/issues/22221)) ([b43b164](https://github.com/angular/angular/commit/b43b164)) +* **bazel:** introduce a binary stamping feature ([#22176](https://github.com/angular/angular/issues/22176)) ([bba65e0](https://github.com/angular/angular/commit/bba65e0)) +* **bazel:** ng_module produces bundle index ([#22176](https://github.com/angular/angular/issues/22176)) ([029dbf0](https://github.com/angular/angular/commit/029dbf0)) +* **bazel:** ng_package adds package.json props ([#22499](https://github.com/angular/angular/issues/22499)) ([b6c9410](https://github.com/angular/angular/commit/b6c9410)), closes [#22416](https://github.com/angular/angular/issues/22416) +* **common:** better error message when non-template element used in NgIf ([#22274](https://github.com/angular/angular/issues/22274)) ([67cf11d](https://github.com/angular/angular/commit/67cf11d)), closes [#16410](https://github.com/angular/angular/issues/16410) +* **common:** export functions to format numbers, percents, currencies & dates ([#22423](https://github.com/angular/angular/issues/22423)) ([4180912](https://github.com/angular/angular/commit/4180912)), closes [#20536](https://github.com/angular/angular/issues/20536) +* **compiler-cli:** Check unvalidated combination of ngc and TypeScript ([#22293](https://github.com/angular/angular/issues/22293)) ([3ceee99](https://github.com/angular/angular/commit/3ceee99)), closes [#20669](https://github.com/angular/angular/issues/20669) +* **core:** set preserveWhitespaces to false by default ([#22046](https://github.com/angular/angular/issues/22046)) ([f1a0632](https://github.com/angular/angular/commit/f1a0632)), closes [#22027](https://github.com/angular/angular/issues/22027) +* **core:** support metadata reflection for native class types ([#22356](https://github.com/angular/angular/issues/22356)) ([5c89d6b](https://github.com/angular/angular/commit/5c89d6b)), closes [#21731](https://github.com/angular/angular/issues/21731) +* **core:** support metadata reflection for native class types ([#22356](https://github.com/angular/angular/issues/22356)) ([b7544cc](https://github.com/angular/angular/commit/b7544cc)), closes [#21731](https://github.com/angular/angular/issues/21731) +* **forms:** allow markAsPending to emit events ([#20212](https://github.com/angular/angular/issues/20212)) ([e86b64b](https://github.com/angular/angular/commit/e86b64b)), closes [#17958](https://github.com/angular/angular/issues/17958) +* allow direct scoping of @Injectables to the root injector ([#22185](https://github.com/angular/angular/issues/22185)) ([7ac34e4](https://github.com/angular/angular/commit/7ac34e4)) +* **platform-browser:** do not throw error when Hammer.js not loaded ([#22257](https://github.com/angular/angular/issues/22257)) ([991300b](https://github.com/angular/angular/commit/991300b)), closes [#16992](https://github.com/angular/angular/issues/16992) +* **platform-browser:** fix [#19604](https://github.com/angular/angular/issues/19604), can config hammerOptions ([#21979](https://github.com/angular/angular/issues/21979)) ([1d571b2](https://github.com/angular/angular/commit/1d571b2)) +* **platform-server:** bump Domino to v2.0 ([#22411](https://github.com/angular/angular/issues/22411)) ([d3827a0](https://github.com/angular/angular/commit/d3827a0)) + + +### BREAKING CHANGES + +* **platform-server:** * Bump the dependency on Domino to 2.0 to resolve issues with + namespacing +* **forms:** - `AbstractControl#statusChanges` now emits an event of `'PENDING'` when you call `AbstractControl#markAsPending` +- Previously it did not emit an event when you called `markAsPending` +- To migrate you would need to ensure that if you are filtering or checking events from `statusChanges` that you account for the new event when calling `markAsPending` +* **animations:** When animation is trigged within a disabled zone, the +associated event (which an instance of AnimationEvent) will no longer +report the totalTime as 0 (it will emit the actual time of the +animation). To detect if an animation event is reporting a disabled +animation then the `event.disabled` property can be used instead. + + + + +## [5.2.7](https://github.com/angular/angular/compare/5.2.6...5.2.7) (2018-02-28) + + +### Bug Fixes + +* **platform-server:** generate correct stylings for camel case names ([#22263](https://github.com/angular/angular/issues/22263)) ([de02a7a](https://github.com/angular/angular/commit/de02a7a)), closes [#19235](https://github.com/angular/angular/issues/19235) +* **router:** don't mutate route configs ([#22358](https://github.com/angular/angular/issues/22358)) ([8f0a064](https://github.com/angular/angular/commit/8f0a064)), closes [#22203](https://github.com/angular/angular/issues/22203) +* **upgrade:** correctly destroy nested downgraded component ([#22400](https://github.com/angular/angular/issues/22400)) ([4aef9de](https://github.com/angular/angular/commit/4aef9de)), closes [#22392](https://github.com/angular/angular/issues/22392) +* **upgrade:** correctly handle `=` bindings in `[@angular](https://github.com/angular)/upgrade` ([#22167](https://github.com/angular/angular/issues/22167)) ([6638390](https://github.com/angular/angular/commit/6638390)) +* **upgrade:** fix empty transclusion content with AngularJS@>=1.5.8 ([#22167](https://github.com/angular/angular/issues/22167)) ([a9a0e27](https://github.com/angular/angular/commit/a9a0e27)), closes [#22175](https://github.com/angular/angular/issues/22175) + # [6.0.0-beta.5](https://github.com/angular/angular/compare/6.0.0-beta.4...6.0.0-beta.5) (2018-02-22) From b8fe121a16785ffcf09f184e934505fd5b4f00c1 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Wed, 28 Feb 2018 15:23:54 -0800 Subject: [PATCH 021/604] release: cut the 6.0.0-beta.6 release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6515cb834e..7a5f16d1ce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-srcs", - "version": "6.0.0-beta.5", + "version": "6.0.0-beta.6", "private": true, "branchPattern": "2.0.*", "description": "Angular - a web framework for modern web apps", From 2f0ab7ee98270fa6f87a9af261a19002b6b98742 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Thu, 1 Mar 2018 06:05:24 -0800 Subject: [PATCH 022/604] docs: correct changelog version for 6.0.0-beta.6 --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d590aff0e..2dddf84efc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ - -# [6.0.0-beta.5](https://github.com/angular/angular/compare/6.0.0-beta.4...6.0.0-beta.5) (2018-02-28) + +# [6.0.0-beta.6](https://github.com/angular/angular/compare/6.0.0-beta.5...6.0.0-beta.6) (2018-02-28) ### Bug Fixes From 94707fe7957d2da630ab03d08ef7ad64024e802b Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 28 Feb 2018 20:10:14 +0000 Subject: [PATCH 023/604] build(aio): initialise `exampleMap` correctly (#22502) The `exampleMap` needs to hold an hash object for each of the `collectExamples.exampleFolders` paths. Previously these hash objects were only created if there was actually an example file the hash's respective example folder. This could cause crashes during `yarn docs-watch` (and so also `yarn sync-and-serve`) if no examples were read in for a particular run of the doc-gen. PR Close #22502 --- .../examples-package/processors/collect-examples.js | 2 +- .../examples-package/processors/collect-examples.spec.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/aio/tools/transforms/examples-package/processors/collect-examples.js b/aio/tools/transforms/examples-package/processors/collect-examples.js index 5bb6a94c50..ebf66658a9 100644 --- a/aio/tools/transforms/examples-package/processors/collect-examples.js +++ b/aio/tools/transforms/examples-package/processors/collect-examples.js @@ -27,6 +27,7 @@ module.exports = function collectExamples(exampleMap, regionParser, log, createD }, $process(docs) { const exampleFolders = this.exampleFolders; + exampleFolders.forEach(folder => exampleMap[folder] = exampleMap[folder] || {}); const regionDocs = []; docs = docs.filter((doc) => { if (doc.docType === 'example-file') { @@ -36,7 +37,6 @@ module.exports = function collectExamples(exampleMap, regionParser, log, createD if (doc.fileInfo.relativePath.indexOf(folder) === 0) { const relativePath = doc.fileInfo.relativePath.substr(folder.length).replace(/^\//, ''); - exampleMap[folder] = exampleMap[folder] || {}; exampleMap[folder][relativePath] = doc; // We treat files that end in `.annotated` specially diff --git a/aio/tools/transforms/examples-package/processors/collect-examples.spec.js b/aio/tools/transforms/examples-package/processors/collect-examples.spec.js index 583c3a1029..85f10ca02e 100644 --- a/aio/tools/transforms/examples-package/processors/collect-examples.spec.js +++ b/aio/tools/transforms/examples-package/processors/collect-examples.spec.js @@ -25,6 +25,12 @@ describe('collectExampleRegions processor', () => { describe('$process', () => { + it('should initialise the `exampleMap` even if there are no examples to collect', () => { + processor.$process([]); + expect(exampleMap['examples-1']).toEqual(jasmine.any(Object)); + expect(exampleMap['examples-2']).toEqual(jasmine.any(Object)); + }); + it('should identify example files that are in the exampleFolders', () => { const docs = [ createDoc('A', 'examples-1/x/app.js'), createDoc('B', 'examples-1/y/index.html'), From 997b30a093a770bd74664233cfff1447ad7dd73f Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 28 Feb 2018 14:52:29 +0000 Subject: [PATCH 024/604] build(aio): move link disambiguation from `getLinkInfo` to `getDocFromAlias` (#22494) The disambiguation needs to be done earlier so that the auto-link-code post-processor can benefit from it. PR Close #22494 --- aio/tools/transforms/links-package/index.js | 13 ++++--- .../deprecatedDocsLinkDisambiguator.js | 12 ------ .../disambiguateByDeprecated.js | 3 ++ .../disambiguateByDeprecated.spec.js | 17 +++++++++ .../disambiguators/disambiguateByModule.js | 12 ++++++ .../disambiguateByModule.spec.js | 29 ++++++++++++++ .../links-package/services/getDocFromAlias.js | 38 ++++++++----------- .../services/getDocFromAlias.spec.js | 37 +++++++++--------- .../links-package/services/getLinkInfo.js | 11 ------ .../services/moduleScopeLinkDisambiguator.js | 15 -------- 10 files changed, 101 insertions(+), 86 deletions(-) delete mode 100644 aio/tools/transforms/links-package/services/deprecatedDocsLinkDisambiguator.js create mode 100644 aio/tools/transforms/links-package/services/disambiguators/disambiguateByDeprecated.js create mode 100644 aio/tools/transforms/links-package/services/disambiguators/disambiguateByDeprecated.spec.js create mode 100644 aio/tools/transforms/links-package/services/disambiguators/disambiguateByModule.js create mode 100644 aio/tools/transforms/links-package/services/disambiguators/disambiguateByModule.spec.js delete mode 100644 aio/tools/transforms/links-package/services/moduleScopeLinkDisambiguator.js diff --git a/aio/tools/transforms/links-package/index.js b/aio/tools/transforms/links-package/index.js index 1fab11d2ae..2f6964cfaf 100644 --- a/aio/tools/transforms/links-package/index.js +++ b/aio/tools/transforms/links-package/index.js @@ -8,15 +8,16 @@ module.exports = .factory(require('./services/getAliases')) .factory(require('./services/getDocFromAlias')) .factory(require('./services/getLinkInfo')) - .factory(require('./services/moduleScopeLinkDisambiguator')) - .factory(require('./services/deprecatedDocsLinkDisambiguator')) + .factory(require('./services/disambiguators/disambiguateByDeprecated')) + .factory(require('./services/disambiguators/disambiguateByModule')) .config(function(inlineTagProcessor, linkInlineTagDef) { inlineTagProcessor.inlineTagDefinitions.push(linkInlineTagDef); }) - .config(function( - getLinkInfo, moduleScopeLinkDisambiguator, deprecatedDocsLinkDisambiguator) { - getLinkInfo.disambiguators.push(moduleScopeLinkDisambiguator); - getLinkInfo.disambiguators.push(deprecatedDocsLinkDisambiguator); + .config(function(getDocFromAlias, disambiguateByDeprecated, disambiguateByModule) { + getDocFromAlias.disambiguators = [ + disambiguateByDeprecated, + disambiguateByModule + ]; }); diff --git a/aio/tools/transforms/links-package/services/deprecatedDocsLinkDisambiguator.js b/aio/tools/transforms/links-package/services/deprecatedDocsLinkDisambiguator.js deleted file mode 100644 index 662d3e53e2..0000000000 --- a/aio/tools/transforms/links-package/services/deprecatedDocsLinkDisambiguator.js +++ /dev/null @@ -1,12 +0,0 @@ -var _ = require('lodash'); - -module.exports = function deprecatedDocsLinkDisambiguator() { - return function(url, title, currentDoc, docs) { - if (docs.length != 2) return docs; - - var filteredDocs = _.filter( - docs, function(doc) { return !doc.fileInfo.relativePath.match(/\/(\w+)-deprecated\//); }); - - return filteredDocs.length > 0 ? filteredDocs : docs; - }; -}; diff --git a/aio/tools/transforms/links-package/services/disambiguators/disambiguateByDeprecated.js b/aio/tools/transforms/links-package/services/disambiguators/disambiguateByDeprecated.js new file mode 100644 index 0000000000..44427c9d40 --- /dev/null +++ b/aio/tools/transforms/links-package/services/disambiguators/disambiguateByDeprecated.js @@ -0,0 +1,3 @@ +module.exports = function disambiguateByDeprecated() { + return (alias, originatingDoc, docs) => docs.filter(doc => doc.deprecated === undefined); +}; diff --git a/aio/tools/transforms/links-package/services/disambiguators/disambiguateByDeprecated.spec.js b/aio/tools/transforms/links-package/services/disambiguators/disambiguateByDeprecated.spec.js new file mode 100644 index 0000000000..edc51aa9dd --- /dev/null +++ b/aio/tools/transforms/links-package/services/disambiguators/disambiguateByDeprecated.spec.js @@ -0,0 +1,17 @@ +const disambiguateByDeprecated = require('./disambiguateByDeprecated')(); +const docs = [ + { id: 'doc1' }, + { id: 'doc2', deprecated: true }, + { id: 'doc3', deprecated: '' }, + { id: 'doc4' }, + { id: 'doc5', deprecated: 'Some text' }, +]; + +describe('disambiguateByDeprecated', () => { + it('should filter out docs whose `deprecated` property is defined', () => { + expect(disambiguateByDeprecated('alias', {}, docs)).toEqual([ + { id: 'doc1' }, + { id: 'doc4' }, + ]); + }); +}); \ No newline at end of file diff --git a/aio/tools/transforms/links-package/services/disambiguators/disambiguateByModule.js b/aio/tools/transforms/links-package/services/disambiguators/disambiguateByModule.js new file mode 100644 index 0000000000..8ed131477f --- /dev/null +++ b/aio/tools/transforms/links-package/services/disambiguators/disambiguateByModule.js @@ -0,0 +1,12 @@ +module.exports = function disambiguateByModule() { + return (alias, originatingDoc, docs) => { + const originatingModule = originatingDoc && originatingDoc.moduleDoc; + if (originatingModule) { + const filteredDocs = docs.filter(doc => doc.moduleDoc === originatingModule); + if (filteredDocs.length > 0) { + return filteredDocs; + } + } + return docs; + }; +}; diff --git a/aio/tools/transforms/links-package/services/disambiguators/disambiguateByModule.spec.js b/aio/tools/transforms/links-package/services/disambiguators/disambiguateByModule.spec.js new file mode 100644 index 0000000000..f80e24ae85 --- /dev/null +++ b/aio/tools/transforms/links-package/services/disambiguators/disambiguateByModule.spec.js @@ -0,0 +1,29 @@ +const disambiguateByModule = require('./disambiguateByModule')(); +const moduleA = { name: 'a' }; +const moduleB = { name: 'b' }; +const docs = [ + { id: 'doc1', moduleDoc: moduleA }, + { id: 'doc2', moduleDoc: moduleA }, + { id: 'doc3', moduleDoc: moduleB }, +]; + +describe('disambiguateByModule', () => { + it('should return all docs if the originating doc has no moduleDoc', () => { + expect(disambiguateByModule('alias', { }, docs)).toEqual(docs); + }); + + it('should return all docs if no docs match the originating doc moduleDoc', () => { + expect(disambiguateByModule('alias', { moduleDoc: { name: 'c' } }, docs)).toEqual(docs); + }); + + it('should return only docs that match the moduleDoc of the originating doc', () => { + expect(disambiguateByModule('alias', { moduleDoc: moduleA }, docs)).toEqual([ + { id: 'doc1', moduleDoc: moduleA }, + { id: 'doc2', moduleDoc: moduleA }, + ]); + + expect(disambiguateByModule('alias', { moduleDoc: moduleB }, docs)).toEqual([ + { id: 'doc3', moduleDoc: moduleB }, + ]); + }); +}); \ No newline at end of file diff --git a/aio/tools/transforms/links-package/services/getDocFromAlias.js b/aio/tools/transforms/links-package/services/getDocFromAlias.js index 8860198c3c..3cb8173669 100644 --- a/aio/tools/transforms/links-package/services/getDocFromAlias.js +++ b/aio/tools/transforms/links-package/services/getDocFromAlias.js @@ -1,31 +1,23 @@ -var _ = require('lodash'); - /** * @dgService getDocFromAlias * @description Get an array of docs that match this alias, relative to the originating doc. + * + * @property {Array<(alias: string, originatingDoc: Doc, ambiguousDocs: Doc[]) => Doc[]>} disambiguators + * a collection of functions that attempt to resolve ambiguous links. Each disambiguator returns + * a new collection of docs with unwanted ambiguous docs removed (see links-package/service/disambiguators + * for examples). */ module.exports = function getDocFromAlias(aliasMap) { - return function getDocFromAlias(alias, originatingDoc) { - var docs = aliasMap.getDocs(alias); + getDocFromAlias.disambiguators = []; + return getDocFromAlias; - // If there is more than one item with this name then try to filter them by the originatingDoc's - // area - if (docs.length > 1 && originatingDoc && originatingDoc.area) { - docs = _.filter(docs, function(doc) { return doc.area === originatingDoc.area; }); - } - - // If filtering by area left us with none then let's start again - if (docs.length === 0) { - docs = aliasMap.getDocs(alias); - } - - // If there is more than one item with this name then try to filter them by the originatingDoc's - // module - if (docs.length > 1 && originatingDoc && originatingDoc.module) { - docs = _.filter(docs, function(doc) { return doc.module === originatingDoc.module; }); - } - - return docs; - }; + function getDocFromAlias(alias, originatingDoc) { + return getDocFromAlias.disambiguators.reduce( + // Run the disambiguators while there is more than 1 doc found + (docs, disambiguater) => docs.length > 1 ? disambiguater(alias, originatingDoc, docs) : docs, + // Start with the docs that match the alias + aliasMap.getDocs(alias) + ); + } }; diff --git a/aio/tools/transforms/links-package/services/getDocFromAlias.spec.js b/aio/tools/transforms/links-package/services/getDocFromAlias.spec.js index c5b24a47f2..989ff43ab6 100644 --- a/aio/tools/transforms/links-package/services/getDocFromAlias.spec.js +++ b/aio/tools/transforms/links-package/services/getDocFromAlias.spec.js @@ -3,15 +3,15 @@ var Dgeni = require('dgeni'); var getDocFromAlias, aliasMap; -describe('getDocFromAlias', function() { - beforeEach(function() { +describe('getDocFromAlias', () => { + beforeEach(() => { var dgeni = new Dgeni([testPackage('links-package', true)]); var injector = dgeni.configureInjector(); aliasMap = injector.get('aliasMap'); getDocFromAlias = injector.get('getDocFromAlias'); }); - it('should return an array of docs that match the alias', function() { + it('should return an array of docs that match the alias', () => { var doc1 = {aliases: ['a', 'b', 'c']}; var doc2 = {aliases: ['a', 'b']}; var doc3 = {aliases: ['a']}; @@ -24,25 +24,24 @@ describe('getDocFromAlias', function() { expect(getDocFromAlias('c')).toEqual([doc1]); }); - it('should return docs that match the alias and originating doc\'s area', function() { - var doc1 = {aliases: ['a'], area: 'api'}; - var doc2 = {aliases: ['a'], area: 'api'}; - var doc3 = {aliases: ['a'], area: 'other'}; + it('should filter ambiguous docs by calling each disambiguator', () => { + getDocFromAlias.disambiguators = [ + (alias, originatingDoc, docs) => docs.filter(doc => doc.name.indexOf('X') !== -1), // only if X appears in name + (alias, originatingDoc, docs) => docs.filter(doc => doc.name.indexOf('Y') !== -1) // only if Y appears in name + ]; + + var doc1 = {name: 'X', aliases: ['a', 'b', 'c']}; + var doc2 = {name: 'Y', aliases: ['a', 'b']}; + var doc3 = {name: 'XY', aliases: ['a', 'c']}; aliasMap.addDoc(doc1); aliasMap.addDoc(doc2); aliasMap.addDoc(doc3); - expect(getDocFromAlias('a', {area: 'api'})).toEqual([doc1, doc2]); - }); - - it('should return docs that match the alias and originating doc\'s area and module', function() { - var doc1 = {aliases: ['a'], area: 'api', module: 'ng'}; - var doc2 = {aliases: ['a'], area: 'api', module: 'ngMock'}; - var doc3 = {aliases: ['a'], area: 'other', module: 'ng'}; - aliasMap.addDoc(doc1); - aliasMap.addDoc(doc2); - aliasMap.addDoc(doc3); - - expect(getDocFromAlias('a', {area: 'api', module: 'ng'})).toEqual([doc1]); + // doc1 and doc2 get removed as they don't both have X and Y in name + expect(getDocFromAlias('a')).toEqual([doc3]); + // doc2 gets removed as it has no X; then we have only one doc left so second disambiguator never runs + expect(getDocFromAlias('b')).toEqual([doc1]); + // doc1 gets removed as it has no Y; then we have only one doc left (which would anyway pass 2nd disambiguator) + expect(getDocFromAlias('c')).toEqual([doc3]); }); }); \ No newline at end of file diff --git a/aio/tools/transforms/links-package/services/getLinkInfo.js b/aio/tools/transforms/links-package/services/getLinkInfo.js index fd6b57dab0..7b9eb61237 100644 --- a/aio/tools/transforms/links-package/services/getLinkInfo.js +++ b/aio/tools/transforms/links-package/services/getLinkInfo.js @@ -10,14 +10,9 @@ var path = require('canonical-path'); * @return {Object} The link information * * @property {boolean} relativeLinks Whether we expect the links to be relative to the originating doc - * @property {array 1) { linkInfo.valid = false; linkInfo.errorType = 'ambiguous'; @@ -80,5 +70,4 @@ module.exports = function getLinkInfo(getDocFromAlias, encodeCodeBlock, log) { return linkInfo; } - }; diff --git a/aio/tools/transforms/links-package/services/moduleScopeLinkDisambiguator.js b/aio/tools/transforms/links-package/services/moduleScopeLinkDisambiguator.js deleted file mode 100644 index 08435b96ee..0000000000 --- a/aio/tools/transforms/links-package/services/moduleScopeLinkDisambiguator.js +++ /dev/null @@ -1,15 +0,0 @@ -var _ = require('lodash'); - -module.exports = function moduleScopeLinkDisambiguator() { - return function(url, title, currentDoc, docs) { - if (docs.length > 1) { - // filter out target docs that are not in the same module as the source doc - var filteredDocs = - _.filter(docs, function(doc) { return doc.moduleDoc === currentDoc.moduleDoc; }); - // if all target docs are in a different module then just return the full collection of - // ambiguous docs - return filteredDocs.length > 0 ? filteredDocs : docs; - } - return docs; - }; -}; From 0e311e39180ea37afbdde660bd4eb242056cd589 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 28 Feb 2018 14:52:53 +0000 Subject: [PATCH 025/604] build(aio): improve accuracy of code auto-linking (#22494) The new version of `dgeni-packages/typescript` no longer strips out "namespaces" from types, which was part of the problem of not autolinking correctly to `HttpEventType.Response`. Another part of the problem was that we did not include `.` characters when matching potential code blocks for auto-linking, which precluded properties of enums from being linked. Finally, members we not being given a `path` property, which is needed to effectively autolink to them. This is now set in the `simplifyMemberAnchors` processor. Closes #21375 PR Close #22494 --- aio/package.json | 2 +- .../transforms/angular-api-package/index.js | 2 +- .../processors/simplifyMemberAnchors.js | 14 ++-- .../processors/simplifyMemberAnchors.spec.js | 72 +++++++++++++++++++ .../post-processors/auto-link-code.js | 2 +- .../post-processors/auto-link-code.spec.js | 9 ++- aio/yarn.lock | 6 +- 7 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 aio/tools/transforms/angular-api-package/processors/simplifyMemberAnchors.spec.js diff --git a/aio/package.json b/aio/package.json index 4c7112df31..a9d0d17e50 100644 --- a/aio/package.json +++ b/aio/package.json @@ -107,7 +107,7 @@ "cross-spawn": "^5.1.0", "css-selector-parser": "^1.3.0", "dgeni": "^0.4.7", - "dgeni-packages": "^0.24.3", + "dgeni-packages": "^0.25.0", "entities": "^1.1.1", "eslint": "^3.19.0", "eslint-plugin-jasmine": "^2.2.0", diff --git a/aio/tools/transforms/angular-api-package/index.js b/aio/tools/transforms/angular-api-package/index.js index b4728d01ce..16c81e7c1e 100644 --- a/aio/tools/transforms/angular-api-package/index.js +++ b/aio/tools/transforms/angular-api-package/index.js @@ -130,6 +130,6 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage]) ]); convertToJsonProcessor.docTypes = convertToJsonProcessor.docTypes.concat(DOCS_TO_CONVERT); postProcessHtml.docTypes = convertToJsonProcessor.docTypes.concat(DOCS_TO_CONVERT); - autoLinkCode.docTypes = DOCS_TO_CONVERT; + autoLinkCode.docTypes = DOCS_TO_CONVERT.concat(['member']); autoLinkCode.codeElements = ['code', 'code-example', 'code-pane']; }); diff --git a/aio/tools/transforms/angular-api-package/processors/simplifyMemberAnchors.js b/aio/tools/transforms/angular-api-package/processors/simplifyMemberAnchors.js index 2519d22870..f36a8d4809 100644 --- a/aio/tools/transforms/angular-api-package/processors/simplifyMemberAnchors.js +++ b/aio/tools/transforms/angular-api-package/processors/simplifyMemberAnchors.js @@ -5,15 +5,21 @@ */ module.exports = function simplifyMemberAnchors() { return { - $runAfter: ['extra-docs-added'], - $runBefore: ['computing-paths'], + $runAfter: ['paths-computed'], + $runBefore: ['rendering-docs'], $process: function(docs) { return docs.forEach(doc => { if (doc.members) { - doc.members.forEach(member => member.anchor = computeAnchor(member)); + doc.members.forEach(member => { + member.anchor = computeAnchor(member); + member.path = doc.path + '#' + member.anchor; + }); } if (doc.statics) { - doc.statics.forEach(member => member.anchor = computeAnchor(member)); + doc.statics.forEach(member => { + member.anchor = computeAnchor(member); + member.path = doc.path + '#' + member.anchor; + }); } }); } diff --git a/aio/tools/transforms/angular-api-package/processors/simplifyMemberAnchors.spec.js b/aio/tools/transforms/angular-api-package/processors/simplifyMemberAnchors.spec.js new file mode 100644 index 0000000000..b857af196d --- /dev/null +++ b/aio/tools/transforms/angular-api-package/processors/simplifyMemberAnchors.spec.js @@ -0,0 +1,72 @@ +const testPackage = require('../../helpers/test-package'); +const processorFactory = require('./simplifyMemberAnchors'); +const Dgeni = require('dgeni'); + +describe('simplifyMemberAnchors processor', () => { + + it('should be available on the injector', () => { + const dgeni = new Dgeni([testPackage('angular-api-package')]); + const injector = dgeni.configureInjector(); + const processor = injector.get('simplifyMemberAnchors'); + expect(processor.$process).toBeDefined(); + expect(processor.$runAfter).toEqual(['paths-computed']); + expect(processor.$runBefore).toEqual(['rendering-docs']); + }); + + describe('$process', () => { + describe('docs without members', () => { + it('should ignore the docs', () => { + const processor = processorFactory(); + const docs = [ + { id: 'some-doc' }, + { id: 'some-other' } + ]; + processor.$process(docs); + expect(docs).toEqual([ + { id: 'some-doc' }, + { id: 'some-other' } + ]); + }); + }); + + describe('docs with members', () => { + it('should compute an anchor for each instance member', () => { + const processor = processorFactory(); + const docs = [ + { id: 'some-doc', members: [ { name: 'foo' }, { name: 'new' }, { name: '' } ] } + ]; + processor.$process(docs); + expect(docs[0].members.map(member => member.anchor)).toEqual(['foo', 'new', 'call']); + }); + + it('should compute a path for each instance member', () => { + const processor = processorFactory(); + const docs = [ + { id: 'some-doc', path: 'a/b/c', members: [ { name: 'foo' }, { name: 'new' }, { name: '' } ] } + ]; + processor.$process(docs); + expect(docs[0].members.map(member => member.path)).toEqual(['a/b/c#foo', 'a/b/c#new', 'a/b/c#call']); + }); + }); + + describe('docs with static members', () => { + it('should compute an anchor for each static member', () => { + const processor = processorFactory(); + const docs = [ + { id: 'some-doc', statics: [ { name: 'foo' }, { name: 'new' }, { name: '' } ] } + ]; + processor.$process(docs); + expect(docs[0].statics.map(member => member.anchor)).toEqual(['foo', 'new', 'call']); + }); + + it('should compute a path for each static member', () => { + const processor = processorFactory(); + const docs = [ + { id: 'some-doc', path: 'a/b/c', statics: [ { name: 'foo' }, { name: 'new' }, { name: '' } ] } + ]; + processor.$process(docs); + expect(docs[0].statics.map(member => member.path)).toEqual(['a/b/c#foo', 'a/b/c#new', 'a/b/c#call']); + }); + }); + }); +}); diff --git a/aio/tools/transforms/angular-base-package/post-processors/auto-link-code.js b/aio/tools/transforms/angular-base-package/post-processors/auto-link-code.js index baf114aaaa..fae9ba475e 100644 --- a/aio/tools/transforms/angular-base-package/post-processors/auto-link-code.js +++ b/aio/tools/transforms/angular-base-package/post-processors/auto-link-code.js @@ -45,7 +45,7 @@ module.exports = function autoLinkCode(getDocFromAlias) { parent.children.splice(index, 1, createLinkNode(docs[0], node.value)); } else { // Parse the text for words that we can convert to links - const nodes = textContent(node).split(/([A-Za-z0-9_-]+)/) + const nodes = textContent(node).split(/([A-Za-z0-9_.-]+)/) .filter(word => word.length) .map((word, index, words) => { // remove docs that fail the custom filter tests diff --git a/aio/tools/transforms/angular-base-package/post-processors/auto-link-code.spec.js b/aio/tools/transforms/angular-base-package/post-processors/auto-link-code.spec.js index c37b2165df..f300487aed 100644 --- a/aio/tools/transforms/angular-base-package/post-processors/auto-link-code.spec.js +++ b/aio/tools/transforms/angular-base-package/post-processors/auto-link-code.spec.js @@ -9,7 +9,7 @@ describe('autoLinkCode post-processor', () => { const dgeni = new Dgeni([testPackage]); const injector = dgeni.configureInjector(); autoLinkCode = injector.get('autoLinkCode'); - autoLinkCode.docTypes = ['class', 'pipe', 'function', 'const']; + autoLinkCode.docTypes = ['class', 'pipe', 'function', 'const', 'member']; aliasMap = injector.get('aliasMap'); processor = injector.get('postProcessHtml'); processor.docTypes = ['test-doc']; @@ -31,6 +31,13 @@ describe('autoLinkCode post-processor', () => { expect(doc.renderedContent).toEqual('foo.MyClass'); }); + it('should match code items within a block of code that contain a dot in their identifier', () => { + aliasMap.addDoc({ docType: 'member', id: 'MyEnum.Value', aliases: ['Value', 'MyEnum.Value'], path: 'a/b/myenum' }); + const doc = { docType: 'test-doc', renderedContent: 'someFn(): MyEnum.Value' }; + processor.$process([doc]); + expect(doc.renderedContent).toEqual('someFn(): MyEnum.Value'); + }); + it('should ignore code items that do not match a link to an API doc', () => { aliasMap.addDoc({ docType: 'guide', id: 'MyClass', aliases: ['MyClass'], path: 'a/b/myclass' }); const doc = { docType: 'test-doc', renderedContent: 'MyClass' }; diff --git a/aio/yarn.lock b/aio/yarn.lock index a2287ddccb..7545c27601 100644 --- a/aio/yarn.lock +++ b/aio/yarn.lock @@ -2312,9 +2312,9 @@ devtools-timeline-model@1.1.6: chrome-devtools-frontend "1.0.401423" resolve "1.1.7" -dgeni-packages@^0.24.3: - version "0.24.3" - resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.24.3.tgz#153ce2bf9d71a70cd8c71171c7f49adf0d725ab9" +dgeni-packages@^0.25.0: + version "0.25.0" + resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.25.0.tgz#380f0b569ae36d82959252604b729e85e0cd7d4a" dependencies: canonical-path "0.0.2" catharsis "^0.8.1" From 8bb2f5c71d5d2b917abbccdf87a92686ebde22df Mon Sep 17 00:00:00 2001 From: Chuck Jazdzewski Date: Tue, 27 Feb 2018 10:15:23 -0800 Subject: [PATCH 026/604] docs(compiler): ivy separate compilation design document (#22480) PR Close #22480 --- .../compiler/design/separate_compilation.md | 835 ++++++++++++++++++ 1 file changed, 835 insertions(+) create mode 100644 packages/compiler/design/separate_compilation.md diff --git a/packages/compiler/design/separate_compilation.md b/packages/compiler/design/separate_compilation.md new file mode 100644 index 0000000000..29e81694a4 --- /dev/null +++ b/packages/compiler/design/separate_compilation.md @@ -0,0 +1,835 @@ +# DESIGN DOC (Ivy): Separate Compilation + +AUTHOR: chuckj@ + +## Background + +### Angular 5 (Renderer2) + +In 5.0 and prior versions of Angular the compiler performs whole program +analysis and generates template and injector definitions that are using +this global knowledge to flatten injector scope definitions, inline +directives into the component, pre-calculate queries, pre-calculate +content projection, etc. This global knowledge requires that module and +component factories are generated as a final global step when compiling +a module. If any of the transitive information changed then all factories +need to be regenerated. + +Separate component and module compilation is supported only at the module +definition level and only from source. That is, npm packages must contain +the metadata necessary to generate the factories, they cannot contain, +themselves, the generated factories. This is because, if any of there +dependencies change, their factories would be invalid preventing them from +using version ranges in their dependencies. To support producing factories +from compiled source (already translated by TypeScript into JavaScript) +libraries include metadata that describe the content of the the Angular +decorators. + +This document refers to this style of code generation as Renderer2 after the +name of the renderer class it uses at runtime. + +### Angular Ivy + +In Ivy, the runtime is crafted in a way that allows for separate compilation +by performing at runtime much of what was previously pre-calculated by +compiler. This allows the definition of components to change without +requiring modules and components that depend on them being recompiled. + +The mental model of Ivy is that the decorator is the compiler. That is +the decorator can be thought of as parameters to a class transformer that +transforms the class by generating definitions based on the decorator +parameters. An `@Component` decorator transforms the class by adding +an `ngComponentDef` static property, `@Directive` adds `ngDirectiveDef`, +`@Pipe` adds `ngPipeDef`, etc. In most cases values supplied to the +decorator is sufficient to generate the definition. However, in the case of +interpreting the template, the compiler needs to know the selector defined for +each component, directive and pipe that are in scope of the template. The +purpose of this document is to define the information is needed by the +compiler and how that information is is serialized to be discovered and +used by subsequent calls to `ngc`. + +This document refers to this style of code generation as ivy after the code +name of the project to create it. It would be more consistent to refer to it +as Renderer3 that looks too similar to Renderer2. + +## Information needed + +The information available across compilations in Angular 5 is represented in +the compiler by a summary description. For example, components and directives +are represented by the [`CompileDirectiveSummary`](https://github.com/angular/angular/blob/d3827a0017fd5ff5ac0f6de8a19692ce47bf91b4/packages/compiler/src/compile_metadata.ts#L257). +The following table shows where this information ends up in an ivy compiled +class: + +### `CompileDirectiveSummary` + +| field | destination | +|---------------------|-----------------------| +| `type` | implicit | +| `isComponent` | `ngComponentDef` | +| `selector` | `ngModuleScope` | +| `exportAs` | `ngDirectiveDef` | +| `inputs` | `ngDirectiveDef` | +| `outputs` | `ngDirectiveDef` | +| `hostListeners` | `ngDirectiveDef` | +| `hostProperties` | `ngDirectiveDef` | +| `hostAttributes` | `ngDirectiveDef` | +| `providers` | `ngInjectorDef` | +| `viewProviders` | `ngComponentDef` | +| `queries` | `ngDirectiveDef` | +| `guards` | not used | +| `viewQueries` | `ngComponentDef` | +| `entryComponents` | not used | +| `changeDetection` | `ngComponentDef` | +| `template` | `ngComponentDef` | +| `componentViewType` | not used | +| `renderType` | not used | +| `componentFactory` | not used | + +Only one definition is generated per class. All components are directives so a +`ngComponentDef` contains all the `ngDirectiveDef` information. All directives +are injectable so `ngComponentDef` and `ngDirectiveDef` contain `ngInjectableDef` +information. + +For `CompilePipeSummary` the table looks like: + +#### `CompilePipeSummary` + +| field | destination | +|---------------------|-----------------------| +| `type` | implicit | +| `name` | `ngModuleScope` | +| `pure` | `ngPipeDef` | + +The only pieces of information that are not generated into the definition are +the directive selector and the pipe name as they go into the module scope. + +The information needed to build an `ngModuleScope` needs to be communicated +from the directive and pipe to the module that declares them. + +## Metadata + +### Angular 5 + +Angular 5 uses `.metadata.json` files to store information that is directly +inferred from the `.ts` files and include value information that is not +included in the `.d.ts` file produced by TypeScript. Because only exports for +types are included in `.d.ts` files and might not include the exports necessary +for values, the metadata includes export clauses from the `.ts` file. + +When a module is flattened into a FESM (Flat ECMAScript Module), a flat +metadata file is also produced which is the metadata for all symbols exported +from the module index. The metadata represents what the `.metadata.json` file +would look like if all the symbols were declared in the index instead of +reexported from the index. + +### Angular Ivy + +The metadata for a class in ivy is transformed to be what the metadata of the +transformed .js file produced by the ivy compiler would be. For example, a +component's `@Component` is removed by the compiler and replaced by a `ngComponentDef`. +The `.metadata.json` file is similarly transformed but the content of the +value assigned is elided (e.g. `"ngComponentDef": {}`). The compiler doesn't +record the selector declared for a component but it is needed to produce the +`ngModuleScope` so the information is recorded as if a static field +`ngSelector` was declared on class with the value of the `selector` field +from the `@Component` or `@Directive` decorator. + +The following transformations are performed: + +#### `@Component` + +The metadata for a component is transformed by: + +1. Removing the `@Component` directive. +2. Add `"ngComponentDef": {}` static field. +3. Add `"ngSelector": ` static field. + +##### Example + +*my.component.ts* +```ts +@Component({ + selector: 'my-comp', + template: `

Hello, {{name}}!

` +}) +export class MyComponent { + @Input() name: string; +} +``` + +*my.component.js* +```js +export class MyComponent { + name: string; + static ngComponentDef = defineComponent({...}); +} +``` + +*my.component.metadata.json* +```json +{ + "__symbolic": "module", + "version": 4, + "metadata": { + "MyComponent": { + "__symbolic": "class", + "statics": { + "ngComponentDef": {}, + "ngSelector": "my-comp" + } + } + } +} +``` + +Note that this is exactly what is produced if the transform had been done +manually or by some other compiler before `ngc` compiler is invoked. That is +this model has the advantage that there is no magic introduced by the compiler +as it treats classes annotated by `@Component` identically to those produced +manually. + +#### `@Directive` + +The metadata for a directive is transformed by: + +1. Removing the `@Directive` directive. +2. Add `"ngDirectiveDef": {}` static field. +3. Add `"ngSelector": ` static field. + +##### example + +*my.directive.ts* + +```ts +@Directive({selector: '[my-dir]'}) +export class MyDirective { + @HostBinding('id') dirId = 'some id'; +} +``` + +*my.directive.js* +```js +export class MyDirective { + constructor() { + this.dirId = 'some id'; + } + static ngDirectiveDef = defineDirective({...}); +} +``` + +*my.directive.metadata.json* +```json +{ + "__symbolic": "module", + "version": 4, + "metadata": { + "MyDirective": { + "__symbolic": "class", + "statics": { + "ngDirectiveDef": {}, + "ngSelector": "[my-dir]" + } + } + } +} +``` + +#### `@Pipe` + +The metadata for a pipe is transformed by: + +1. Removing the `@Pipe` directive. +2. Add `"ngPipeDef": {}` static field. +3. Add `"ngSelector": ` static field. + +##### example + +*my.pipe.ts* +```ts +@Pipe({name: 'myPipe'}) +export class MyPipe implements PipeTransform { + transform(...) ... +} +``` + +*my.pipe.js* +```js +export class MyPipe { + transform(...) ... + static ngPipeDef = definePipe({...}); +} +``` + +*my.pipe.metadata.json* +```json +{ + "__symbolic": "module", + "version": 4, + "metadata": { + "MyPipe": { + "__symbolic": "class", + "statics": { + "ngPipeDef": {}, + "ngSelector": "myPipe" + } + } + } +} +``` + +#### `@NgModule` + +The metadata for a module is transformed by: + +1. Remove the `@NgModule` directive. +2. Add `"ngInjectorDef": {}` static field. +3. Add `"ngModuleScope": ` static field. + +The scope value is an array the following type: + +```ts +export type ModuleScope = ModuleScopeEntry[]; + +export interface ModuleDirectiveEntry { + type: Type; + selector: string; +} + +export interface ModulePipeEntry { + type: Type; + name: string; + isPipe: true; +} + +export interface ModuleExportEntry { + type: Type; + isModule: true; +} + +type ModuleScopeEntry = ModuleDirectiveEntry | ModulePipeEntry | ModuleExportEntry; +``` + +where the `type` values are generated as references. + +##### example + +*my.module.ts* +```ts +@NgModule({ + imports: [CommonModule, UtilityModule], + declarations: [MyComponent, MyDirective, MyComponent], + exports: [MyComponent, MyDirective, MyPipe, UtilityModule], + providers: [{ + provide: Service, useClass: ServiceImpl + }] +}) +export class MyModule {} +``` + +*my.module.js* +```js +export class MyModule { + static ngInjectorDef = defineInjector(...); +} +``` + +*my.module.metadata.json* +```json +{ + "__symbolic": "module", + "version": 4, + "metadata": { + "MyModule": { + "__symbolic": "class", + "statics": { + "ngInjectorDef": {}, + "ngModuleScope": [ + { + "type": { + "__symbolic": "reference", + "module": "./my.component", + "name": "MyComponent" + }, + "selector": "my-comp" + }, + { + "type": { + "__symbolic": "reference", + "module": "./my.directive", + "name": "MyDirective" + }, + "selector": "[my-dir]" + }, + { + "type": { + "__symbolic": "reference", + "module": "./my.pipe", + "name": "MyPipe" + }, + "name": "myPipe", + "isPipe": true + }, + { + "type": { + "__symbolic": "reference", + "module": "./utility.module", + "name": "UtilityModule" + }, + "isModule": true + } + ] + } + } + } +} +``` + +Note that this is identical to what would have been generated if the this was +manually written as: + +```ts +export class MyModule { + static ngInjectorDef = defineInjector({ + providers: [{ + provide: Service, useClass: ServiceImpl + }], + imports: [CommonModule, UtilityModule] + }); + static ngModuleScope = [{ + type: MyComponent, + selector: 'my-comp' + }, { + type: MyDirective, + selector: '[my-dir]' + }, { + type: MyPipe, + name: 'myPipe' + }, { + type: UtilityModule, + isModule: true + }]; +} +``` + +except for the call to `defineInjector` would generate a `{ __symbolic: 'error' }` +value which is ignored by the ivy compiler. This allows the system to ignore +the difference between manually and mechanically created module definitions. + + +## Manual Considerations + +With this proposal, the compiler treats manually and mechanically generated +Angular definitions identically. This allows flexibility not only in the future +for how the declarations are mechanically produced it also allows alternative +mechanism to generate declarations be easily explored without altering the +compiler or dependent tool chain. It also allows third-party code generators +with possibly different component syntaxes to generate a component fully +understood by the compiler. + +Unfortunately, however, manually generated modules contain references to +classes that might not be necessary at runtime. Manually or third-party +components can get the same payload properties of an Angular generated +component by annotating the `ngSelector` and `ngModuleScope` properties with +`// @__BUILD_OPTIMIZER_REMOVE_` comment which will cause the build optimizer +to remove the declaration. + +##### example + +For example the above manually created module would have better payload +properties by including a `// @__BUILD_OPTIMIZER_REMOVE_` comment: + +```ts +export class MyModule { + static ngInjectorDef = defineInjector({ + providers: [{ + provide: Service, useClass: ServiceImpl + }], + imports: [CommonModule, UtilityModule] + }); + + // @__BUILD_OPTIMIZER_REMOVE_ + static ngModuleScope = [{ + type: MyComponent, + selector: 'my-comp' + }, { + type: MyDirective, + selector: '[my-dir]' + }, { + type: MyPipe, + name: 'myPipe' + }, { + type: UtilityModule, + isModule: true + }]; +} +``` + +## `ngc` output (non-Bazel) + +The cases that `ngc` handle are producing an application and producing a +reusable library used in an application. + +### Application output + +The output of the ivy compiler only optionally generates the factories +generated by the Renderer2 style output of Angular 5.0. In ivy, the information +that was generated in factories is now generated in Angular as a definition +that is generated as a static field on the Angular decorated class. + +Renderer2 requires that, when building the final application, all factories for +all libraries also be generated. In ivy, the definitions are generated when +the library is compiled. + +The ivy compile can adapt Renderer2 target libraries by generating the factories +for them and back-patching, at runtime, the static property into the class. + +#### Back-patching module (`"renderer2BackPatching"`) + +When an application contains Renderer2 target libraries the ivy definitions +need to be back-patch onto the component, directive, module, pipe, and +injectable classes. + +If the Angular compiler option `"renderer2BackPatching"` is enabled, the +compiler will generate an `angular.back-patch` module in the to root output +directory of the project. If `"generateRenderer2Factories"` is set to `true` +then the default value for `"renderer2BackPatching"` is `true` and it is and +error for it to be `false`. `"renderer2BackPatching"` is ignored if `"enableIvy"` +is `false`. + +`angular.back-patch` exports a function per `@NgModule` for the entire +application, including previously compiled libraries. The name of the function +is determined by name of the imported module with all non alphanumeric +character, including '`/`' and '`.`', replaced by '`_`'. + +The back-patch functions will call the back-patch function of any module they +import. This means that only the application's module and lazy loaded modules +back-patching functions needs to be called. If using the Renderer2 module factory +instances, this is performed automatically when the first application module +instance is created. + +#### Renderer2 Factories (`"generateRenderer2Factories"`) + +`ngc` can generate an implementation of `NgModuleFactory` in the same location +that Angular 5.0 would generate it. This implementation of `NgModuleFactory` +will back-patch the Renderer2 style classes when the first module instance is +created by calling the correct back-patching function generated in the`angular.back-patch` +module. + +Renderer2 style factories are created when the `"generateRenderer2Factories"` +Angular compiler option is `true`. Setting `"generateRenderer2Factories"` implies +`"renderer2BackPatching"` is also `true` and it is an error to explicitly set it +to `false`. `"generateRenderer2Factories"` is ignored if `"enableIvy"` is +`false`. + +When this option is `true` a factory module is created with the same public API +at the same location as Angular 5.0 whenever Angular 5.0 would have generated a +factory. + +### Recommended options + +The recommended options for producing a ivy application are + +| option | value | | +|--------------------------------|----------|-------------| +| `"enableIvy"` | `true` | required | +| `"generateRenderer2Factories"` | `true` | implied | +| `"renderer2BackPatching"` | `true` | implied | +| `"generateCodeForLibraries"` | `true` | default | +| `"annotationsAs"` | `remove` | implied | +| `"enableLegacyTemplate"` | `false` | default | +| `"preserveWhitespaces"` | `false` | default | +| `"skipMetadataEmit"` | `true` | default | +| `"strictMetadataEmit"` | `false` | implied | +| `"skipTemplateCodegen"` | | ignored | + +The options marked "implied" are implied by other options having the +recommended value and do not need to be explicitly set. Options marked +"default" also do not need to be set explicitly. + +## Library output + +Building an ivy library with `ngc` differs from Renderer2 in that the +declarations are included in the generated output and should be included in the +package published to `npm`. The `.metadata.json` files still need to be +included but they are transformed as described below. + +### Transforming metadata + +As described above, when the compiler adds the declaration to the class it will +also transform the `.metadata.json` file to reflect the new static fields added +to the class. + +Once the static fields are added to the metadata, the ivy compiler no longer +needs the the information in the decorator. When `"enableIvy"` is `true` this +information is removed from the `.metadata.json` file. + +### Recommended options + +The recommended options for producing a ivy library are: + +| option | value | | +|--------------------------------|----------|-------------| +| `"enableIvy"` | `true` | required | +| `"generateRenderer2Factories"` | `false` | | +| `"renderer2BackPatching"` | `false` | default | +| `"generateCodeForLibraries"` | `false` | | +| `"annotationsAs"` | `remove` | implied | +| `"enableLegacyTemplate"` | `false` | default | +| `"preserveWhitespaces"` | `false` | default | +| `"skipMetadataEmit"` | `false` | | +| `"strictMetadataEmit"` | `true ` | | +| `"skipTemplateCodegen"` | | ignored | + +The options marked "implied" are implied by other options having the +recommended value and do not need to be explicitly set. Options marked +"default" also do not need to be set explicitly. + +## Simplified options + +The default Angular Compiler options default to, mostly, the recommended set of +options but the options necessary to set for specific targets are not clear and +mixing them can produce nonsensical results. The `"target"` option can be used +to simplify the setting of the compiler options to the recommended values +depending on the target: + +| target | option | value | | +|-------------------|--------------------------------|--------------|-------------| +| `"application"` | `"generateRenderer2Factories"` | `true` | enforced | +| | `"renderer2BackPatching"` | `true` | enforced | +| | `"generateCodeForLibraries"` | `true` | | +| | `"annotationsAs"` | `remove` | | +| | `"enableLegacyTemplate"` | `false` | | +| | `"preserveWhitespaces"` | `false` | | +| | `"skipMetadataEmit"` | `false` | | +| | `"strictMetadataEmit"` | `true` | | +| | `"skipTemplateCodegen"` | `false` | | +| | `"fullTemplateTypeCheck"` | `true` | | +| | `"enableLegacyTemplate"` | `false` | | +| | | | | +| `"library"` | `"generateRenderer2Factories"` | `false` | enforced | +| | `"renderer2BackPatching"` | `false` | enforced | +| | `"generateCodeForLibraries"` | `false` | enforced | +| | `"annotationsAs"` | `decorators` | | +| | `"enableLegacyTemplate"` | `false` | | +| | `"preserveWhitespaces"` | `false` | | +| | `"skipMetadataEmit"` | `false` | enforced | +| | `"strictMetadataEmit"` | `true` | | +| | `"skipTemplateCodegen"` | `false` | enforced | +| | `"fullTemplateTypeCheck"` | `true` | | +| | `"enableLegacyTemplate"` | `false` | | +| | | | | +| `"package"` | `"flatModuleOutFile"` | | required | +| | `"flatModuleId"` | | required | +| | `"enableIvy"` | `false` | enforced | +| | `"generateRenderer2Factories"` | `false` | enforced | +| | `"renderer2BackPatching"` | `false` | enforced | +| | `"generateCodeForLibraries"` | `false` | enforced | +| | `"annotationsAs"` | `remove` | | +| | `"enableLegacyTemplate"` | `false` | | +| | `"preserveWhitespaces"` | `false` | | +| | `"skipMetadataEmit"` | `false` | enforced | +| | `"strictMetadataEmit"` | `true` | | +| | `"skipTemplateCodegen"` | `false` | enforced | +| | `"fullTemplateTypeCheck"` | `true` | | +| | `"enableLegacyTemplate"` | `false` | | + +Options that are marked "enforced" are reported as an error if they are +explicitly set to a value different from what is specified here. The options +marked "required" are required to be set and an error message is displayed if +no value is supplied but no default is provided. + +The purpose of the "application" target is for the options used when the `ngc` +invocation contains the root application module. Lazy loaded modules should +also be considered "application" targets. + +The purpose of the "library" target is for are all `ngc` invocations that do +not contain the root application module or a lazy loaded module. + +The purpose of the "package" target is to produce a library package that will +be an entry point for an npm package. Each entry point should be separately +compiled using a "package" target. + +##### example - application + +To produce a Renderer2 application the options would look like, + +```json +{ + "compileOptions": { + ... + }, + "angularCompilerOptions": { + "target": "application" + } +} +``` + +alternately, since the recommended `"application"` options are the default +values, the `"angularCompilerOptions"` can be out. + +##### example - library + +To produce a Renderer2 library the options would look like, + +```json +{ + "compileOptions": { + ... + }, + "angularCompilerOptions": { + "target": "library" + } +} +``` + +##### example - package + +To produce a Renderer2 package the options would look like, + +```json +{ + "compileOptions": { + ... + }, + "angularCompilerOptions": { + "target": "package" + } +} +``` + +##### example - ivy application + +To produce an ivy application the options would look like, + +```json +{ + "compileOptions": { + ... + }, + "angularCompilerOptions": { + "target": "application", + "enableIvy": true + } +} +``` + +##### example - ivy library + +To produce an ivy application the options would look like, + +```json +{ + "compileOptions": { + ... + }, + "angularCompilerOptions": { + "target": "library", + "enableIvy": true + } +} +``` + +##### example - ivy package + +Ivy packages are not supported in Angular 6.0 as they are not recommended in +npm packages as they would only be usable if in ivy application where an ivy +application. Ivy application support Renderer2 libraries so npm packages +should all be Renderer2 libraries. + +## `ng_module` output (Bazel) + +The `ng_module` rule describes the source necessary to produce a Angular +library that is reusable and composable into an application. + +### Angular 5.0 + +The `ng_module` rule invokes `ngc`[1](#ngc_wrapped) to produce +the Angular output. However, `ng_module` uses a feature, the `.ngsummary.json` +file, not normally used and is often difficult to configure correctly. + +The `.ngsummary.json` describes all the information that is necessary for +the compiler to use a generated factory. It is produced by actions defined +in the `ng_module` rule and is consumed by actions defined by `ng_module` +rules that depend on other `ng_module` rules. + +### Angular Ivy + +The `ng_module` rule will still use `ngc` to produce the Angular output but, +when producing ivy output, it no longer will need the `.ngsummary.json` file. + +#### `ng_experimental_ivy_srcs` + +The `ng_experimental_ivy_srcs` can be used as use to cause the ivy versions of +files to be generated. It is intended the sole dependency of a `ts_dev_server` +rule and the `ts_dev_server` sources move to `ng_experimental_iv_srcs`. + +#### `ng_module` ivy output + +The `ng_module` is able to provide the ivy version of the `.js` files which +will be generated with as `.ivy.js` for the development sources and `.ivy.closure.js` +for the production sources. + +The `ng_module` rule will also generate a `angular.back_patch.js` and `.closure.js` +files and a `module_scope.json` file. The type of the `module_scope.json` file will +be: + +```ts +interface ModuleScopeSummary { + [moduleName: string]: ModuleScopeEntry[]; +} +``` + +where `moduleName` is the name of the as it would appear in an import statement +in a `.ts` file at the same relative location in the source tree. All the +references in this file are also relative this location. + +##### example + +The following is a typical Angular application build in bazel: + +*src/BUILD.bazel* +```py +ng_module( + name = "src", + srcs = glob(["*.ts"]), + deps= ["//common/component"], +) + +ts_dev_server( + name = "server", + srcs = ":src", +) +``` + +To use produce an ivy version you would add: + +```py +ng_experimental_ivy_srcs( + name = "ivy_srcs", + srcs = ":src", +) + +ts_dev_server( + name = "server_ivy", + srcs = [":ivy_srcs"] +) +``` + +To serve the Renderer2 version, you would run: + +```sh +bazel run :server +``` + +to serve the ivy version you would run + +```sh +bazel run :server_ivy +``` + +The `ng_experimental_ivy_srcs` rule is only needed when ivy is experimental. Once ivy +is released the `ng_experimental_ivy_srcs`, dependent rules, can be removed. + +--- +1 More correctly, it calls `performCompilation` +from the `@angular/compiler-cli` which is what `ngc` does too. From dd534471ec2b7b0b84e1185d9d27c6a077b7533b Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Thu, 22 Feb 2018 10:29:01 -0800 Subject: [PATCH 027/604] fix(compiler): allow tree-shakeable injectables to depend on string tokens (#22376) Previously the injectable compiler assumed all tree-shakeable injectables would have dependencies that were injectables or InjectionTokens. However old code still uses string tokens (e.g. NgUpgrade and '$injector'). Using such tokens would cause the injectable compiler to crash. Now, the injectable compiler can properly generate a dependency on such a string token. PR Close #22376 --- .../bazel/injectable_def/app/src/string.ts | 38 +++++++++++++++++++ .../bazel/injectable_def/app/test/app_spec.ts | 11 ++++++ packages/compiler/src/injectable_compiler.ts | 5 ++- 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/string.ts diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/string.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/string.ts new file mode 100644 index 0000000000..91cc13ca7b --- /dev/null +++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/src/string.ts @@ -0,0 +1,38 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Component, Inject, Injectable, NgModule} from '@angular/core'; +import {BrowserModule} from '@angular/platform-browser'; +import {ServerModule} from '@angular/platform-server'; + + +@Component({ + selector: 'string-app', + template: '{{data}}', +}) +export class AppComponent { + data: string; + constructor(service: Service) { this.data = service.data; } +} + +@NgModule({ + imports: [ + BrowserModule.withServerTransition({appId: 'id-app'}), + ServerModule, + ], + declarations: [AppComponent], + bootstrap: [AppComponent], + providers: [{provide: 'someStringToken', useValue: 'works'}], +}) +export class StringAppModule { +} + +@Injectable({scope: StringAppModule}) +export class Service { + constructor(@Inject('someStringToken') readonly data: string) {} +} diff --git a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/app_spec.ts b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/app_spec.ts index 74da00c714..bfcf4b26ea 100644 --- a/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/app_spec.ts +++ b/packages/compiler-cli/integrationtest/bazel/injectable_def/app/test/app_spec.ts @@ -13,6 +13,7 @@ import {DepAppModuleNgFactory} from 'app_built/src/dep.ngfactory'; import {HierarchyAppModuleNgFactory} from 'app_built/src/hierarchy.ngfactory'; import {RootAppModuleNgFactory} from 'app_built/src/root.ngfactory'; import {SelfAppModuleNgFactory} from 'app_built/src/self.ngfactory'; +import {StringAppModuleNgFactory} from 'app_built/src/string.ngfactory'; import {TokenAppModuleNgFactory} from 'app_built/src/token.ngfactory'; enableProdMode(); @@ -77,4 +78,14 @@ describe('ngInjectableDef Bazel Integration', () => { done(); }); }); + + it('string tokens work', done => { + renderModuleFactory(StringAppModuleNgFactory, { + document: '', + url: '/', + }).then(html => { + expect(html).toMatch(/>works<\//); + done(); + }); + }); }); diff --git a/packages/compiler/src/injectable_compiler.ts b/packages/compiler/src/injectable_compiler.ts index 2ae1c7af7b..afa8ea54cf 100644 --- a/packages/compiler/src/injectable_compiler.ts +++ b/packages/compiler/src/injectable_compiler.ts @@ -55,10 +55,11 @@ export class InjectableCompiler { } } } + const tokenExpr = typeof token === 'string' ? o.literal(token) : ctx.importExpr(token); if (flags !== InjectFlags.Default || defaultValue !== undefined) { - args = [ctx.importExpr(token), o.literal(defaultValue), o.literal(flags)]; + args = [tokenExpr, o.literal(defaultValue), o.literal(flags)]; } else { - args = [ctx.importExpr(token)]; + args = [tokenExpr]; } return o.importExpr(Identifiers.inject).callFn(args); }); From ab790f3c84931976a5600e7f298cd7939d4bf646 Mon Sep 17 00:00:00 2001 From: Rado Kirov Date: Wed, 28 Feb 2018 22:52:31 -0800 Subject: [PATCH 028/604] build: Add support for bazelOptions.maxCacheSizeMb in ngc-wrapped. (#22511) PR Close #22511 --- packages/bazel/src/ngc-wrapped/index.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/bazel/src/ngc-wrapped/index.ts b/packages/bazel/src/ngc-wrapped/index.ts index 2c066ef22f..99b3e1ef12 100644 --- a/packages/bazel/src/ngc-wrapped/index.ts +++ b/packages/bazel/src/ngc-wrapped/index.ts @@ -95,6 +95,13 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true, }): {diagnostics: ng.Diagnostics, program: ng.Program} { let fileLoader: FileLoader; + if (bazelOpts.maxCacheSizeMb !== undefined) { + const maxCacheSizeBytes = bazelOpts.maxCacheSizeMb * (1 << 20); + fileCache.setMaxCacheSize(maxCacheSizeBytes); + } else { + fileCache.resetMaxCacheSize(); + } + if (inputs) { fileLoader = new CachedFileLoader(fileCache, allowNonHermeticReads); // Resolve the inputs to absolute paths to match TypeScript internals From ec445b5c73c05080b15a2fd9a07b5dae413a478b Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Thu, 1 Mar 2018 07:15:30 -0800 Subject: [PATCH 029/604] ci: speed up lint job on CircleCI (#22526) When I enabled bazel remote caching, I also switched to running buildifier and skylint from the package.json script, which builds them from head. With remote caching, we do get cache hits for these, but looking up the action inputs actually takes quite a bit of time since we have to first fetch the remote repository, then do loading and analysis, then read the inputs to determine the cache key. It's more important to keep the lint job fast, so I'm reverting that part of the change for now. We can experiment with building them from head in a less critical repo. PR Close #22526 --- .circleci/config.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a2d8f0dbf1..80335f00c3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,14 +41,14 @@ jobs: steps: - checkout: <<: *post_checkout - # See remote cache documentation in /docs/BAZEL.md - - run: .circleci/setup_cache.sh - - run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc - - *setup-bazel-remote-cache - - run: 'yarn buildifier -mode=check || - (echo -e "\nBUILD files not formatted. Please run ''yarn buildifier''" ; exit 1)' - - run: 'yarn skylint || + # Check BUILD.bazel formatting before we have a node_modules directory + # Then we don't need any exclude pattern to avoid checking those files + - run: 'buildifier -mode=check $(find . -type f \( -name BUILD.bazel -or -name BUILD \)) || + (echo "BUILD files not formatted. Please run ''yarn buildifier''" ; exit 1)' + # Run the skylark linter to check our Bazel rules + - run: 'find . -type f -name "*.bzl" | + xargs java -jar /usr/local/bin/Skylint_deploy.jar || (echo -e "\n.bzl files have lint errors. Please run ''yarn skylint''"; exit 1)' - restore_cache: From 49f074f61dc4967027e129d3e47c2d2f8fe9909f Mon Sep 17 00:00:00 2001 From: Chuck Jazdzewski Date: Wed, 14 Feb 2018 17:12:05 -0800 Subject: [PATCH 030/604] feat(ivy): support array and object literals in binding expressions (#22336) PR Close #22336 --- .../src/compiler_util/expression_converter.ts | 6 +- packages/compiler/src/constant_pool.ts | 93 +++++- packages/compiler/src/output/output_ast.ts | 85 ++++++ .../compiler/src/render3/r3_identifiers.ts | 11 + .../compiler/src/render3/r3_view_compiler.ts | 59 +++- .../render3/r3_compiler_compliance_spec.ts | 267 ++++++++++++++++++ 6 files changed, 503 insertions(+), 18 deletions(-) diff --git a/packages/compiler/src/compiler_util/expression_converter.ts b/packages/compiler/src/compiler_util/expression_converter.ts index 0b942328e7..99fe8f6570 100644 --- a/packages/compiler/src/compiler_util/expression_converter.ts +++ b/packages/compiler/src/compiler_util/expression_converter.ts @@ -97,7 +97,7 @@ export enum BindingForm { General, // Try to generate a simple binding (no temporaries or statements) - // otherise generate a general binding + // otherwise generate a general binding TrySimple, } @@ -341,7 +341,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor { } visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any { - // For literal values of null, undefined, true, or false allow type inteference + // For literal values of null, undefined, true, or false allow type interference // to infer the type. const type = ast.value === null || ast.value === undefined || ast.value === true || ast.value === true ? @@ -648,7 +648,7 @@ function convertStmtIntoExpression(stmt: o.Statement): o.Expression|null { return null; } -class BuiltinFunctionCall extends cdAst.FunctionCall { +export class BuiltinFunctionCall extends cdAst.FunctionCall { constructor(span: cdAst.ParseSpan, public args: cdAst.AST[], public converter: BuiltinConverter) { super(span, null, args); } diff --git a/packages/compiler/src/constant_pool.ts b/packages/compiler/src/constant_pool.ts index 00835f00b2..90b54d9a38 100644 --- a/packages/compiler/src/constant_pool.ts +++ b/packages/compiler/src/constant_pool.ts @@ -13,6 +13,14 @@ const CONSTANT_PREFIX = '_c'; export const enum DefinitionKind {Injector, Directive, Component, Pipe} +/** + * Context to use when producing a key. + * + * This ensures we see the constant not the reference variable when producing + * a key. + */ +const KEY_CONTEXT = {}; + /** * A node that is a place-holder that allows the node to be replaced when the actual * node is known. @@ -22,18 +30,31 @@ export const enum DefinitionKind {Injector, Directive, Component, Pipe} * change the referenced expression. */ class FixupExpression extends o.Expression { - constructor(public resolved: o.Expression) { super(resolved.type); } + private original: o.Expression; shared: boolean; + constructor(public resolved: o.Expression) { + super(resolved.type); + this.original = resolved; + } + visitExpression(visitor: o.ExpressionVisitor, context: any): any { - return this.resolved.visitExpression(visitor, context); + if (context === KEY_CONTEXT) { + // When producing a key we want to traverse the constant not the + // variable used to refer to it. + return this.original.visitExpression(visitor, context); + } else { + return this.resolved.visitExpression(visitor, context); + } } isEquivalent(e: o.Expression): boolean { return e instanceof FixupExpression && this.resolved.isEquivalent(e.resolved); } + isConstant() { return true; } + fixup(expression: o.Expression) { this.resolved = expression; this.shared = true; @@ -48,6 +69,7 @@ class FixupExpression extends o.Expression { export class ConstantPool { statements: o.Statement[] = []; private literals = new Map(); + private literalFactories = new Map(); private injectorDefinitions = new Map(); private directiveDefinitions = new Map(); private componentDefinitions = new Map(); @@ -56,6 +78,11 @@ export class ConstantPool { private nextNameIndex = 0; getConstLiteral(literal: o.Expression, forceShared?: boolean): o.Expression { + if (literal instanceof o.LiteralExpr || literal instanceof FixupExpression) { + // Do no put simple literals into the constant pool or try to produce a constant for a + // reference to a constant. + return literal; + } const key = this.keyOf(literal); let fixup = this.literals.get(key); let newValue = false; @@ -97,6 +124,54 @@ export class ConstantPool { return fixup; } + getLiteralFactory(literal: o.LiteralArrayExpr|o.LiteralMapExpr): + {literalFactory: o.Expression, literalFactoryArguments: o.Expression[]} { + // Create a pure function that builds an array of a mix of constant and variable expressions + if (literal instanceof o.LiteralArrayExpr) { + const argumentsForKey = literal.entries.map(e => e.isConstant() ? e : o.literal(null)); + const key = this.keyOf(o.literalArr(argumentsForKey)); + return this._getLiteralFactory(key, literal.entries, entries => o.literalArr(entries)); + } else { + const expressionForKey = o.literalMap( + literal.entries.map(e => ({ + key: e.key, + value: e.value.isConstant() ? e.value : o.literal(null), + quoted: e.quoted + }))); + const key = this.keyOf(expressionForKey); + return this._getLiteralFactory( + key, literal.entries.map(e => e.value), + entries => o.literalMap(entries.map((value, index) => ({ + key: literal.entries[index].key, + value, + quoted: literal.entries[index].quoted + })))); + } + } + + private _getLiteralFactory( + key: string, values: o.Expression[], resultMap: (parameters: o.Expression[]) => o.Expression): + {literalFactory: o.Expression, literalFactoryArguments: o.Expression[]} { + let literalFactory = this.literalFactories.get(key); + const literalFactoryArguments = values.filter((e => !e.isConstant())); + if (!literalFactory) { + const resultExpressions = values.map( + (e, index) => e.isConstant() ? this.getConstLiteral(e, true) : o.variable(`a${index}`)); + const parameters = + resultExpressions.filter(isVariable).map(e => new o.FnParam(e.name !, o.DYNAMIC_TYPE)); + const pureFunctionDeclaration = + o.fn(parameters, [new o.ReturnStatement(resultMap(resultExpressions))], o.INFERRED_TYPE); + const name = this.freshName(); + this.statements.push( + o.variable(name).set(pureFunctionDeclaration).toDeclStmt(o.INFERRED_TYPE, [ + o.StmtModifier.Final + ])); + literalFactory = o.variable(name); + this.literalFactories.set(key, literalFactory); + } + return {literalFactory, literalFactoryArguments}; + } + /** * Produce a unique name. * @@ -139,7 +214,7 @@ export class ConstantPool { private freshName(): string { return this.uniqueName(CONSTANT_PREFIX); } private keyOf(expression: o.Expression) { - return expression.visitExpression(new KeyVisitor(), null); + return expression.visitExpression(new KeyVisitor(), KEY_CONTEXT); } } @@ -147,13 +222,13 @@ class KeyVisitor implements o.ExpressionVisitor { visitLiteralExpr(ast: o.LiteralExpr): string { return `${typeof ast.value === 'string' ? '"' + ast.value + '"' : ast.value}`; } - visitLiteralArrayExpr(ast: o.LiteralArrayExpr): string { - return `[${ast.entries.map(entry => entry.visitExpression(this, null)).join(',')}]`; + visitLiteralArrayExpr(ast: o.LiteralArrayExpr, context: object): string { + return `[${ast.entries.map(entry => entry.visitExpression(this, context)).join(',')}]`; } - visitLiteralMapExpr(ast: o.LiteralMapExpr): string { + visitLiteralMapExpr(ast: o.LiteralMapExpr, context: object): string { const mapEntry = (entry: o.LiteralMapEntry) => - `${entry.key}:${entry.value.visitExpression(this, null)}`; + `${entry.key}:${entry.value.visitExpression(this, context)}`; return `{${ast.entries.map(mapEntry).join(',')}`; } @@ -184,3 +259,7 @@ function invalid(arg: o.Expression | o.Statement): never { throw new Error( `Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`); } + +function isVariable(e: o.Expression): e is o.ReadVarExpr { + return e instanceof o.ReadVarExpr; +} \ No newline at end of file diff --git a/packages/compiler/src/output/output_ast.ts b/packages/compiler/src/output/output_ast.ts index e0a672fbc9..f5b930dce6 100644 --- a/packages/compiler/src/output/output_ast.ts +++ b/packages/compiler/src/output/output_ast.ts @@ -144,6 +144,11 @@ export abstract class Expression { */ abstract isEquivalent(e: Expression): boolean; + /** + * Return true if the expression is constant. + */ + abstract isConstant(): boolean; + prop(name: string, sourceSpan?: ParseSourceSpan|null): ReadPropExpr { return new ReadPropExpr(this, name, null, sourceSpan); } @@ -250,10 +255,13 @@ export class ReadVarExpr extends Expression { this.builtin = name; } } + isEquivalent(e: Expression): boolean { return e instanceof ReadVarExpr && this.name === e.name && this.builtin === e.builtin; } + isConstant() { return false; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitReadVarExpr(this, context); } @@ -274,10 +282,13 @@ export class WriteVarExpr extends Expression { super(type || value.type, sourceSpan); this.value = value; } + isEquivalent(e: Expression): boolean { return e instanceof WriteVarExpr && this.name === e.name && this.value.isEquivalent(e.value); } + isConstant() { return false; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitWriteVarExpr(this, context); } @@ -296,10 +307,14 @@ export class WriteKeyExpr extends Expression { super(type || value.type, sourceSpan); this.value = value; } + isEquivalent(e: Expression): boolean { return e instanceof WriteKeyExpr && this.receiver.isEquivalent(e.receiver) && this.index.isEquivalent(e.index) && this.value.isEquivalent(e.value); } + + isConstant() { return false; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitWriteKeyExpr(this, context); } @@ -314,10 +329,14 @@ export class WritePropExpr extends Expression { super(type || value.type, sourceSpan); this.value = value; } + isEquivalent(e: Expression): boolean { return e instanceof WritePropExpr && this.receiver.isEquivalent(e.receiver) && this.name === e.name && this.value.isEquivalent(e.value); } + + isConstant() { return false; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitWritePropExpr(this, context); } @@ -344,10 +363,14 @@ export class InvokeMethodExpr extends Expression { this.builtin = method; } } + isEquivalent(e: Expression): boolean { return e instanceof InvokeMethodExpr && this.receiver.isEquivalent(e.receiver) && this.name === e.name && this.builtin === e.builtin && areAllEquivalent(this.args, e.args); } + + isConstant() { return false; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitInvokeMethodExpr(this, context); } @@ -360,10 +383,14 @@ export class InvokeFunctionExpr extends Expression { sourceSpan?: ParseSourceSpan|null) { super(type, sourceSpan); } + isEquivalent(e: Expression): boolean { return e instanceof InvokeFunctionExpr && this.fn.isEquivalent(e.fn) && areAllEquivalent(this.args, e.args); } + + isConstant() { return false; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitInvokeFunctionExpr(this, context); } @@ -376,10 +403,14 @@ export class InstantiateExpr extends Expression { sourceSpan?: ParseSourceSpan|null) { super(type, sourceSpan); } + isEquivalent(e: Expression): boolean { return e instanceof InstantiateExpr && this.classExpr.isEquivalent(e.classExpr) && areAllEquivalent(this.args, e.args); } + + isConstant() { return false; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitInstantiateExpr(this, context); } @@ -392,9 +423,13 @@ export class LiteralExpr extends Expression { sourceSpan?: ParseSourceSpan|null) { super(type, sourceSpan); } + isEquivalent(e: Expression): boolean { return e instanceof LiteralExpr && this.value === e.value; } + + isConstant() { return true; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitLiteralExpr(this, context); } @@ -407,10 +442,14 @@ export class ExternalExpr extends Expression { sourceSpan?: ParseSourceSpan|null) { super(type, sourceSpan); } + isEquivalent(e: Expression): boolean { return e instanceof ExternalExpr && this.value.name === e.value.name && this.value.moduleName === e.value.moduleName && this.value.runtime === e.value.runtime; } + + isConstant() { return false; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitExternalExpr(this, context); } @@ -424,16 +463,21 @@ export class ExternalReference { export class ConditionalExpr extends Expression { public trueCase: Expression; + constructor( public condition: Expression, trueCase: Expression, public falseCase: Expression|null = null, type?: Type|null, sourceSpan?: ParseSourceSpan|null) { super(type || trueCase.type, sourceSpan); this.trueCase = trueCase; } + isEquivalent(e: Expression): boolean { return e instanceof ConditionalExpr && this.condition.isEquivalent(e.condition) && this.trueCase.isEquivalent(e.trueCase) && nullSafeIsEquivalent(this.falseCase, e.falseCase); } + + isConstant() { return false; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitConditionalExpr(this, context); } @@ -444,9 +488,13 @@ export class NotExpr extends Expression { constructor(public condition: Expression, sourceSpan?: ParseSourceSpan|null) { super(BOOL_TYPE, sourceSpan); } + isEquivalent(e: Expression): boolean { return e instanceof NotExpr && this.condition.isEquivalent(e.condition); } + + isConstant() { return false; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitNotExpr(this, context); } @@ -456,9 +504,13 @@ export class AssertNotNull extends Expression { constructor(public condition: Expression, sourceSpan?: ParseSourceSpan|null) { super(condition.type, sourceSpan); } + isEquivalent(e: Expression): boolean { return e instanceof AssertNotNull && this.condition.isEquivalent(e.condition); } + + isConstant() { return false; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitAssertNotNullExpr(this, context); } @@ -468,9 +520,13 @@ export class CastExpr extends Expression { constructor(public value: Expression, type?: Type|null, sourceSpan?: ParseSourceSpan|null) { super(type, sourceSpan); } + isEquivalent(e: Expression): boolean { return e instanceof CastExpr && this.value.isEquivalent(e.value); } + + isConstant() { return false; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitCastExpr(this, context); } @@ -490,10 +546,14 @@ export class FunctionExpr extends Expression { sourceSpan?: ParseSourceSpan|null, public name?: string|null) { super(type, sourceSpan); } + isEquivalent(e: Expression): boolean { return e instanceof FunctionExpr && areAllEquivalent(this.params, e.params) && areAllEquivalent(this.statements, e.statements); } + + isConstant() { return false; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitFunctionExpr(this, context); } @@ -513,10 +573,14 @@ export class BinaryOperatorExpr extends Expression { super(type || lhs.type, sourceSpan); this.lhs = lhs; } + isEquivalent(e: Expression): boolean { return e instanceof BinaryOperatorExpr && this.operator === e.operator && this.lhs.isEquivalent(e.lhs) && this.rhs.isEquivalent(e.rhs); } + + isConstant() { return false; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitBinaryOperatorExpr(this, context); } @@ -529,13 +593,18 @@ export class ReadPropExpr extends Expression { sourceSpan?: ParseSourceSpan|null) { super(type, sourceSpan); } + isEquivalent(e: Expression): boolean { return e instanceof ReadPropExpr && this.receiver.isEquivalent(e.receiver) && this.name === e.name; } + + isConstant() { return false; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitReadPropExpr(this, context); } + set(value: Expression): WritePropExpr { return new WritePropExpr(this.receiver, this.name, value, null, this.sourceSpan); } @@ -548,13 +617,18 @@ export class ReadKeyExpr extends Expression { sourceSpan?: ParseSourceSpan|null) { super(type, sourceSpan); } + isEquivalent(e: Expression): boolean { return e instanceof ReadKeyExpr && this.receiver.isEquivalent(e.receiver) && this.index.isEquivalent(e.index); } + + isConstant() { return false; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitReadKeyExpr(this, context); } + set(value: Expression): WriteKeyExpr { return new WriteKeyExpr(this.receiver, this.index, value, null, this.sourceSpan); } @@ -567,6 +641,9 @@ export class LiteralArrayExpr extends Expression { super(type, sourceSpan); this.entries = entries; } + + isConstant() { return this.entries.every(e => e.isConstant()); } + isEquivalent(e: Expression): boolean { return e instanceof LiteralArrayExpr && areAllEquivalent(this.entries, e.entries); } @@ -591,9 +668,13 @@ export class LiteralMapExpr extends Expression { this.valueType = type.valueType; } } + isEquivalent(e: Expression): boolean { return e instanceof LiteralMapExpr && areAllEquivalent(this.entries, e.entries); } + + isConstant() { return this.entries.every(e => e.value.isConstant()); } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitLiteralMapExpr(this, context); } @@ -603,9 +684,13 @@ export class CommaExpr extends Expression { constructor(public parts: Expression[], sourceSpan?: ParseSourceSpan|null) { super(parts[parts.length - 1].type, sourceSpan); } + isEquivalent(e: Expression): boolean { return e instanceof CommaExpr && areAllEquivalent(this.parts, e.parts); } + + isConstant() { return false; } + visitExpression(visitor: ExpressionVisitor, context: any): any { return visitor.visitCommaExpr(this, context); } diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 4fbe0e5954..253fdd1f63 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -64,6 +64,17 @@ export class Identifiers { static interpolation8: o.ExternalReference = {name: 'ɵi8', moduleName: CORE}; static interpolationV: o.ExternalReference = {name: 'ɵiV', moduleName: CORE}; + static pureFunction0: o.ExternalReference = {name: 'ɵf0', moduleName: CORE}; + static pureFunction1: o.ExternalReference = {name: 'ɵf1', moduleName: CORE}; + static pureFunction2: o.ExternalReference = {name: 'ɵf2', moduleName: CORE}; + static pureFunction3: o.ExternalReference = {name: 'ɵf3', moduleName: CORE}; + static pureFunction4: o.ExternalReference = {name: 'ɵf4', moduleName: CORE}; + static pureFunction5: o.ExternalReference = {name: 'ɵf5', moduleName: CORE}; + static pureFunction6: o.ExternalReference = {name: 'ɵf6', moduleName: CORE}; + static pureFunction7: o.ExternalReference = {name: 'ɵf7', moduleName: CORE}; + static pureFunction8: o.ExternalReference = {name: 'ɵf8', moduleName: CORE}; + static pureFunctionV: o.ExternalReference = {name: 'ɵfV', moduleName: CORE}; + static pipeBind1: o.ExternalReference = {name: 'ɵpb1', moduleName: CORE}; static pipeBind2: o.ExternalReference = {name: 'ɵpb2', moduleName: CORE}; static pipeBind3: o.ExternalReference = {name: 'ɵpb3', moduleName: CORE}; diff --git a/packages/compiler/src/render3/r3_view_compiler.ts b/packages/compiler/src/render3/r3_view_compiler.ts index f127880733..84df4f2319 100644 --- a/packages/compiler/src/render3/r3_view_compiler.ts +++ b/packages/compiler/src/render3/r3_view_compiler.ts @@ -8,9 +8,9 @@ import {CompileDirectiveMetadata, CompilePipeSummary, CompileTokenMetadata, CompileTypeMetadata, flatten, identifierName, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata'; import {CompileReflector} from '../compile_reflector'; -import {BindingForm, BuiltinConverter, ConvertPropertyBindingResult, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter'; +import {BindingForm, BuiltinConverter, BuiltinFunctionCall, ConvertPropertyBindingResult, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter'; import {ConstantPool, DefinitionKind} from '../constant_pool'; -import {AST, AstMemoryEfficientTransformer, AstTransformer, BindingPipe, FunctionCall, ImplicitReceiver, LiteralPrimitive, MethodCall, ParseSpan, PropertyRead} from '../expression_parser/ast'; +import {AST, AstMemoryEfficientTransformer, AstTransformer, BindingPipe, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, PropertyRead} from '../expression_parser/ast'; import {Identifiers} from '../identifiers'; import {LifecycleHooks} from '../lifecycle_reflector'; import * as o from '../output/output_ast'; @@ -22,6 +22,7 @@ import {OutputContext, error} from '../util'; import {Identifiers as R3} from './r3_identifiers'; + /** Name of the context parameter passed into a template function */ const CONTEXT_NAME = 'ctx'; @@ -217,6 +218,23 @@ function pipeBinding(args: o.Expression[]): o.ExternalReference { } } +const pureFunctionIdentifiers = [ + R3.pureFunction0, R3.pureFunction1, R3.pureFunction2, R3.pureFunction3, R3.pureFunction4, + R3.pureFunction5, R3.pureFunction6, R3.pureFunction7, R3.pureFunction8 +]; +function getLiteralFactory( + outputContext: OutputContext, literal: o.LiteralArrayExpr | o.LiteralMapExpr): o.Expression { + const {literalFactory, literalFactoryArguments} = + outputContext.constantPool.getLiteralFactory(literal); + literalFactoryArguments.length > 0 || error(`Expected arguments to a literal factory function`); + let pureFunctionIdent = + pureFunctionIdentifiers[literalFactoryArguments.length] || R3.pureFunctionV; + + // Literal factories are pure functions that only need to be re-invoked when the parameters + // change. + return o.importExpr(pureFunctionIdent).callFn([literalFactory, ...literalFactoryArguments]); +} + class BindingScope { private map = new Map(); private referenceNameIndex = 0; @@ -269,7 +287,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { private _postfix: o.Statement[] = []; private _contentProjections: Map; private _projectionDefinitionIndex = 0; - private _pipeConverter: PipeConverter; + private _valueConverter: ValueConverter; private unsupported = unsupported; private invalid = invalid; @@ -279,8 +297,8 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { private bindingScope: BindingScope, private level = 0, private ngContentSelectors: string[], private contextName: string|null, private templateName: string|null, private pipes: Map) { - this._pipeConverter = - new PipeConverter(() => this.allocateDataSlot(), (name, localName, slot, value) => { + this._valueConverter = new ValueConverter( + outputCtx, () => this.allocateDataSlot(), (name, localName, slot, value) => { bindingScope.set(localName, value); const pipe = pipes.get(name) !; pipe || error(`Could not find pipe ${name}`); @@ -634,7 +652,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { } private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression { - const pipesConvertedValue = value.visit(this._pipeConverter); + const pipesConvertedValue = value.visit(this._valueConverter); const convertedPropertyBinding = convertPropertyBinding( this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple, interpolate); @@ -688,10 +706,10 @@ export function createFactory( o.INFERRED_TYPE, null, type.reference.name ? `${type.reference.name}_Factory` : null); } -class PipeConverter extends AstMemoryEfficientTransformer { +class ValueConverter extends AstMemoryEfficientTransformer { private pipeSlots = new Map(); constructor( - private allocateSlot: () => number, + private outputCtx: OutputContext, private allocateSlot: () => number, private definePipe: (name: string, localName: string, slot: number, value: o.Expression) => void) { super(); @@ -715,6 +733,31 @@ class PipeConverter extends AstMemoryEfficientTransformer { return new FunctionCall( ast.span, target, [new LiteralPrimitive(ast.span, slot), value, ...args]); } + + visitLiteralArray(ast: LiteralArray, context: any): AST { + return new BuiltinFunctionCall(ast.span, this.visitAll(ast.expressions), values => { + // If the literal has calculated (non-literal) elements transform it into + // calls to literal factories that compose the literal and will cache intermediate + // values. Otherwise, just return an literal array that contains the values. + const literal = o.literalArr(values); + return values.every(a => a.isConstant()) ? + this.outputCtx.constantPool.getConstLiteral(literal, true) : + getLiteralFactory(this.outputCtx, literal); + }); + } + + visitLiteralMap(ast: LiteralMap, context: any): AST { + return new BuiltinFunctionCall(ast.span, this.visitAll(ast.values), values => { + // If the literal has calculated (non-literal) elements transform it into + // calls to literal factories that compose the literal and will cache intermediate + // values. Otherwise, just return an literal array that contains the values. + const literal = o.literalMap(values.map( + (value, index) => ({key: ast.keys[index].key, value, quoted: ast.keys[index].quoted}))); + return values.every(a => a.isConstant()) ? + this.outputCtx.constantPool.getConstLiteral(literal, true) : + getLiteralFactory(this.outputCtx, literal); + }); + } } function invalid(arg: o.Expression | o.Statement | TemplateAst): never { diff --git a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts index 5935766ddf..f2149d2f48 100644 --- a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts +++ b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts @@ -209,6 +209,273 @@ describe('compiler compliance', () => { expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef'); }); + describe('value composition', () => { + + it('should support array literals', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, Input, NgModule} from '@angular/core'; + + @Component({ + selector: 'my-comp', + template: \` +

{{ names[0] }}

+

{{ names[1] }}

+ \` + }) + export class MyComp { + @Input() names: string[]; + } + + @Component({ + selector: 'my-app', + template: \` + + \` + }) + export class MyApp { + customName = 'Bess'; + } + + @NgModule({declarations: [MyComp, MyApp]}) + export class MyModule { } + ` + } + }; + + const MyAppDeclaration = ` + const $e0_ff$ = ($v$: any) => { return ['Nancy', $v$]; }; + … + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, MyComp); + $r3$.ɵe(); + } + $r3$.ɵp(0, 'names', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.customName))); + MyComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + `; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, MyAppDeclaration, 'Invalid array emit'); + }); + + it('should support 9+ bindings in array literals', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, Input, NgModule} from '@angular/core'; + + @Component({ + selector: 'my-comp', + template: \` + {{ names[0] }} + {{ names[1] }} + {{ names[3] }} + {{ names[4] }} + {{ names[5] }} + {{ names[6] }} + {{ names[7] }} + {{ names[8] }} + {{ names[9] }} + {{ names[10] }} + {{ names[11] }} + \` + }) + export class MyComp { + @Input() names: string[]; + } + + @Component({ + selector: 'my-app', + template: \` + + + \` + }) + export class MyApp { + n0 = 'a'; + n1 = 'b'; + n2 = 'c'; + n3 = 'd'; + n4 = 'e'; + n5 = 'f'; + n6 = 'g'; + n7 = 'h'; + n8 = 'i'; + } + + @NgModule({declarations: [MyComp, MyApp]}) + export class MyModule {} + ` + } + }; + + const MyAppDefinition = ` + const $e0_ff$ = ($v0$: $any$, $v1$: $any$, $v2$: $any$, $v3$: $any$, $v4$: $any$, $v5$: $any$, $v6$: $any$, $v7$: $any$, $v8$: $any$) => { + return ['start-', $v0$, $v1$, $v2$, $v3$, $v4$, '-middle-', $v5$, $v6$, $v7$, $v8$, '-end']; + } + … + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, MyComp); + $r3$.ɵe(); + } + $r3$.ɵp( + 0, 'names', + $r3$.ɵb($r3$.ɵfV($e0_ff$, ctx.n0, ctx.n1, ctx.n2, ctx.n3, ctx.n4, ctx.n5, ctx.n6, ctx.n7, ctx.n8))); + MyComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + `; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, MyAppDefinition, 'Invalid array binding'); + }); + + it('should support object literals', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, Input, NgModule} from '@angular/core'; + + @Component({ + selector: 'object-comp', + template: \` +

{{ config['duration'] }}

+

{{ config.animation }}

+ \` + }) + export class ObjectComp { + @Input() config: {[key: string]: any}; + } + + @Component({ + selector: 'my-app', + template: \` + + \` + }) + export class MyApp { + name = 'slide'; + } + + @NgModule({declarations: [ObjectComp, MyApp]}) + export class MyModule {} + ` + } + }; + + const MyAppDefinition = ` + const $e0_ff$ = ($v$: any) => { return {'duration': 500, animation: $v$}; }; + … + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, ObjectComp); + $r3$.ɵe(); + } + $r3$.ɵp(0, 'config', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.name))); + ObjectComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + `; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, MyAppDefinition, 'Invalid object literal binding'); + }); + + it('should support expressions nested deeply in object/array literals', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, Input, NgModule} from '@angular/core'; + + @Component({ + selector: 'nested-comp', + template: \` +

{{ config.animation }}

+

{{config.actions[0].opacity }}

+

{{config.actions[1].duration }}

+ \` + }) + export class NestedComp { + @Input() config: {[key: string]: any}; + } + + @Component({ + selector: 'my-app', + template: \` + + + \` + }) + export class MyApp { + name = 'slide'; + duration = 100; + } + + @NgModule({declarations: [NestedComp, MyApp]}) + export class MyModule {} + ` + } + }; + + const MyAppDefinition = ` + const $c0$ = {opacity: 0, duration: 0}; + const $e0_ff$ = ($v$: any) => { return {opacity: 1, duration: $v$}; }; + const $e0_ff_1$ = ($v$: any) => { return [$c0$, $v$]; }; + const $e0_ff_2$ = ($v1$: any, $v2$: any) => { return {animation: $v1$, actions: $v2$}; }; + … + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, NestedComp); + $r3$.ɵe(); + } + $r3$.ɵp( + 0, 'config', + $r3$.ɵb($r3$.ɵf2( + $e0_ff_2$, ctx.name, $r3$.ɵf1($e0_ff_1$, $r3$.ɵf1($e0_ff$, ctx.duration))))); + NestedComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + `; + + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, MyAppDefinition, 'Invalid array/object literal binding'); + }); + }); + it('should support content projection', () => { const files = { app: { From 0451fd93df954e9a6ecedb15997bfab9e74b2b22 Mon Sep 17 00:00:00 2001 From: Chuck Jazdzewski Date: Tue, 13 Feb 2018 10:48:22 -0800 Subject: [PATCH 031/604] feat(ivy): support generating view and content queries (#22330) PR Close #22330 --- .../src/output/abstract_js_emitter.ts | 7 +- packages/compiler/src/output/output_ast.ts | 16 +- .../compiler/src/output/output_interpreter.ts | 3 +- packages/compiler/src/output/ts_emitter.ts | 6 +- .../compiler/src/render3/r3_identifiers.ts | 3 + .../compiler/src/render3/r3_pipe_compiler.ts | 2 +- .../compiler/src/render3/r3_view_compiler.ts | 145 ++++++++++++++++-- .../render3/r3_compiler_compliance_spec.ts | 127 +++++++++++++++ 8 files changed, 281 insertions(+), 28 deletions(-) diff --git a/packages/compiler/src/output/abstract_js_emitter.ts b/packages/compiler/src/output/abstract_js_emitter.ts index 6a70b78ced..8dfa19bc3d 100644 --- a/packages/compiler/src/output/abstract_js_emitter.ts +++ b/packages/compiler/src/output/abstract_js_emitter.ts @@ -82,8 +82,11 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor { return null; } visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): any { - ctx.print(stmt, `var ${stmt.name} = `); - stmt.value.visitExpression(this, ctx); + ctx.print(stmt, `var ${stmt.name}`); + if (stmt.value) { + ctx.print(stmt, ' = '); + stmt.value.visitExpression(this, ctx); + } ctx.println(stmt, `;`); return null; } diff --git a/packages/compiler/src/output/output_ast.ts b/packages/compiler/src/output/output_ast.ts index f5b930dce6..e7cf3644c3 100644 --- a/packages/compiler/src/output/output_ast.ts +++ b/packages/compiler/src/output/output_ast.ts @@ -756,14 +756,14 @@ export abstract class Statement { export class DeclareVarStmt extends Statement { public type: Type|null; constructor( - public name: string, public value: Expression, type?: Type|null, + public name: string, public value?: Expression, type?: Type|null, modifiers: StmtModifier[]|null = null, sourceSpan?: ParseSourceSpan|null) { super(modifiers, sourceSpan); - this.type = type || value.type; + this.type = type || (value && value.type) || null; } isEquivalent(stmt: Statement): boolean { return stmt instanceof DeclareVarStmt && this.name === stmt.name && - this.value.isEquivalent(stmt.value); + (this.value ? !!stmt.value && this.value.isEquivalent(stmt.value) : !stmt.value); } visitStatement(visitor: StatementVisitor, context: any): any { return visitor.visitDeclareVarStmt(this, context); @@ -1087,11 +1087,9 @@ export class AstTransformer implements StatementVisitor, ExpressionVisitor { } visitDeclareVarStmt(stmt: DeclareVarStmt, context: any): any { + const value = stmt.value && stmt.value.visitExpression(this, context); return this.transformStmt( - new DeclareVarStmt( - stmt.name, stmt.value.visitExpression(this, context), stmt.type, stmt.modifiers, - stmt.sourceSpan), - context); + new DeclareVarStmt(stmt.name, value, stmt.type, stmt.modifiers, stmt.sourceSpan), context); } visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): any { return this.transformStmt( @@ -1275,7 +1273,9 @@ export class RecursiveAstVisitor implements StatementVisitor, ExpressionVisitor } visitDeclareVarStmt(stmt: DeclareVarStmt, context: any): any { - stmt.value.visitExpression(this, context); + if (stmt.value) { + stmt.value.visitExpression(this, context); + } if (stmt.type) { stmt.type.visitType(this, context); } diff --git a/packages/compiler/src/output/output_interpreter.ts b/packages/compiler/src/output/output_interpreter.ts index 3f2b134a84..24747422e2 100644 --- a/packages/compiler/src/output/output_interpreter.ts +++ b/packages/compiler/src/output/output_interpreter.ts @@ -95,7 +95,8 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor { debugAst(ast: o.Expression|o.Statement|o.Type): string { return debugOutputAstAsTypeScript(ast); } visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: _ExecutionContext): any { - ctx.vars.set(stmt.name, stmt.value.visitExpression(this, ctx)); + const initialValue = stmt.value ? stmt.value.visitExpression(this, ctx) : undefined; + ctx.vars.set(stmt.name, initialValue); if (stmt.hasModifier(o.StmtModifier.Exported)) { ctx.exports.push(stmt.name); } diff --git a/packages/compiler/src/output/ts_emitter.ts b/packages/compiler/src/output/ts_emitter.ts index 54df9a3b36..ef96f62001 100644 --- a/packages/compiler/src/output/ts_emitter.ts +++ b/packages/compiler/src/output/ts_emitter.ts @@ -161,8 +161,10 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor } ctx.print(stmt, ` ${stmt.name}`); this._printColonType(stmt.type, ctx); - ctx.print(stmt, ` = `); - stmt.value.visitExpression(this, ctx); + if (stmt.value) { + ctx.print(stmt, ` = `); + stmt.value.visitExpression(this, ctx); + } ctx.println(stmt, `;`); return null; } diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 253fdd1f63..6d239df39b 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -110,5 +110,8 @@ export class Identifiers { static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE}; + static query: o.ExternalReference = {name: 'ɵQ', moduleName: CORE}; + static queryRefresh: o.ExternalReference = {name: 'ɵqR', moduleName: CORE}; + static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE}; } \ No newline at end of file diff --git a/packages/compiler/src/render3/r3_pipe_compiler.ts b/packages/compiler/src/render3/r3_pipe_compiler.ts index 3593418efd..e0b189a755 100644 --- a/packages/compiler/src/render3/r3_pipe_compiler.ts +++ b/packages/compiler/src/render3/r3_pipe_compiler.ts @@ -24,7 +24,7 @@ export function compilePipe( {key: 'type', value: outputCtx.importExpr(pipe.type.reference), quoted: false}); // e.g. factory: function MyPipe_Factory() { return new MyPipe(); }, - const templateFactory = createFactory(pipe.type, outputCtx, reflector); + const templateFactory = createFactory(pipe.type, outputCtx, reflector, []); definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false}); // e.g. pure: true diff --git a/packages/compiler/src/render3/r3_view_compiler.ts b/packages/compiler/src/render3/r3_view_compiler.ts index 84df4f2319..9be0521d2c 100644 --- a/packages/compiler/src/render3/r3_view_compiler.ts +++ b/packages/compiler/src/render3/r3_view_compiler.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {CompileDirectiveMetadata, CompilePipeSummary, CompileTokenMetadata, CompileTypeMetadata, flatten, identifierName, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata'; +import {CompileDirectiveMetadata, CompilePipeSummary, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, flatten, identifierName, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata'; import {CompileReflector} from '../compile_reflector'; import {BindingForm, BuiltinConverter, BuiltinFunctionCall, ConvertPropertyBindingResult, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter'; import {ConstantPool, DefinitionKind} from '../constant_pool'; @@ -47,7 +47,7 @@ export function compileDirective( {key: 'type', value: outputCtx.importExpr(directive.type.reference), quoted: false}); // e.g. `factory: () => new MyApp(injectElementRef())` - const templateFactory = createFactory(directive.type, outputCtx, reflector); + const templateFactory = createFactory(directive.type, outputCtx, reflector, directive.queries); definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false}); // e.g 'inputs: {a: 'a'}` @@ -108,9 +108,15 @@ export function compileComponent( } // e.g. `factory: function MyApp_Factory() { return new MyApp(injectElementRef()); }` - const templateFactory = createFactory(component.type, outputCtx, reflector); + const templateFactory = createFactory(component.type, outputCtx, reflector, component.queries); definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false}); + // e.g `hostBindings: function MyApp_HostBindings { ... } + const hostBindings = createHostBindingsFunction(component.type, outputCtx, component.queries); + if (hostBindings) { + definitionMapValues.push({key: 'hostBindings', value: hostBindings, quoted: false}); + } + // e.g. `template: function MyComponent_Template(_ctx, _cm) {...}` const templateTypeName = component.type.reference.name; const templateName = templateTypeName ? `${templateTypeName}_Template` : null; @@ -118,7 +124,8 @@ export function compileComponent( const templateFunctionExpression = new TemplateDefinitionBuilder( outputCtx, outputCtx.constantPool, reflector, CONTEXT_NAME, ROOT_SCOPE.nestedScope(), 0, - component.template !.ngContentSelectors, templateTypeName, templateName, pipeMap) + component.template !.ngContentSelectors, templateTypeName, templateName, pipeMap, + component.viewQueries) .buildTemplateFunction(template, []); definitionMapValues.push({key: 'template', value: templateFunctionExpression, quoted: false}); @@ -296,7 +303,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { private reflector: CompileReflector, private contextParameter: string, private bindingScope: BindingScope, private level = 0, private ngContentSelectors: string[], private contextName: string|null, private templateName: string|null, - private pipes: Map) { + private pipes: Map, private viewQueries: CompileQueryMetadata[]) { this._valueConverter = new ValueConverter( outputCtx, () => this.allocateDataSlot(), (name, localName, slot, value) => { bindingScope.set(localName, value); @@ -353,6 +360,32 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { } } + // Define and update any view queries + for (let query of this.viewQueries) { + // e.g. r3.Q(0, SomeDirective, true); + const querySlot = this.allocateDataSlot(); + const predicate = getQueryPredicate(query, this.outputCtx); + const args = [ + /* memoryIndex */ o.literal(querySlot, o.INFERRED_TYPE), + /* predicate */ predicate, + /* descend */ o.literal(query.descendants, o.INFERRED_TYPE) + ]; + + if (query.read) { + args.push(this.outputCtx.importExpr(query.read.identifier !.reference)); + } + this.instruction(this._creationMode, null, R3.query, ...args); + + // (r3.qR(tmp = r3.ɵld(0)) && (ctx.someDir = tmp)); + const temporary = this.temp(); + const getQueryList = o.importExpr(R3.load).callFn([o.literal(querySlot)]); + const refresh = o.importExpr(R3.queryRefresh).callFn([temporary.set(getQueryList)]); + const updateDirective = o.variable(CONTEXT_NAME) + .prop(query.propertyName) + .set(query.first ? temporary.prop('first') : temporary); + this._bindingMode.push(refresh.and(updateDirective).toStmt()); + } + templateVisitAll(this, asts); const creationMode = this._creationMode.length > 0 ? @@ -589,7 +622,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { const templateVisitor = new TemplateDefinitionBuilder( this.outputCtx, this.constantPool, this.reflector, templateContext, this.bindingScope.nestedScope(), this.level + 1, this.ngContentSelectors, contextName, - templateName, this.pipes); + templateName, this.pipes, []); const templateFunctionExpr = templateVisitor.buildTemplateFunction(ast.children, ast.variables); this._postfix.push(templateFunctionExpr.toDeclStmt(templateName, null)); } @@ -643,9 +676,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { private temp(): o.ReadVarExpr { if (!this._temporaryAllocated) { - this._prefix.push(o.variable(TEMPORARY_NAME, o.DYNAMIC_TYPE,  null) - .set(o.literal(undefined)) - .toDeclStmt(o.DYNAMIC_TYPE)); + this._prefix.push(new o.DeclareVarStmt(TEMPORARY_NAME, undefined, o.DYNAMIC_TYPE)); this._temporaryAllocated = true; } return o.variable(TEMPORARY_NAME); @@ -665,9 +696,31 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver { } } +function getQueryPredicate(query: CompileQueryMetadata, outputCtx: OutputContext): o.Expression { + let predicate: o.Expression; + if (query.selectors.length > 1 || (query.selectors.length == 1 && query.selectors[0].value)) { + const selectors = query.selectors.map(value => value.value as string); + selectors.some(value => !value) && error('Found a type among the string selectors expected'); + predicate = outputCtx.constantPool.getConstLiteral( + o.literalArr(selectors.map(value => o.literal(value)))); + } else if (query.selectors.length == 1) { + const first = query.selectors[0]; + if (first.identifier) { + predicate = outputCtx.importExpr(first.identifier.reference); + } else { + error('Unexpected query form'); + predicate = o.literal(null); + } + } else { + error('Unexpected query form'); + predicate = o.literal(null); + } + return predicate; +} + export function createFactory( - type: CompileTypeMetadata, outputCtx: OutputContext, - reflector: CompileReflector): o.FunctionExpr { + type: CompileTypeMetadata, outputCtx: OutputContext, reflector: CompileReflector, + queries: CompileQueryMetadata[]): o.Expression { let args: o.Expression[] = []; const elementRef = reflector.resolveExternalReference(Identifiers.ElementRef); @@ -700,10 +753,74 @@ export function createFactory( } } + const queryDefinitions: o.Expression[] = []; + for (let query of queries) { + const predicate = getQueryPredicate(query, outputCtx); + + // e.g. r3.Q(null, SomeDirective, false) or r3.Q(null, ['div'], false) + const parameters = [ + /* memoryIndex */ o.literal(null, o.INFERRED_TYPE), + /* predicate */ predicate, + /* descend */ o.literal(query.descendants) + ]; + + if (query.read) { + parameters.push(outputCtx.importExpr(query.read.identifier !.reference)); + } + + queryDefinitions.push(o.importExpr(R3.query).callFn(parameters)); + } + + const createInstance = new o.InstantiateExpr(outputCtx.importExpr(type.reference), args); + const result = queryDefinitions.length > 0 ? o.literalArr([createInstance, ...queryDefinitions]) : + createInstance; + return o.fn( - [], - [new o.ReturnStatement(new o.InstantiateExpr(outputCtx.importExpr(type.reference), args))], - o.INFERRED_TYPE, null, type.reference.name ? `${type.reference.name}_Factory` : null); + [], [new o.ReturnStatement(result)], o.INFERRED_TYPE, null, + type.reference.name ? `${type.reference.name}_Factory` : null); +} + +// Return a host binding function or null if one is not necessary. +export function createHostBindingsFunction( + type: CompileTypeMetadata, outputCtx: OutputContext, + queries: CompileQueryMetadata[]): o.Expression|null { + const statements: o.Statement[] = []; + + const temporary = function() { + let declared = false; + return () => { + if (!declared) { + statements.push(new o.DeclareVarStmt(TEMPORARY_NAME, undefined, o.DYNAMIC_TYPE)); + declared = true; + } + return o.variable(TEMPORARY_NAME); + }; + }(); + + for (let index = 0; index < queries.length; index++) { + const query = queries[index]; + + // e.g. r3.qR(tmp = r3.ld(dirIndex)[1]) && (r3.ld(dirIndex)[0].someDir = tmp); + const getDirectiveMemory = o.importExpr(R3.load).callFn([o.variable('dirIndex')]); + // The query list is at the query index + 1 because the directive itself is in slot 0. + const getQueryList = getDirectiveMemory.key(o.literal(index + 1)); + const assignToTemporary = temporary().set(getQueryList); + const callQueryRefresh = o.importExpr(R3.queryRefresh).callFn([assignToTemporary]); + const updateDirective = getDirectiveMemory.key(o.literal(0, o.INFERRED_TYPE)) + .prop(query.propertyName) + .set(query.first ? temporary().key(o.literal(0)) : temporary()); + const andExpression = callQueryRefresh.and(updateDirective); + statements.push(andExpression.toStmt()); + } + + if (statements.length > 0) { + return o.fn( + [new o.FnParam('dirIndex', o.NUMBER_TYPE), new o.FnParam('elIndex', o.NUMBER_TYPE)], + statements, o.INFERRED_TYPE, null, + type.reference.name ? `${type.reference.name}_HostBindings` : null); + } + + return null; } class ValueConverter extends AstMemoryEfficientTransformer { diff --git a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts index f2149d2f48..c675688a2f 100644 --- a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts +++ b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts @@ -551,6 +551,133 @@ describe('compiler compliance', () => { result.source, ComplexComponentDefinition, 'Incorrect ComplexComponent definition'); }); + describe('queries', () => { + const directive = { + 'some.directive.ts': ` + import {Directive} from '@angular/core'; + + @Directive({ + selector: '[someDir]', + }) + export class SomeDirective { } + ` + }; + + it('should support view queries', () => { + const files = { + app: { + ...directive, + 'view_query.component.ts': ` + import {Component, NgModule, ViewChild} from '@angular/core'; + import {SomeDirective} from './some.directive'; + + @Component({ + selector: 'view-query-component', + template: \` +
+ \` + }) + export class ViewQueryComponent { + @ViewChild(SomeDirective) someDir: SomeDirective; + } + + @NgModule({declarations: [SomeDirective, ViewQueryComponent]}) + export class MyModule {} + ` + } + }; + + const ViewQueryComponentDefinition = ` + const $e0_attrs$ = ['someDir','']; + const $e1_dirs$ = [SomeDirective]; + … + static ngComponentDef = $r3$.ɵdefineComponent({ + type: ViewQueryComponent, + tag: 'view-query-component', + factory: function ViewQueryComponent_Factory() { return new ViewQueryComponent(); }, + template: function ViewQueryComponent_Template(ctx: $ViewQueryComponent$, cm: $boolean$) { + var $tmp$: $any$; + if (cm) { + $r3$.ɵQ(0, SomeDirective, true); + $r3$.ɵE(1, 'div', $e0_attrs$, $e1_dirs$); + $r3$.ɵe(); + } + ($r3$.ɵqR(($tmp$ = $r3$.ɵld(0))) && (ctx.someDir = $tmp$.first)); + SomeDirective.ngDirectiveDef.h(2, 1); + $r3$.ɵr(2, 1); + } + });`; + + const result = compile(files, angularFiles); + const source = result.source; + + expectEmit(source, ViewQueryComponentDefinition, 'Invalid ViewQuery declaration'); + }); + + it('should support content queries', () => { + const files = { + app: { + ...directive, + 'spec.ts': ` + import {Component, ContentChild, NgModule} from '@angular/core'; + import {SomeDirective} from './some.directive'; + + @Component({ + selector: 'content-query-component', + template: \` +
+ \` + }) + export class ContentQueryComponent { + @ContentChild(SomeDirective) someDir: SomeDirective; + } + + @Component({ + selector: 'my-app', + template: \` + +
+
+ \` + }) + export class MyApp { } + + @NgModule({declarations: [SomeDirective, ContentQueryComponent, MyApp]}) + export class MyModule { } + ` + } + }; + + const ContentQueryComponentDefinition = ` + static ngComponentDef = $r3$.ɵdefineComponent({ + type: ContentQueryComponent, + tag: 'content-query-component', + factory: function ContentQueryComponent_Factory() { + return [new ContentQueryComponent(), $r3$.ɵQ(null, SomeDirective, true)]; + }, + hostBindings: function ContentQueryComponent_HostBindings( + dirIndex: $number$, elIndex: $number$) { + var $tmp$: $any$; + ($r3$.ɵqR(($tmp$ = $r3$.ɵld(dirIndex)[1])) && ($r3$.ɵld(dirIndex)[0].someDir = $tmp$[0])); + }, + template: function ContentQueryComponent_Template( + ctx: $ContentQueryComponent$, cm: $boolean$) { + if (cm) { + $r3$.ɵpD(0); + $r3$.ɵE(1, 'div'); + $r3$.ɵP(2, 0); + $r3$.ɵe(); + } + } + });`; + + const result = compile(files, angularFiles); + + const source = result.source; + expectEmit(source, ContentQueryComponentDefinition, 'Invalid ContentQuery declaration'); + }); + }); + describe('pipes', () => { const files = { From ba8df8a3f1bafc63f2a15c8502723c270820649f Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Fri, 2 Mar 2018 10:10:59 -0800 Subject: [PATCH 032/604] build: update to latest bazel rules (#22558) PR Close #22558 --- WORKSPACE | 16 +++++++--------- integration/bazel/WORKSPACE | 18 ++++++------------ packages/bazel/src/esm5.bzl | 2 +- packages/bazel/src/ngc-wrapped/BUILD.bazel | 2 +- packages/service-worker/worker/src/driver.ts | 8 ++++---- .../service-worker/worker/test/happy_spec.ts | 2 +- 6 files changed, 20 insertions(+), 28 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index e6a50b61ec..dd18ca2191 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -2,13 +2,13 @@ workspace(name = "angular") # Using a pre-release snapshot to pick up a commit that makes all nodejs_binary # programs produce source-mapped stack traces and uglify sourcemaps. -RULES_NODEJS_VERSION = "4303cbef12e5e252ad66cc35cff1123e3a44ee83" +RULES_NODEJS_VERSION = "f3fc23b7e1f32984a3e5d0c7eabe3baa127fb32a" http_archive( name = "build_bazel_rules_nodejs", - url = "https://github.com/bazelbuild/rules_nodejs/archive/%s.zip" % RULES_NODEJS_VERSION, - strip_prefix = "rules_nodejs-%s" % RULES_NODEJS_VERSION, - sha256 = "fccb9a7122f339d89c9994dc0fea33c737dd76e66281d0da0cb841da5f1edec7", + url = "https://github.com/bazelbuild/rules_nodejs/archive/0.5.0.zip", + strip_prefix = "rules_nodejs-0.5.0", + sha256 = "06aabb253c3867d51724386ac5622a0a238bbd82e2c70ce1d09ee3ceac4c31d6", ) load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories") @@ -16,13 +16,11 @@ load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_reposi check_bazel_version("0.9.0") node_repositories(package_json = ["//:package.json"]) -RULES_TYPESCRIPT_VERSION = "d3cc5cd72d89aee0e4c2553ae1b99c707ecbef4e" - http_archive( name = "build_bazel_rules_typescript", - url = "https://github.com/bazelbuild/rules_typescript/archive/%s.zip" % RULES_TYPESCRIPT_VERSION, - strip_prefix = "rules_typescript-%s" % RULES_TYPESCRIPT_VERSION, - sha256 = "a233fcca41c3e59f639ac71c396edb30e9e9716cf8ed5fb20b51ff8910d5d895", + url = "https://github.com/bazelbuild/rules_typescript/archive/0.11.0.zip", + strip_prefix = "rules_typescript-0.11.0", + sha256 = "ce7bac7b5287d5162fcbe4f7c14ff507ae7d506ceb44626ad09f6b7e27d3260b", ) load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace") diff --git a/integration/bazel/WORKSPACE b/integration/bazel/WORKSPACE index c680a8b74e..06ca4806eb 100644 --- a/integration/bazel/WORKSPACE +++ b/integration/bazel/WORKSPACE @@ -1,26 +1,20 @@ workspace(name = "bazel_integration_test") -# Using a pre-release snapshot to pick up a commit that makes all nodejs_binary -# programs produce source-mapped stack traces and uglify sourcemaps. -RULES_NODEJS_VERSION = "4303cbef12e5e252ad66cc35cff1123e3a44ee83" - http_archive( name = "build_bazel_rules_nodejs", - url = "https://github.com/bazelbuild/rules_nodejs/archive/%s.zip" % RULES_NODEJS_VERSION, - strip_prefix = "rules_nodejs-%s" % RULES_NODEJS_VERSION, - sha256 = "fccb9a7122f339d89c9994dc0fea33c737dd76e66281d0da0cb841da5f1edec7", + url = "https://github.com/bazelbuild/rules_nodejs/archive/0.5.0.zip", + strip_prefix = "rules_nodejs-0.5.0", + sha256 = "06aabb253c3867d51724386ac5622a0a238bbd82e2c70ce1d09ee3ceac4c31d6", ) load("@build_bazel_rules_nodejs//:defs.bzl", "node_repositories") node_repositories(package_json = ["//:package.json"]) -RULES_TYPESCRIPT_VERSION = "d3cc5cd72d89aee0e4c2553ae1b99c707ecbef4e" - http_archive( name = "build_bazel_rules_typescript", - url = "https://github.com/bazelbuild/rules_typescript/archive/%s.zip" % RULES_TYPESCRIPT_VERSION, - strip_prefix = "rules_typescript-%s" % RULES_TYPESCRIPT_VERSION, - sha256 = "a233fcca41c3e59f639ac71c396edb30e9e9716cf8ed5fb20b51ff8910d5d895", + url = "https://github.com/bazelbuild/rules_typescript/archive/0.11.0.zip", + strip_prefix = "rules_typescript-0.11.0", + sha256 = "ce7bac7b5287d5162fcbe4f7c14ff507ae7d506ceb44626ad09f6b7e27d3260b", ) load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace") diff --git a/packages/bazel/src/esm5.bzl b/packages/bazel/src/esm5.bzl index 514ed7e357..0d550c5579 100644 --- a/packages/bazel/src/esm5.bzl +++ b/packages/bazel/src/esm5.bzl @@ -113,7 +113,7 @@ esm5_outputs_aspect = aspect( # For some reason, having the compiler output as an input to the action above # is not sufficient. "_tsc_wrapped": attr.label( - default = Label("@build_bazel_rules_typescript//internal/tsc_wrapped:tsc_wrapped_bin"), + default = Label("@build_bazel_rules_typescript//internal:tsc_wrapped_bin"), executable = True, cfg = "host", ), diff --git a/packages/bazel/src/ngc-wrapped/BUILD.bazel b/packages/bazel/src/ngc-wrapped/BUILD.bazel index beca2b1f0f..75b98f6d71 100644 --- a/packages/bazel/src/ngc-wrapped/BUILD.bazel +++ b/packages/bazel/src/ngc-wrapped/BUILD.bazel @@ -16,7 +16,7 @@ ts_library( # Users will get this dependency from node_modules. "//packages/compiler-cli", # END-INTERNAL - "@build_bazel_rules_typescript//internal/tsc_wrapped", + "@build_bazel_rules_typescript//internal:tsc_wrapped", ], ) diff --git a/packages/service-worker/worker/src/driver.ts b/packages/service-worker/worker/src/driver.ts index 65c1d3c9f1..b4e046539f 100644 --- a/packages/service-worker/worker/src/driver.ts +++ b/packages/service-worker/worker/src/driver.ts @@ -243,7 +243,7 @@ export class Driver implements Debuggable, UpdateSource { } private async handlePush(data: any): Promise { - this.broadcast({ + await this.broadcast({ type: 'PUSH', data, }); @@ -254,7 +254,7 @@ export class Driver implements Debuggable, UpdateSource { let options: {[key: string]: string | undefined} = {}; NOTIFICATION_OPTION_NAMES.filter(name => desc.hasOwnProperty(name)) .forEach(name => options[name] = desc[name]); - this.scope.registration.showNotification(desc['title'] !, options); + await this.scope.registration.showNotification(desc['title'] !, options); } private async reportStatus(client: Client, promise: Promise, nonce: number): Promise { @@ -614,7 +614,7 @@ export class Driver implements Debuggable, UpdateSource { if (!res.ok) { if (res.status === 404) { await this.deleteAllCaches(); - this.scope.registration.unregister(); + await this.scope.registration.unregister(); } throw new Error('Manifest fetch failed!'); } @@ -707,7 +707,7 @@ export class Driver implements Debuggable, UpdateSource { // Firstly, check if the manifest version is correct. if (manifest.configVersion !== SUPPORTED_CONFIG_VERSION) { await this.deleteAllCaches(); - this.scope.registration.unregister(); + await this.scope.registration.unregister(); throw new Error( `Invalid config version: expected ${SUPPORTED_CONFIG_VERSION}, got ${manifest.configVersion}.`); } diff --git a/packages/service-worker/worker/test/happy_spec.ts b/packages/service-worker/worker/test/happy_spec.ts index bf0d71fd2a..994e6f326d 100644 --- a/packages/service-worker/worker/test/happy_spec.ts +++ b/packages/service-worker/worker/test/happy_spec.ts @@ -527,7 +527,7 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate)); expect(await driver.checkForUpdate()).toEqual(true); serverUpdate.assertSawRequestFor('/quux.txt'); serverUpdate.clearRequests(); - driver.updateClient(await scope.clients.get('default')); + await driver.updateClient(await scope.clients.get('default')); expect(await makeRequest(scope, '/quux.txt')).toEqual('this is quux v2'); serverUpdate.assertNoOtherRequests(); }); From d7e5d45f43ada7060047bc693b109e3011c29f95 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Thu, 1 Mar 2018 14:31:10 -0800 Subject: [PATCH 033/604] release: packageGroup should use scoped package names (#22538) PR Close #22538 --- build.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index 9b45f02815..e9334f1a59 100755 --- a/build.sh +++ b/build.sh @@ -33,8 +33,16 @@ TSC_PACKAGES=(compiler-cli NODE_PACKAGES=(compiler-cli benchpress) +SCOPED_PACKAGES=$( + for P in ${PACKAGES[@]}; do echo @angular/${P}; done +) NG_UPDATE_PACKAGE_GROUP=$( - echo \[\"${PACKAGES[@]}\"] | sed 's/ /", "/g' + # The first sed creates an array of strings + # The second sed is to allow it to be run in the perl expression so forward slashes don't end + # the regular expression. + echo \[\"${SCOPED_PACKAGES[@]}\"] \ + | sed 's/ /", "/g' \ + | sed 's/\//\\\//g' ) @@ -497,7 +505,7 @@ do rsync -am --include="*.externs.js" --include="*/" --exclude=* ${SRC_DIR}/ ${NPM_DIR}/ # Replace the NG_UPDATE_PACKAGE_GROUP value with the JSON array of packages. - perl -p -i -e "s/\"NG_UPDATE_PACKAGE_GROUP\"/${NG_UPDATE_PACKAGE_GROUP}/g" ${NPM_DIR}/package.json < /dev/null 2> /dev/null + perl -p -i -e "s/\"NG_UPDATE_PACKAGE_GROUP\"/${NG_UPDATE_PACKAGE_GROUP}/g" ${NPM_DIR}/package.json < /dev/null cp ${ROOT_DIR}/README.md ${NPM_DIR}/ fi From 25faf808a51ee547ec7787030d41c7b8b03eaf4f Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Fri, 2 Mar 2018 14:19:01 -0800 Subject: [PATCH 034/604] build: copy ts-api-guardian sources (#22544) This is an exact mirror of https://github.com/angular/ts-api-guardian/tree/750f651eca4d57a4a7ebcc12c19d0beea1a62202 PR Close #22544 --- tools/ts-api-guardian/README.md | 3 + tools/ts-api-guardian/bin/ts-api-guardian | 3 + tools/ts-api-guardian/lib/cli.ts | 189 +++++++ tools/ts-api-guardian/lib/main.ts | 37 ++ tools/ts-api-guardian/lib/serializer.ts | 338 ++++++++++++ tools/ts-api-guardian/package.json | 60 +++ tools/ts-api-guardian/test/cli_e2e_test.ts | 138 +++++ tools/ts-api-guardian/test/cli_unit_test.ts | 86 +++ .../test/fixtures/classes_and_interfaces.d.ts | 14 + .../classes_and_interfaces_expected.d.ts | 15 + .../ts-api-guardian/test/fixtures/empty.d.ts | 0 tools/ts-api-guardian/test/fixtures/empty.ts | 0 .../test/fixtures/empty_expected.d.ts | 0 .../test/fixtures/enum_as_type.d.ts | 8 + .../test/fixtures/enum_as_type_expected.d.ts | 8 + .../ts-api-guardian/test/fixtures/keyof.d.ts | 3 + .../test/fixtures/keyof_expected.d.ts | 3 + .../test/fixtures/module_identifier.d.ts | 4 + .../fixtures/module_identifier_expected.d.ts | 2 + .../test/fixtures/reexported.d.ts | 1 + .../test/fixtures/reexported_aliased.d.ts | 1 + .../test/fixtures/reexported_classes.d.ts | 1 + .../fixtures/reexported_classes_expected.d.ts | 4 + .../test/fixtures/reexported_expected.d.ts | 3 + .../test/fixtures/reexported_extern.d.ts | 5 + .../fixtures/reexported_extern_expected.d.ts | 0 .../test/fixtures/reexported_star.d.ts | 1 + .../fixtures/reexported_star_expected.d.ts | 3 + .../ts-api-guardian/test/fixtures/simple.d.ts | 2 + .../test/fixtures/simple_expected.d.ts | 3 + .../test/fixtures/sorting.d.ts | 11 + .../test/fixtures/sorting_expected.d.ts | 16 + .../test/fixtures/stripped_alias.d.ts | 3 + .../fixtures/stripped_alias_expected.d.ts | 2 + .../fixtures/stripped_alias_original.d.ts | 1 + .../test/fixtures/type_literals.d.ts | 7 + .../test/fixtures/type_literals_expected.d.ts | 7 + .../test/fixtures/underscored.d.ts | 3 + .../test/fixtures/underscored_expected.d.ts | 2 + .../test/fixtures/verify.patch | 11 + .../test/fixtures/verify_entrypoint.d.ts | 5 + .../test/fixtures/verify_expected.d.ts | 5 + .../test/fixtures/verify_submodule.d.ts | 1 + tools/ts-api-guardian/test/helpers.ts | 19 + .../ts-api-guardian/test/integration_test.ts | 132 +++++ tools/ts-api-guardian/test/unit_test.ts | 494 ++++++++++++++++++ 46 files changed, 1654 insertions(+) create mode 100644 tools/ts-api-guardian/README.md create mode 100755 tools/ts-api-guardian/bin/ts-api-guardian create mode 100644 tools/ts-api-guardian/lib/cli.ts create mode 100644 tools/ts-api-guardian/lib/main.ts create mode 100644 tools/ts-api-guardian/lib/serializer.ts create mode 100644 tools/ts-api-guardian/package.json create mode 100644 tools/ts-api-guardian/test/cli_e2e_test.ts create mode 100644 tools/ts-api-guardian/test/cli_unit_test.ts create mode 100644 tools/ts-api-guardian/test/fixtures/classes_and_interfaces.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/classes_and_interfaces_expected.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/empty.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/empty.ts create mode 100644 tools/ts-api-guardian/test/fixtures/empty_expected.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/enum_as_type.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/enum_as_type_expected.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/keyof.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/keyof_expected.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/module_identifier.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/module_identifier_expected.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/reexported.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/reexported_aliased.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/reexported_classes.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/reexported_classes_expected.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/reexported_expected.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/reexported_extern.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/reexported_extern_expected.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/reexported_star.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/reexported_star_expected.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/simple.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/simple_expected.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/sorting.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/sorting_expected.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/stripped_alias.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/stripped_alias_expected.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/stripped_alias_original.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/type_literals.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/type_literals_expected.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/underscored.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/underscored_expected.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/verify.patch create mode 100644 tools/ts-api-guardian/test/fixtures/verify_entrypoint.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/verify_expected.d.ts create mode 100644 tools/ts-api-guardian/test/fixtures/verify_submodule.d.ts create mode 100644 tools/ts-api-guardian/test/helpers.ts create mode 100644 tools/ts-api-guardian/test/integration_test.ts create mode 100644 tools/ts-api-guardian/test/unit_test.ts diff --git a/tools/ts-api-guardian/README.md b/tools/ts-api-guardian/README.md new file mode 100644 index 0000000000..0de5176121 --- /dev/null +++ b/tools/ts-api-guardian/README.md @@ -0,0 +1,3 @@ +# Typescript API Guardian + +Keeps track of public API surface of a typescript library. diff --git a/tools/ts-api-guardian/bin/ts-api-guardian b/tools/ts-api-guardian/bin/ts-api-guardian new file mode 100755 index 0000000000..4dc9563ac2 --- /dev/null +++ b/tools/ts-api-guardian/bin/ts-api-guardian @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +require('../build/lib/cli').startCli(); diff --git a/tools/ts-api-guardian/lib/cli.ts b/tools/ts-api-guardian/lib/cli.ts new file mode 100644 index 0000000000..8b0c513e92 --- /dev/null +++ b/tools/ts-api-guardian/lib/cli.ts @@ -0,0 +1,189 @@ +import chalk from 'chalk'; +import * as minimist from 'minimist'; +import {ParsedArgs} from 'minimist'; +import * as path from 'path'; + +import {SerializationOptions, generateGoldenFile, verifyAgainstGoldenFile} from './main'; + +// Examples: +// +// ```sh +// # Generate one declaration file +// ts-api-guardian --out api_guard.d.ts index.d.ts +// +// # Generate multiple declaration files // # (output location like typescript) +// ts-api-guardian --outDir api_guard [--rootDir .] core/index.d.ts core/testing.d.ts +// +// # Print usage +// ts-api-guardian --help +// +// # Check against one declaration file +// ts-api-guardian --verify api_guard.d.ts index.d.ts +// +// # Check against multiple declaration files +// ts-api-guardian --verifyDir api_guard [--rootDir .] core/index.d.ts core/testing.d.ts +// ``` + +const CMD = 'ts-api-guardian'; + +export function startCli() { + const {argv, mode, errors} = parseArguments(process.argv.slice(2)); + + const options: SerializationOptions = { + stripExportPattern: argv['stripExportPattern'], + allowModuleIdentifiers: [].concat(argv['allowModuleIdentifiers']), + onStabilityMissing: argv['onStabilityMissing'] || 'none' + }; + + if (['warn', 'error', 'none'].indexOf(options.onStabilityMissing) < 0) { + throw new Error( + 'Argument for "--onStabilityMissing" option must be one of: "warn", "error", "none"'); + } + + for (const error of errors) { + console.warn(error); + } + + if (mode === 'help') { + printUsageAndExit(!!errors.length); + } else { + const targets = generateFileNamePairs(argv, mode); + + if (mode === 'out') { + for (const {entrypoint, goldenFile} of targets) { + generateGoldenFile(entrypoint, goldenFile, options); + } + } else { // mode === 'verify' + let hasDiff = false; + + for (const {entrypoint, goldenFile} of targets) { + const diff = verifyAgainstGoldenFile(entrypoint, goldenFile, options); + if (diff) { + hasDiff = true; + const lines = diff.split('\n'); + if (lines.length) { + lines.pop(); // Remove trailing newline + } + for (const line of lines) { + const chalkMap = {'-': chalk.red, '+': chalk.green, '@': chalk.cyan}; + const chalkFunc = chalkMap[line[0]] || chalk.reset; + console.log(chalkFunc(line)); + } + } + } + + if (hasDiff) { + process.exit(1); + } + } + } +} + +export function parseArguments(input: string[]): + {argv: ParsedArgs, mode: string, errors?: string[]} { + let help = false; + const errors = []; + + const argv = minimist(input, { + string: [ + 'out', 'outDir', 'verify', 'verifyDir', 'rootDir', 'stripExportPattern', + 'allowModuleIdentifiers', 'onStabilityMissing' + ], + boolean: [ + 'help', + // Options used by chalk automagically + 'color', 'no-color' + ], + alias: {'outFile': 'out', 'verifyFile': 'verify'}, + unknown: option => { + if (option[0] === '-') { + errors.push(`Unknown option: ${option}`); + help = true; + return false; // do not add to argv._ + } else { + return true; // add to argv._ + } + } + }); + + help = help || argv['help']; + + if (help) { + return {argv, mode: 'help', errors}; + } + + let modes: string[] = []; + + if (argv['out']) { + modes.push('out'); + } + if (argv['outDir']) { + modes.push('out'); + } + if (argv['verify']) { + modes.push('verify'); + } + if (argv['verifyDir']) { + modes.push('verify'); + } + + if (!argv._.length) { + errors.push('No input file specified.'); + modes = ['help']; + } else if (modes.length !== 1) { + errors.push('Specify either --out[Dir] or --verify[Dir]'); + modes = ['help']; + } else if (argv._.length > 1 && !argv['outDir'] && !argv['verifyDir']) { + errors.push(`More than one input specified. Use --${modes[0]}Dir instead.`); + modes = ['help']; + } + + return {argv, mode: modes[0], errors}; +} + +function printUsageAndExit(error = false) { + const print = error ? console.warn.bind(console) : console.log.bind(console); + print(`Usage: ${CMD} [options] + ${CMD} --out + ${CMD} --outDir [--rootDir .] + + ${CMD} --verify + ${CMD} --verifyDir [--rootDir .] + +Options: + --help Show this usage message + + --out Write golden output to file + --outDir Write golden file structure to directory + + --verify Read golden input from file + --verifyDir Read golden file structure from directory + + --rootDir Specify the root directory of input files + + --stripExportPattern Do not output exports matching the pattern + --allowModuleIdentifiers + Whitelist identifier for "* as foo" imports + --onStabilityMissing + Warn or error if an export has no stability + annotation`); + process.exit(error ? 1 : 0); +} + +export function generateFileNamePairs( + argv: ParsedArgs, mode: string): {entrypoint: string, goldenFile: string}[] { + if (argv[mode]) { + return [{entrypoint: argv._[0], goldenFile: argv[mode]}]; + + } else { // argv[mode + 'Dir'] + let rootDir = argv['rootDir'] || '.'; + const goldenDir = argv[mode + 'Dir']; + + return argv._.map(fileName => { + return { + entrypoint: fileName, + goldenFile: path.join(goldenDir, path.relative(rootDir, fileName)) + }; + }); + } +} diff --git a/tools/ts-api-guardian/lib/main.ts b/tools/ts-api-guardian/lib/main.ts new file mode 100644 index 0000000000..86aa5f86f7 --- /dev/null +++ b/tools/ts-api-guardian/lib/main.ts @@ -0,0 +1,37 @@ +import {createPatch} from 'diff'; +import * as fs from 'fs'; +import * as path from 'path'; +import {SerializationOptions, publicApi} from './serializer'; + +export {SerializationOptions, publicApi} from './serializer'; + +export function generateGoldenFile( + entrypoint: string, outFile: string, options: SerializationOptions = {}): void { + const output = publicApi(entrypoint, options); + ensureDirectory(path.dirname(outFile)); + fs.writeFileSync(outFile, output); +} + +export function verifyAgainstGoldenFile( + entrypoint: string, goldenFile: string, options: SerializationOptions = {}): string { + const actual = publicApi(entrypoint, options); + const expected = fs.readFileSync(goldenFile).toString(); + + if (actual === expected) { + return ''; + } else { + const patch = createPatch(goldenFile, expected, actual, 'Golden file', 'Generated API'); + + // Remove the header of the patch + const start = patch.indexOf('\n', patch.indexOf('\n') + 1) + 1; + + return patch.substring(start); + } +} + +function ensureDirectory(dir: string) { + if (!fs.existsSync(dir)) { + ensureDirectory(path.dirname(dir)); + fs.mkdirSync(dir); + } +} diff --git a/tools/ts-api-guardian/lib/serializer.ts b/tools/ts-api-guardian/lib/serializer.ts new file mode 100644 index 0000000000..bb9bda2665 --- /dev/null +++ b/tools/ts-api-guardian/lib/serializer.ts @@ -0,0 +1,338 @@ +import * as path from 'path'; +import * as ts from 'typescript'; + +const baseTsOptions: ts.CompilerOptions = { + // We don't want symbols from external modules to be resolved, so we use the + // classic algorithm. + moduleResolution: ts.ModuleResolutionKind.Classic +}; + +export interface SerializationOptions { + /** + * Removes all exports matching the regular expression. + */ + stripExportPattern?: RegExp; + /** + * Whitelists these identifiers as modules in the output. For example, + * ``` + * import * as angular from './angularjs'; + * + * export class Foo extends angular.Bar {} + * ``` + * will produce `export class Foo extends angular.Bar {}` and requires + * whitelisting angular. + */ + allowModuleIdentifiers?: string[]; + /** + * Warns or errors if stability annotations are missing on an export. + * Supports experimental, stable and deprecated. + */ + onStabilityMissing?: DiagnosticSeverity; +} + +export type DiagnosticSeverity = 'warn' | 'error' | 'none'; + +export function publicApi(fileName: string, options: SerializationOptions = {}): string { + return publicApiInternal(ts.createCompilerHost(baseTsOptions), fileName, baseTsOptions, options); +} + +export function publicApiInternal( + host: ts.CompilerHost, fileName: string, tsOptions: ts.CompilerOptions, + options: SerializationOptions = {}): string { + const entrypoint = path.normalize(fileName); + + if (!entrypoint.match(/\.d\.ts$/)) { + throw new Error(`Source file "${fileName}" is not a declaration file`); + } + + const program = ts.createProgram([entrypoint], tsOptions, host); + return new ResolvedDeclarationEmitter(program, entrypoint, options).emit(); +} + +interface Diagnostic { + type: DiagnosticSeverity; + message: string; +} + +class ResolvedDeclarationEmitter { + private program: ts.Program; + private fileName: string; + private typeChecker: ts.TypeChecker; + private options: SerializationOptions; + private diagnostics: Diagnostic[]; + + constructor(program: ts.Program, fileName: string, options: SerializationOptions) { + this.program = program; + this.fileName = fileName; + this.options = options; + this.diagnostics = []; + + this.typeChecker = this.program.getTypeChecker(); + } + + emit(): string { + const sourceFile = this.program.getSourceFiles().find(sf => sf.fileName === this.fileName); + if (!sourceFile) { + throw new Error(`Source file "${this.fileName}" not found`); + } + + let output = ''; + + const resolvedSymbols = this.getResolvedSymbols(sourceFile); + // Sort all symbols so that the output is more deterministic + resolvedSymbols.sort(symbolCompareFunction); + + for (const symbol of resolvedSymbols) { + if (this.options.stripExportPattern && symbol.name.match(this.options.stripExportPattern)) { + continue; + } + + let decl: ts.Node = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0]; + if (!decl) { + this.diagnostics.push({ + type: 'warn', + message: `${sourceFile.fileName}: error: No declaration found for symbol "${symbol.name}"` + }); + continue; + } + + // The declaration node may not be a complete statement, e.g. for var/const + // symbols. We need to find the complete export statement by traversing + // upwards. + while (!hasModifier(decl, ts.SyntaxKind.ExportKeyword) && decl.parent) { + decl = decl.parent; + } + + if (hasModifier(decl, ts.SyntaxKind.ExportKeyword)) { + // Make an empty line between two exports + if (output) { + output += '\n'; + } + + // Print stability annotation + const sourceText = decl.getSourceFile().text; + const trivia = sourceText.substr(decl.pos, decl.getLeadingTriviaWidth()); + const match = stabilityAnnotationPattern.exec(trivia); + if (match) { + output += `/** @${match[1]} */\n`; + } else if (['warn', 'error'].indexOf(this.options.onStabilityMissing) >= 0) { + this.diagnostics.push({ + type: this.options.onStabilityMissing, + message: createErrorMessage( + decl, `No stability annotation found for symbol "${symbol.name}"`) + }); + } + + output += stripEmptyLines(this.emitNode(decl)) + '\n'; + } else { + // This may happen for symbols re-exported from external modules. + this.diagnostics.push({ + type: 'warn', + message: + createErrorMessage(decl, `No export declaration found for symbol "${symbol.name}"`) + }); + } + } + + if (this.diagnostics.length) { + const message = this.diagnostics.map(d => d.message).join('\n'); + console.warn(message); + if (this.diagnostics.some(d => d.type === 'error')) { + throw new Error(message); + } + } + + return output; + } + + private getResolvedSymbols(sourceFile: ts.SourceFile): ts.Symbol[] { + const ms = (sourceFile).symbol; + const rawSymbols = ms ? (this.typeChecker.getExportsOfModule(ms) || []) : []; + return rawSymbols.map(s => { + if (s.flags & ts.SymbolFlags.Alias) { + const resolvedSymbol = this.typeChecker.getAliasedSymbol(s); + + // This will happen, e.g. for symbols re-exported from external modules. + if (!resolvedSymbol.valueDeclaration && !resolvedSymbol.declarations) { + return s; + } + if (resolvedSymbol.name !== s.name) { + if (this.options.stripExportPattern && s.name.match(this.options.stripExportPattern)) { + return s; + } + throw new Error( + `Symbol "${resolvedSymbol.name}" was aliased as "${s.name}". ` + + `Aliases are not supported."`); + } + + return resolvedSymbol; + } else { + return s; + } + }); + } + + emitNode(node: ts.Node) { + if (hasModifier(node, ts.SyntaxKind.PrivateKeyword)) { + return ''; + } + + const firstQualifier: ts.Identifier = getFirstQualifier(node); + + if (firstQualifier) { + let isAllowed = false; + + // Try to resolve the qualifier. + const resolvedSymbol = this.typeChecker.getSymbolAtLocation(firstQualifier); + if (resolvedSymbol && resolvedSymbol.declarations.length > 0) { + // If the qualifier can be resolved, and it's not a namespaced import, then it should be allowed. + isAllowed = resolvedSymbol.declarations.every(decl => decl.kind !== ts.SyntaxKind.NamespaceImport); + } + + // If it is not allowed otherwise, it's allowed if it's on the list of allowed identifiers. + isAllowed = isAllowed || !(!this.options.allowModuleIdentifiers || + this.options.allowModuleIdentifiers.indexOf(firstQualifier.text) < 0); + if (!isAllowed) { + this.diagnostics.push({ + type: 'error', + message: createErrorMessage( + firstQualifier, + `Module identifier "${firstQualifier.text}" is not allowed. Remove it ` + + `from source or whitelist it via --allowModuleIdentifiers.`) + }); + } + } + + let children = node.getChildren(); + const sourceText = node.getSourceFile().text; + if (children.length) { + // Sort declarations under a class or an interface + if (node.kind === ts.SyntaxKind.SyntaxList) { + switch (node.parent && node.parent.kind) { + case ts.SyntaxKind.ClassDeclaration: + case ts.SyntaxKind.InterfaceDeclaration: { + // There can be multiple SyntaxLists under a class or an interface, + // since SyntaxList is just an arbitrary data structure generated + // by Node#getChildren(). We need to check that we are sorting the + // right list. + if (children.every(node => node.kind in memberDeclarationOrder)) { + children = children.slice(); + children.sort((a: ts.NamedDeclaration, b: ts.NamedDeclaration) => { + // Static after normal + return compareFunction( + hasModifier(a, ts.SyntaxKind.StaticKeyword), + hasModifier(b, ts.SyntaxKind.StaticKeyword) + ) || + // Our predefined order + compareFunction( + memberDeclarationOrder[a.kind], memberDeclarationOrder[b.kind]) || + // Alphebetical order + // We need safe dereferencing due to edge cases, e.g. having two call signatures + compareFunction((a.name || a).getText(), (b.name || b).getText()); + }); + } + break; + } + } + } + + let output = children + .filter(x => x.kind !== ts.SyntaxKind.JSDocComment) + .map(n => this.emitNode(n)) + .join(''); + + // Print stability annotation for fields + if (node.kind in memberDeclarationOrder) { + const trivia = sourceText.substr(node.pos, node.getLeadingTriviaWidth()); + const match = stabilityAnnotationPattern.exec(trivia); + if (match) { + // Add the annotation after the leading whitespace + output = output.replace(/^(\n\s*)/, `$1/** @${match[1]} */ `); + } + } + + return output; + } else { + const ranges = ts.getLeadingCommentRanges(sourceText, node.pos); + let tail = node.pos; + for (const range of ranges || []) { + if (range.end > tail) { + tail = range.end; + } + } + return sourceText.substring(tail, node.end); + } + } +} + +function symbolCompareFunction(a: ts.Symbol, b: ts.Symbol) { + return a.name.localeCompare(b.name); +} + +function compareFunction(a: T, b: T) { + return a === b ? 0 : a > b ? 1 : -1; +} + +const memberDeclarationOrder = { + [ts.SyntaxKind.PropertySignature]: 0, + [ts.SyntaxKind.PropertyDeclaration]: 0, + [ts.SyntaxKind.GetAccessor]: 0, + [ts.SyntaxKind.SetAccessor]: 0, + [ts.SyntaxKind.CallSignature]: 1, + [ts.SyntaxKind.Constructor]: 2, + [ts.SyntaxKind.ConstructSignature]: 2, + [ts.SyntaxKind.IndexSignature]: 3, + [ts.SyntaxKind.MethodSignature]: 4, + [ts.SyntaxKind.MethodDeclaration]: 4 +}; + +const stabilityAnnotationPattern = /@(experimental|stable|deprecated)\b/; + +function stripEmptyLines(text: string): string { + return text.split('\n').filter(x => !!x.length).join('\n'); +} + +/** + * Returns the first qualifier if the input node is a dotted expression. + */ +function getFirstQualifier(node: ts.Node): ts.Identifier { + switch (node.kind) { + case ts.SyntaxKind.PropertyAccessExpression: { + // For expression position + let lhs = node; + do { + lhs = (lhs).expression; + } while (lhs && lhs.kind !== ts.SyntaxKind.Identifier); + + return lhs; + } + case ts.SyntaxKind.TypeReference: { + // For type position + let lhs: ts.Node = (node).typeName; + do { + lhs = (lhs).left; + } while (lhs && lhs.kind !== ts.SyntaxKind.Identifier); + + return lhs; + } + default: + return null; + } +} + +function createErrorMessage(node: ts.Node, message: string): string { + const sourceFile = node.getSourceFile(); + let position; + if (sourceFile) { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + position = `${sourceFile.fileName}(${line + 1},${character + 1})`; + } else { + position = ''; + } + + return `${position}: error: ${message}`; +} + +function hasModifier(node: ts.Node, modifierKind: ts.SyntaxKind): boolean { + return !!node.modifiers && node.modifiers.some(x => x.kind === modifierKind); +} diff --git a/tools/ts-api-guardian/package.json b/tools/ts-api-guardian/package.json new file mode 100644 index 0000000000..7873bf0494 --- /dev/null +++ b/tools/ts-api-guardian/package.json @@ -0,0 +1,60 @@ +{ + "name": "ts-api-guardian", + "version": "0.3.0", + "description": "Guards the API of TypeScript libraries!", + "main": "build/lib/main.js", + "typings": "build/definitions/main.d.ts", + "bin": { + "ts-api-guardian": "./bin/ts-api-guardian" + }, + "directories": { + "test": "test" + }, + "peerDependencies": { + "typescript": "^2.6.1" + }, + "dependencies": { + "chalk": "^2.3.1", + "diff": "^3.4.0", + "minimist": "^1.2.0" + }, + "devDependencies": { + "@types/chai": "^4.1.2", + "@types/diff": "^3.2.2", + "@types/minimist": "^1.2.0", + "@types/mocha": "^2.2.48", + "@types/node": "^0.12.15", + "chai": "^4.1.2", + "clang-format": "^1.0.25", + "gulp": "^3.8.11", + "gulp-clang-format": "^1.0.25", + "gulp-mocha": "^5.0.0", + "gulp-sourcemaps": "^2.6.4", + "gulp-typescript": "^4.0.1", + "gulp-util": "^3.0.8", + "merge2": "^1.2.1", + "source-map": "^0.7.1", + "source-map-support": "^0.5.3", + "typescript": "~2.6.2" + }, + "scripts": { + "prepublish": "gulp compile", + "test": "gulp test.unit" + }, + "repository": {}, + "keywords": [ + "typescript" + ], + "contributors": [ + "Alan Agius (https://github.com/alan-agius4/)", + "Alex Eagle (https://angular.io/)", + "Martin Probst (https://angular.io/)", + "Victor Savkin (https://victorsavkin.com)", + "Igor Minar (https://angular.io/)" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/angular/ts-api-guardian/issues" + }, + "homepage": "https://github.com/angular/ts-api-guardian" +} diff --git a/tools/ts-api-guardian/test/cli_e2e_test.ts b/tools/ts-api-guardian/test/cli_e2e_test.ts new file mode 100644 index 0000000000..81fb4b10b6 --- /dev/null +++ b/tools/ts-api-guardian/test/cli_e2e_test.ts @@ -0,0 +1,138 @@ +import chai = require('chai'); +import * as child_process from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import {assertFileEqual, unlinkRecursively} from './helpers'; + +const BINARY = path.resolve(__dirname, '../../bin/ts-api-guardian'); + +describe('cli: e2e test', () => { + const outDir = path.resolve(__dirname, '../../build/tmp'); + + beforeEach(() => { + if (!fs.existsSync(outDir)) { + fs.mkdirSync(outDir); + } + }); + + afterEach(() => { unlinkRecursively(outDir); }); + + it('should print usage without any argument', () => { + const {stderr} = execute([]); + chai.assert.match(stderr, /Usage/); + }); + + it('should show help message with --help', () => { + const {stdout} = execute(['--help']); + chai.assert.match(stdout, /Usage/); + }); + + it('should generate golden file with --out', () => { + const simpleFile = path.join(outDir, 'simple.d.ts'); + const {status} = execute(['--out', simpleFile, 'test/fixtures/simple.d.ts']); + chai.assert.equal(status, 0); + assertFileEqual(simpleFile, 'test/fixtures/simple_expected.d.ts'); + }); + + it('should verify golden file with --verify and exit cleanly on no difference', () => { + const {stdout, status} = + execute(['--verify', 'test/fixtures/simple_expected.d.ts', 'test/fixtures/simple.d.ts']); + chai.assert.equal(stdout, ''); + chai.assert.equal(status, 0); + }); + + it('should verify golden file with --verify and exit with error on difference', () => { + const {stdout, status} = execute( + ['--verify', 'test/fixtures/verify_expected.d.ts', 'test/fixtures/verify_entrypoint.d.ts']); + chai.assert.equal(stdout, fs.readFileSync('test/fixtures/verify.patch').toString()); + chai.assert.equal(status, 1); + }); + + it('should generate multiple golden files with --outDir and --rootDir', () => { + const {status} = execute([ + '--outDir', outDir, '--rootDir', 'test/fixtures', 'test/fixtures/simple.d.ts', + 'test/fixtures/sorting.d.ts' + ]); + chai.assert.equal(status, 0); + assertFileEqual(path.join(outDir, 'simple.d.ts'), 'test/fixtures/simple_expected.d.ts'); + assertFileEqual(path.join(outDir, 'sorting.d.ts'), 'test/fixtures/sorting_expected.d.ts'); + }); + + it('should verify multiple golden files with --verifyDir and --rootDir', () => { + copyFile('test/fixtures/simple_expected.d.ts', path.join(outDir, 'simple.d.ts')); + copyFile('test/fixtures/sorting_expected.d.ts', path.join(outDir, 'sorting.d.ts')); + const {stdout, status} = execute([ + '--verifyDir', outDir, '--rootDir', 'test/fixtures', 'test/fixtures/simple.d.ts', + 'test/fixtures/sorting.d.ts' + ]); + chai.assert.equal(stdout, ''); + chai.assert.equal(status, 0); + }); + + it('should generate respecting --stripExportPattern', () => { + const {stdout, status} = execute([ + '--out', path.join(outDir, 'underscored.d.ts'), '--stripExportPattern', '^__.*', + 'test/fixtures/underscored.d.ts' + ]); + chai.assert.equal(status, 0); + assertFileEqual( + path.join(outDir, 'underscored.d.ts'), 'test/fixtures/underscored_expected.d.ts'); + }); + + it('should not throw for aliased stripped exports', () => { + const {stdout, status} = execute([ + '--out', path.join(outDir, 'stripped_alias.d.ts'), '--stripExportPattern', '^__.*', + 'test/fixtures/stripped_alias.d.ts' + ]); + chai.assert.equal(status, 0); + assertFileEqual( + path.join(outDir, 'stripped_alias.d.ts'), 'test/fixtures/stripped_alias_expected.d.ts'); + }); + + it('should verify respecting --stripExportPattern', () => { + const {stdout, status} = execute([ + '--verify', 'test/fixtures/underscored_expected.d.ts', 'test/fixtures/underscored.d.ts', + '--stripExportPattern', '^__.*' + ]); + chai.assert.equal(stdout, ''); + chai.assert.equal(status, 0); + }); + + it('should respect --allowModuleIdentifiers', () => { + const {stdout, status} = execute([ + '--verify', 'test/fixtures/module_identifier_expected.d.ts', '--allowModuleIdentifiers', + 'foo', 'test/fixtures/module_identifier.d.ts' + ]); + chai.assert.equal(stdout, ''); + chai.assert.equal(status, 0); + }); + + it('should respect --onStabilityMissing', () => { + const {stdout, stderr, status} = execute([ + '--verify', 'test/fixtures/simple_expected.d.ts', '--onStabilityMissing', 'warn', + 'test/fixtures/simple.d.ts' + ]); + chai.assert.equal(stdout, ''); + chai.assert.equal( + stderr, + 'test/fixtures/simple.d.ts(1,1): error: No stability annotation found for symbol "A"\n' + + 'test/fixtures/simple.d.ts(2,1): error: No stability annotation found for symbol "B"\n'); + chai.assert.equal(status, 0); + }); +}); + +function copyFile(sourceFile: string, targetFile: string) { + fs.writeFileSync(targetFile, fs.readFileSync(sourceFile)); +} + +function execute(args: string[]): {stdout: string, stderr: string, status: number} { + const output = child_process.spawnSync(BINARY, args); + chai.assert(!output.error, 'Child process failed or timed out'); + chai.assert(!output.signal, `Child process killed by signal ${output.signal}`); + + return { + stdout: output.stdout.toString(), + stderr: output.stderr.toString(), + status: output.status + }; +} diff --git a/tools/ts-api-guardian/test/cli_unit_test.ts b/tools/ts-api-guardian/test/cli_unit_test.ts new file mode 100644 index 0000000000..8e55cf476a --- /dev/null +++ b/tools/ts-api-guardian/test/cli_unit_test.ts @@ -0,0 +1,86 @@ +import chai = require('chai'); +import * as ts from 'typescript'; +import {parseArguments, generateFileNamePairs} from '../lib/cli'; + + +describe('cli: parseArguments', () => { + it('should show usage with error when supplied with no arguments', () => { + const {argv, mode, errors} = parseArguments([]); + chai.assert.equal(mode, 'help'); + chai.assert.deepEqual(errors, ['No input file specified.']); + }); + + it('should show usage without error when supplied with --help', () => { + const {argv, mode, errors} = parseArguments(['--help']); + chai.assert.equal(mode, 'help'); + chai.assert.deepEqual(errors, []); + }); + + it('should show usage with error when supplied with none of --out/verify[Dir]', () => { + const {argv, mode, errors} = parseArguments(['input.d.ts']); + chai.assert.equal(mode, 'help'); + chai.assert.deepEqual(errors, ['Specify either --out[Dir] or --verify[Dir]']); + }); + + it('should show usage with error when supplied with both of --out/verify[Dir]', () => { + const {argv, mode, errors} = + parseArguments(['--out', 'out.d.ts', '--verifyDir', 'golden.d.ts', 'input.d.ts']); + chai.assert.equal(mode, 'help'); + chai.assert.deepEqual(errors, ['Specify either --out[Dir] or --verify[Dir]']); + }); + + it('should show usage with error when supplied without input file', () => { + const {argv, mode, errors} = parseArguments(['--out', 'output.d.ts']); + chai.assert.equal(mode, 'help'); + chai.assert.deepEqual(errors, ['No input file specified.']); + }); + + it('should show usage with error when supplied without input file', () => { + const {argv, mode, errors} = + parseArguments(['--out', 'output.d.ts', 'first.d.ts', 'second.d.ts']); + chai.assert.equal(mode, 'help'); + chai.assert.deepEqual(errors, ['More than one input specified. Use --outDir instead.']); + }); + + it('should use out mode when supplied with --out', () => { + const {argv, mode, errors} = parseArguments(['--out', 'out.d.ts', 'input.d.ts']); + chai.assert.equal(argv['out'], 'out.d.ts'); + chai.assert.deepEqual(argv._, ['input.d.ts']); + chai.assert.equal(mode, 'out'); + chai.assert.deepEqual(errors, []); + }); + + it('should use verify mode when supplied with --verify', () => { + const {argv, mode, errors} = parseArguments(['--verify', 'out.d.ts', 'input.d.ts']); + chai.assert.equal(argv['verify'], 'out.d.ts'); + chai.assert.deepEqual(argv._, ['input.d.ts']); + chai.assert.equal(mode, 'verify'); + chai.assert.deepEqual(errors, []); + }); +}); + +describe('cli: generateFileNamePairs', () => { + it('should generate one file pair in one-file mode', () => { + chai.assert.deepEqual( + generateFileNamePairs({_: ['input.d.ts'], out: 'output.d.ts'}, 'out'), + [{entrypoint: 'input.d.ts', goldenFile: 'output.d.ts'}]); + }); + + it('should generate file pairs in multi-file mode according to current directory', () => { + chai.assert.deepEqual( + generateFileNamePairs({_: ['src/first.d.ts', 'src/second.d.ts'], outDir: 'bank'}, 'out'), [ + {entrypoint: 'src/first.d.ts', goldenFile: 'bank/src/first.d.ts'}, + {entrypoint: 'src/second.d.ts', goldenFile: 'bank/src/second.d.ts'} + ]); + }); + + it('should generate file pairs according to rootDir if provided', () => { + chai.assert.deepEqual( + generateFileNamePairs( + {_: ['src/first.d.ts', 'src/second.d.ts'], outDir: 'bank', rootDir: 'src'}, 'out'), + [ + {entrypoint: 'src/first.d.ts', goldenFile: 'bank/first.d.ts'}, + {entrypoint: 'src/second.d.ts', goldenFile: 'bank/second.d.ts'} + ]); + }); +}); diff --git a/tools/ts-api-guardian/test/fixtures/classes_and_interfaces.d.ts b/tools/ts-api-guardian/test/fixtures/classes_and_interfaces.d.ts new file mode 100644 index 0000000000..485e4ef31a --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/classes_and_interfaces.d.ts @@ -0,0 +1,14 @@ +export declare class A { + field: string; + method(a: string): number; +} +export interface B { + field: A; +} +export declare class C { + private privateProp; + propWithDefault: number; + protected protectedProp: number; + someProp: string; + constructor(someProp: string, propWithDefault: number, privateProp: any, protectedProp: number); +} diff --git a/tools/ts-api-guardian/test/fixtures/classes_and_interfaces_expected.d.ts b/tools/ts-api-guardian/test/fixtures/classes_and_interfaces_expected.d.ts new file mode 100644 index 0000000000..6c37af1a01 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/classes_and_interfaces_expected.d.ts @@ -0,0 +1,15 @@ +export declare class A { + field: string; + method(a: string): number; +} + +export interface B { + field: A; +} + +export declare class C { + propWithDefault: number; + protected protectedProp: number; + someProp: string; + constructor(someProp: string, propWithDefault: number, privateProp: any, protectedProp: number); +} diff --git a/tools/ts-api-guardian/test/fixtures/empty.d.ts b/tools/ts-api-guardian/test/fixtures/empty.d.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/ts-api-guardian/test/fixtures/empty.ts b/tools/ts-api-guardian/test/fixtures/empty.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/ts-api-guardian/test/fixtures/empty_expected.d.ts b/tools/ts-api-guardian/test/fixtures/empty_expected.d.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/ts-api-guardian/test/fixtures/enum_as_type.d.ts b/tools/ts-api-guardian/test/fixtures/enum_as_type.d.ts new file mode 100644 index 0000000000..db4a3c65e7 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/enum_as_type.d.ts @@ -0,0 +1,8 @@ +export declare enum Foo { + Alpha = 0, + Beta = 1, +} + +export interface Bar { + field: Foo.Alpha, +} \ No newline at end of file diff --git a/tools/ts-api-guardian/test/fixtures/enum_as_type_expected.d.ts b/tools/ts-api-guardian/test/fixtures/enum_as_type_expected.d.ts new file mode 100644 index 0000000000..63e8c6ba32 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/enum_as_type_expected.d.ts @@ -0,0 +1,8 @@ +export interface Bar { + field: Foo.Alpha, +} + +export declare enum Foo { + Alpha = 0, + Beta = 1, +} diff --git a/tools/ts-api-guardian/test/fixtures/keyof.d.ts b/tools/ts-api-guardian/test/fixtures/keyof.d.ts new file mode 100644 index 0000000000..c2b6c1749c --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/keyof.d.ts @@ -0,0 +1,3 @@ +export declare type SimpleChanges = { + [P in keyof T]?: any; +}; \ No newline at end of file diff --git a/tools/ts-api-guardian/test/fixtures/keyof_expected.d.ts b/tools/ts-api-guardian/test/fixtures/keyof_expected.d.ts new file mode 100644 index 0000000000..b044702368 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/keyof_expected.d.ts @@ -0,0 +1,3 @@ +export declare type SimpleChanges = { + [P in keyof T]?: any; +}; diff --git a/tools/ts-api-guardian/test/fixtures/module_identifier.d.ts b/tools/ts-api-guardian/test/fixtures/module_identifier.d.ts new file mode 100644 index 0000000000..fa50910435 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/module_identifier.d.ts @@ -0,0 +1,4 @@ +import * as foo from './somewhere'; + +export declare class A extends foo.Bar { +} diff --git a/tools/ts-api-guardian/test/fixtures/module_identifier_expected.d.ts b/tools/ts-api-guardian/test/fixtures/module_identifier_expected.d.ts new file mode 100644 index 0000000000..174b833508 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/module_identifier_expected.d.ts @@ -0,0 +1,2 @@ +export declare class A extends foo.Bar { +} diff --git a/tools/ts-api-guardian/test/fixtures/reexported.d.ts b/tools/ts-api-guardian/test/fixtures/reexported.d.ts new file mode 100644 index 0000000000..b583f44e4f --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/reexported.d.ts @@ -0,0 +1 @@ +export { A, B } from './simple'; diff --git a/tools/ts-api-guardian/test/fixtures/reexported_aliased.d.ts b/tools/ts-api-guardian/test/fixtures/reexported_aliased.d.ts new file mode 100644 index 0000000000..058677bf1b --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/reexported_aliased.d.ts @@ -0,0 +1 @@ +export { A as Apple } from './classes_and_interfaces'; diff --git a/tools/ts-api-guardian/test/fixtures/reexported_classes.d.ts b/tools/ts-api-guardian/test/fixtures/reexported_classes.d.ts new file mode 100644 index 0000000000..01a7a03a10 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/reexported_classes.d.ts @@ -0,0 +1 @@ +export { A } from './classes_and_interfaces'; diff --git a/tools/ts-api-guardian/test/fixtures/reexported_classes_expected.d.ts b/tools/ts-api-guardian/test/fixtures/reexported_classes_expected.d.ts new file mode 100644 index 0000000000..b8c5750924 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/reexported_classes_expected.d.ts @@ -0,0 +1,4 @@ +export declare class A { + field: string; + method(a: string): number; +} diff --git a/tools/ts-api-guardian/test/fixtures/reexported_expected.d.ts b/tools/ts-api-guardian/test/fixtures/reexported_expected.d.ts new file mode 100644 index 0000000000..7020ff3c3f --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/reexported_expected.d.ts @@ -0,0 +1,3 @@ +export declare const A: string; + +export declare var B: string; diff --git a/tools/ts-api-guardian/test/fixtures/reexported_extern.d.ts b/tools/ts-api-guardian/test/fixtures/reexported_extern.d.ts new file mode 100644 index 0000000000..0c0d859899 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/reexported_extern.d.ts @@ -0,0 +1,5 @@ +/** + * We want to ensure that external modules are not resolved. Typescript happens + * to be conveniently available in our environment. + */ +export { CompilerHost } from 'typescript'; diff --git a/tools/ts-api-guardian/test/fixtures/reexported_extern_expected.d.ts b/tools/ts-api-guardian/test/fixtures/reexported_extern_expected.d.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/ts-api-guardian/test/fixtures/reexported_star.d.ts b/tools/ts-api-guardian/test/fixtures/reexported_star.d.ts new file mode 100644 index 0000000000..882a36dbe8 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/reexported_star.d.ts @@ -0,0 +1 @@ +export * from './simple'; diff --git a/tools/ts-api-guardian/test/fixtures/reexported_star_expected.d.ts b/tools/ts-api-guardian/test/fixtures/reexported_star_expected.d.ts new file mode 100644 index 0000000000..7020ff3c3f --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/reexported_star_expected.d.ts @@ -0,0 +1,3 @@ +export declare const A: string; + +export declare var B: string; diff --git a/tools/ts-api-guardian/test/fixtures/simple.d.ts b/tools/ts-api-guardian/test/fixtures/simple.d.ts new file mode 100644 index 0000000000..191760e7a0 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/simple.d.ts @@ -0,0 +1,2 @@ +export declare const A: string; +export declare var B: string; diff --git a/tools/ts-api-guardian/test/fixtures/simple_expected.d.ts b/tools/ts-api-guardian/test/fixtures/simple_expected.d.ts new file mode 100644 index 0000000000..7020ff3c3f --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/simple_expected.d.ts @@ -0,0 +1,3 @@ +export declare const A: string; + +export declare var B: string; diff --git a/tools/ts-api-guardian/test/fixtures/sorting.d.ts b/tools/ts-api-guardian/test/fixtures/sorting.d.ts new file mode 100644 index 0000000000..58b42c6515 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/sorting.d.ts @@ -0,0 +1,11 @@ +export declare type E = string; +export interface D { + e: number; +} +export declare var e: C; +export declare class C { + e: number; + d: string; +} +export declare function b(): boolean; +export declare const a: string; diff --git a/tools/ts-api-guardian/test/fixtures/sorting_expected.d.ts b/tools/ts-api-guardian/test/fixtures/sorting_expected.d.ts new file mode 100644 index 0000000000..d3921f8d91 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/sorting_expected.d.ts @@ -0,0 +1,16 @@ +export declare const a: string; + +export declare function b(): boolean; + +export declare class C { + d: string; + e: number; +} + +export interface D { + e: number; +} + +export declare var e: C; + +export declare type E = string; diff --git a/tools/ts-api-guardian/test/fixtures/stripped_alias.d.ts b/tools/ts-api-guardian/test/fixtures/stripped_alias.d.ts new file mode 100644 index 0000000000..df39a7eb0d --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/stripped_alias.d.ts @@ -0,0 +1,3 @@ +export {original_symbol as __private_symbol} from './stripped_alias_original'; +export class B { +} \ No newline at end of file diff --git a/tools/ts-api-guardian/test/fixtures/stripped_alias_expected.d.ts b/tools/ts-api-guardian/test/fixtures/stripped_alias_expected.d.ts new file mode 100644 index 0000000000..94fa3c9c47 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/stripped_alias_expected.d.ts @@ -0,0 +1,2 @@ +export class B { +} diff --git a/tools/ts-api-guardian/test/fixtures/stripped_alias_original.d.ts b/tools/ts-api-guardian/test/fixtures/stripped_alias_original.d.ts new file mode 100644 index 0000000000..76536ae835 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/stripped_alias_original.d.ts @@ -0,0 +1 @@ +export let original_symbol: number; \ No newline at end of file diff --git a/tools/ts-api-guardian/test/fixtures/type_literals.d.ts b/tools/ts-api-guardian/test/fixtures/type_literals.d.ts new file mode 100644 index 0000000000..1a2e41b591 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/type_literals.d.ts @@ -0,0 +1,7 @@ +export class UsesTypeLiterals { + a: number | undefined; + b: number | null; + c: number | true; + d: number | null | undefined; + e: Array; +} \ No newline at end of file diff --git a/tools/ts-api-guardian/test/fixtures/type_literals_expected.d.ts b/tools/ts-api-guardian/test/fixtures/type_literals_expected.d.ts new file mode 100644 index 0000000000..ce70fa6fa3 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/type_literals_expected.d.ts @@ -0,0 +1,7 @@ +export class UsesTypeLiterals { + a: number | undefined; + b: number | null; + c: number | true; + d: number | null | undefined; + e: Array; +} diff --git a/tools/ts-api-guardian/test/fixtures/underscored.d.ts b/tools/ts-api-guardian/test/fixtures/underscored.d.ts new file mode 100644 index 0000000000..ee6bfd8eab --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/underscored.d.ts @@ -0,0 +1,3 @@ +export const __a__: string; +export class B { +} diff --git a/tools/ts-api-guardian/test/fixtures/underscored_expected.d.ts b/tools/ts-api-guardian/test/fixtures/underscored_expected.d.ts new file mode 100644 index 0000000000..94fa3c9c47 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/underscored_expected.d.ts @@ -0,0 +1,2 @@ +export class B { +} diff --git a/tools/ts-api-guardian/test/fixtures/verify.patch b/tools/ts-api-guardian/test/fixtures/verify.patch new file mode 100644 index 0000000000..bb23ff2ee2 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/verify.patch @@ -0,0 +1,11 @@ +--- test/fixtures/verify_expected.d.ts Golden file ++++ test/fixtures/verify_expected.d.ts Generated API +@@ -1,5 +1,6 @@ + export interface A { + c: number; +- a(arg: any[]): any; +- b: string; ++ a(arg: any[]): {[name: string]: number}; + } ++ ++export declare const b: boolean; diff --git a/tools/ts-api-guardian/test/fixtures/verify_entrypoint.d.ts b/tools/ts-api-guardian/test/fixtures/verify_entrypoint.d.ts new file mode 100644 index 0000000000..07a6c15ae8 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/verify_entrypoint.d.ts @@ -0,0 +1,5 @@ +export interface A { + c: number; + a(arg: any[]): {[name: string]: number}; +} +export { b } from './verify_submodule'; diff --git a/tools/ts-api-guardian/test/fixtures/verify_expected.d.ts b/tools/ts-api-guardian/test/fixtures/verify_expected.d.ts new file mode 100644 index 0000000000..c7d5188ec4 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/verify_expected.d.ts @@ -0,0 +1,5 @@ +export interface A { + c: number; + a(arg: any[]): any; + b: string; +} diff --git a/tools/ts-api-guardian/test/fixtures/verify_submodule.d.ts b/tools/ts-api-guardian/test/fixtures/verify_submodule.d.ts new file mode 100644 index 0000000000..a9f8f857a2 --- /dev/null +++ b/tools/ts-api-guardian/test/fixtures/verify_submodule.d.ts @@ -0,0 +1 @@ +export declare const b: boolean; diff --git a/tools/ts-api-guardian/test/helpers.ts b/tools/ts-api-guardian/test/helpers.ts new file mode 100644 index 0000000000..c97fdb7860 --- /dev/null +++ b/tools/ts-api-guardian/test/helpers.ts @@ -0,0 +1,19 @@ +import * as chai from 'chai'; +import * as fs from 'fs'; +import * as path from 'path'; + +export function unlinkRecursively(file: string) { + if (fs.statSync(file).isDirectory()) { + for (const f of fs.readdirSync(file)) { + unlinkRecursively(path.join(file, f)); + } + fs.rmdirSync(file); + } else { + fs.unlinkSync(file); + } +} + +export function assertFileEqual(actualFile: string, expectedFile: string) { + chai.assert.equal( + fs.readFileSync(actualFile).toString(), fs.readFileSync(expectedFile).toString()); +} diff --git a/tools/ts-api-guardian/test/integration_test.ts b/tools/ts-api-guardian/test/integration_test.ts new file mode 100644 index 0000000000..1b836e604b --- /dev/null +++ b/tools/ts-api-guardian/test/integration_test.ts @@ -0,0 +1,132 @@ +import * as chai from 'chai'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as main from '../lib/main'; +import {assertFileEqual, unlinkRecursively} from './helpers'; + +describe('integration test: public api', () => { + let _warn = null; + let warnings: string[] = []; + beforeEach(() => { + _warn = console.warn; + console.warn = (...args: string[]) => warnings.push(args.join(' ')); + }); + + afterEach(() => { + console.warn = _warn; + warnings = []; + _warn = null; + }); + + it('should handle empty files', + () => { check('test/fixtures/empty.d.ts', 'test/fixtures/empty_expected.d.ts'); }); + + it('should include symbols', + () => { check('test/fixtures/simple.d.ts', 'test/fixtures/simple_expected.d.ts'); }); + + it('should include symbols reexported explicitly', + () => { check('test/fixtures/reexported.d.ts', 'test/fixtures/reexported_expected.d.ts'); }); + + it('should include symbols reexported with *', () => { + check('test/fixtures/reexported_star.d.ts', 'test/fixtures/reexported_star_expected.d.ts'); + }); + + it('should include members of classes and interfaces', () => { + check( + 'test/fixtures/classes_and_interfaces.d.ts', + 'test/fixtures/classes_and_interfaces_expected.d.ts'); + }); + + it('should include members reexported classes', () => { + check( + 'test/fixtures/reexported_classes.d.ts', 'test/fixtures/reexported_classes_expected.d.ts'); + }); + + it('should remove reexported external symbols', () => { + check('test/fixtures/reexported_extern.d.ts', 'test/fixtures/reexported_extern_expected.d.ts'); + chai.assert.deepEqual(warnings, [ + 'test/fixtures/reexported_extern.d.ts(5,1): error: No export declaration found for symbol "CompilerHost"' + ]); + }); + + it('should support type literals', () => { + check('test/fixtures/type_literals.d.ts', 'test/fixtures/type_literals_expected.d.ts'); + }); + + it('should allow enums as types', () => { + check('test/fixtures/enum_as_type.d.ts', 'test/fixtures/enum_as_type_expected.d.ts'); + }); + + it('should throw on passing a .ts file as an input', () => { + chai.assert.throws(() => { + main.publicApi('test/fixtures/empty.ts'); + }, 'Source file "test/fixtures/empty.ts" is not a declaration file'); + }); + + it('should respect serialization options', () => { + check( + 'test/fixtures/underscored.d.ts', 'test/fixtures/underscored_expected.d.ts', + {stripExportPattern: /^__.*/}); + }); +}); + +describe('integration test: generateGoldenFile', () => { + const outDir = path.resolve(__dirname, '../../build/tmp'); + const outFile = path.join(outDir, 'out.d.ts'); + const deepOutFile = path.join(outDir, 'a/b/c/out.d.ts'); + + beforeEach(() => { + if (!fs.existsSync(outDir)) { + fs.mkdirSync(outDir); + } + }); + + afterEach(() => { unlinkRecursively(outDir); }); + + + it('should generate a golden file', () => { + main.generateGoldenFile('test/fixtures/reexported_classes.d.ts', outFile); + assertFileEqual(outFile, 'test/fixtures/reexported_classes_expected.d.ts'); + }); + + it('should generate a golden file with any ancestor directory created', () => { + main.generateGoldenFile('test/fixtures/reexported_classes.d.ts', deepOutFile); + assertFileEqual(deepOutFile, 'test/fixtures/reexported_classes_expected.d.ts'); + }); + + it('should respect serialization options', () => { + main.generateGoldenFile( + 'test/fixtures/underscored.d.ts', outFile, {stripExportPattern: /^__.*/}); + assertFileEqual(outFile, 'test/fixtures/underscored_expected.d.ts'); + }); + + it('should generate a golden file with keyof', () => { + main.generateGoldenFile('test/fixtures/keyof.d.ts', outFile); + assertFileEqual(outFile, 'test/fixtures/keyof_expected.d.ts'); + }); +}); + +describe('integration test: verifyAgainstGoldenFile', () => { + it('should check an entrypoint against a golden file on equal', () => { + const diff = main.verifyAgainstGoldenFile( + 'test/fixtures/reexported_classes.d.ts', 'test/fixtures/reexported_classes_expected.d.ts'); + chai.assert.equal(diff, ''); + }); + + it('should check an entrypoint against a golden file with proper diff message', () => { + const diff = main.verifyAgainstGoldenFile( + 'test/fixtures/verify_entrypoint.d.ts', 'test/fixtures/verify_expected.d.ts'); + chai.assert.equal(diff, fs.readFileSync('test/fixtures/verify.patch').toString()); + }); + + it('should respect serialization options', () => { + const diff = main.verifyAgainstGoldenFile( + 'test/fixtures/underscored.d.ts', 'test/fixtures/underscored_expected.d.ts', + {stripExportPattern: /^__.*/}); + chai.assert.equal(diff, ''); + }); +}); + +function check(sourceFile: string, expectedFile: string, options: main.SerializationOptions = {}) { + chai.assert.equal(main.publicApi(sourceFile, options), fs.readFileSync(expectedFile).toString()); +} diff --git a/tools/ts-api-guardian/test/unit_test.ts b/tools/ts-api-guardian/test/unit_test.ts new file mode 100644 index 0000000000..36bc8f199f --- /dev/null +++ b/tools/ts-api-guardian/test/unit_test.ts @@ -0,0 +1,494 @@ +import * as chai from 'chai'; +import * as ts from 'typescript'; +import { publicApiInternal, SerializationOptions } from '../lib/serializer'; + +const classesAndInterfaces = ` + export declare class A { + field: string; + method(a: string): number; + } + export interface B { + field: A; + } + export declare class C { + someProp: string; + propWithDefault: number; + private privateProp; + protected protectedProp: number; + constructor(someProp: string, propWithDefault: number, privateProp: any, protectedProp: number); + } +`; + +describe('unit test', () => { + let _warn = null; + let warnings: string[] = []; + beforeEach(() => { + _warn = console.warn; + console.warn = (...args: string[]) => warnings.push(args.join(' ')); + }); + + afterEach(() => { + console.warn = _warn; + warnings = []; + _warn = null; + }); + + it('should ignore private methods', () => { + const input = ` + export declare class A { + fa(): void; + protected fb(): void; + private fc(); + } + `; + const expected = ` + export declare class A { + fa(): void; + protected fb(): void; + } + `; + check({ 'file.d.ts': input }, expected); + }); + + it('should ignore private props', () => { + const input = ` + export declare class A { + fa: any; + protected fb: any; + private fc; + } + `; + const expected = ` + export declare class A { + fa: any; + protected fb: any; + } + `; + check({ 'file.d.ts': input }, expected); + }); + + it('should support imports without capturing imports', () => { + const input = ` + import {A} from './classes_and_interfaces'; + export declare class C { + field: A; + } + `; + const expected = ` + export declare class C { + field: A; + } + `; + check({ 'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input }, expected); + }); + + it('should throw on aliased reexports', () => { + const input = ` + export { A as Apple } from './classes_and_interfaces'; + `; + checkThrows( + { 'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input }, + 'Symbol "A" was aliased as "Apple". Aliases are not supported.'); + }); + + it('should remove reexported external symbols', () => { + const input = ` + export { Foo } from 'some-external-module-that-cannot-be-resolved'; + `; + const expected = ` + `; + check({ 'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input }, expected); + chai.assert.deepEqual( + warnings, ['file.d.ts(1,1): error: No export declaration found for symbol "Foo"']); + }); + + it('should sort exports', () => { + const input = ` + export declare type E = string; + export interface D { + e: number; + } + export declare var e: C; + export declare class C { + e: number; + d: string; + } + export declare function b(): boolean; + export declare const a: string; + `; + const expected = ` + export declare const a: string; + + export declare function b(): boolean; + + export declare class C { + d: string; + e: number; + } + + export interface D { + e: number; + } + + export declare var e: C; + + export declare type E = string; + `; + check({ 'file.d.ts': input }, expected); + }); + + it('should sort class members', () => { + const input = ` + export class A { + f: number; + static foo(): void; + c: string; + static a: boolean; + constructor(); + static bar(): void; + } + `; + const expected = ` + export class A { + c: string; + f: number; + constructor(); + static a: boolean; + static bar(): void; + static foo(): void; + } + `; + check({ 'file.d.ts': input }, expected); + }); + + it('should sort interface members', () => { + const input = ` + export interface A { + (): void; + [key: string]: any; + c(): void; + a: number; + new (): Object; + } + `; + const expected = ` + export interface A { + a: number; + (): void; + new (): Object; + [key: string]: any; + c(): void; + } + `; + check({ 'file.d.ts': input }, expected); + }); + + it('should sort class members including readonly', () => { + const input = ` + export declare class DebugNode { + private _debugContext; + nativeNode: any; + listeners: any[]; + parent: any | null; + constructor(nativeNode: any, parent: DebugNode | null, _debugContext: any); + readonly injector: any; + readonly componentInstance: any; + readonly context: any; + readonly references: { + [key: string]: any; + }; + readonly providerTokens: any[]; + } + `; + const expected = ` + export declare class DebugNode { + readonly componentInstance: any; + readonly context: any; + readonly injector: any; + listeners: any[]; + nativeNode: any; + parent: any | null; + readonly providerTokens: any[]; + readonly references: { + [key: string]: any; + }; + constructor(nativeNode: any, parent: DebugNode | null, _debugContext: any); + } + `; + check({ 'file.d.ts': input }, expected); + }); + + it('should sort two call signatures', () => { + const input = ` + export interface A { + (b: number): void; + (a: number): void; + } + `; + const expected = ` + export interface A { + (a: number): void; + (b: number): void; + } + `; + check({ 'file.d.ts': input }, expected); + }); + + it('should sort exports including re-exports', () => { + const submodule = ` + export declare var e: C; + export declare class C { + e: number; + d: string; + } + `; + const input = ` + export * from './submodule'; + export declare type E = string; + export interface D { + e: number; + } + export declare function b(): boolean; + export declare const a: string; + `; + const expected = ` + export declare const a: string; + + export declare function b(): boolean; + + export declare class C { + d: string; + e: number; + } + + export interface D { + e: number; + } + + export declare var e: C; + + export declare type E = string; + `; + check({ 'submodule.d.ts': submodule, 'file.d.ts': input }, expected); + }); + + it('should remove module comments', () => { + const input = ` + /** + * An amazing module. + * @module + */ + /** + * Foo function. + */ + export declare function foo(): boolean; + export declare const bar: number; + `; + const expected = ` + export declare const bar: number; + + export declare function foo(): boolean; + `; + check({ 'file.d.ts': input }, expected); + }); + + it('should remove class and field comments', () => { + const input = ` + /** + * Does something really cool. + */ + export declare class A { + /** + * A very interesting getter. + */ + b: string; + /** + * A very useful field. + */ + name: string; + } + `; + const expected = ` + export declare class A { + b: string; + name: string; + } + `; + check({ 'file.d.ts': input }, expected); + }); + + it('should skip symbols matching specified pattern', () => { + const input = ` + export const __a__: string; + export class B { + } + `; + const expected = ` + export class B { + } + `; + check({ 'file.d.ts': input }, expected, { stripExportPattern: /^__.*/ }); + }); + + it('should throw on using non-whitelisted module imports in expression position', () => { + const input = ` + import * as foo from './foo'; + export declare class A extends foo.A { + } + `; + checkThrows( + { 'file.d.ts': input }, 'file.d.ts(2,32): error: Module identifier "foo" is not allowed. ' + + 'Remove it from source or whitelist it via --allowModuleIdentifiers.'); + }); + + it('should throw on using non-whitelisted module imports in type position', () => { + const input = ` + import * as foo from './foo'; + export type A = foo.A; + `; + checkThrows( + { 'file.d.ts': input }, 'file.d.ts(2,17): error: Module identifier "foo" is not allowed. ' + + 'Remove it from source or whitelist it via --allowModuleIdentifiers.'); + }); + + it('should not throw on using whitelisted module imports', () => { + const input = ` + import * as foo from './foo'; + export declare class A extends foo.A { + } + `; + const expected = ` + export declare class A extends foo.A { + } + `; + check({ 'file.d.ts': input }, expected, { allowModuleIdentifiers: ['foo'] }); + }); + + it('should not throw if non-whitelisted module imports are not written', () => { + const input = ` + import * as foo from './foo'; + export declare class A { + } + `; + const expected = ` + export declare class A { + } + `; + check({ 'file.d.ts': input }, expected); + }); + + it('should keep stability annotations of exports in docstrings', () => { + const input = ` + /** + * @deprecated This is useless now + */ + export declare class A { + } + /** + * @experimental + */ + export declare const b: string; + /** + * @stable + */ + export declare var c: number; + `; + const expected = ` + /** @deprecated */ + export declare class A { + } + + /** @experimental */ + export declare const b: string; + + /** @stable */ + export declare var c: number; + `; + check({ 'file.d.ts': input }, expected); + }); + + it('should keep stability annotations of fields in docstrings', () => { + const input = ` + export declare class A { + /** + * @stable + */ + value: number; + /** + * @experimental + */ + constructor(); + /** + * @deprecated + */ + foo(): void; + } + `; + const expected = ` + export declare class A { + /** @stable */ value: number; + /** @experimental */ constructor(); + /** @deprecated */ foo(): void; + } + `; + check({ 'file.d.ts': input }, expected); + }); + + it('should warn on onStabilityMissing: warn', () => { + const input = ` + export declare class A { + constructor(); + } + `; + const expected = ` + export declare class A { + constructor(); + } + `; + check({ 'file.d.ts': input }, expected, { onStabilityMissing: 'warn' }); + chai.assert.deepEqual( + warnings, ['file.d.ts(1,1): error: No stability annotation found for symbol "A"']); + }); +}); + +function getMockHost(files: { [name: string]: string }): ts.CompilerHost { + return { + getSourceFile: (sourceName, languageVersion) => { + if (!files[sourceName]) return undefined; + return ts.createSourceFile( + sourceName, stripExtraIndentation(files[sourceName]), languageVersion, true); + }, + writeFile: (name, text, writeByteOrderMark) => { }, + fileExists: (filename) => !!files[filename], + readFile: (filename) => stripExtraIndentation(files[filename]), + getDefaultLibFileName: () => 'lib.ts', + useCaseSensitiveFileNames: () => true, + getCanonicalFileName: (filename) => filename, + getCurrentDirectory: () => './', + getNewLine: () => '\n', + getDirectories: () => [] + }; +} + +function check( + files: { [name: string]: string }, expected: string, options: SerializationOptions = {}) { + const actual = publicApiInternal(getMockHost(files), 'file.d.ts', {}, options); + chai.assert.equal(actual.trim(), stripExtraIndentation(expected).trim()); +} + +function checkThrows(files: { [name: string]: string }, error: string) { + chai.assert.throws(() => { publicApiInternal(getMockHost(files), 'file.d.ts', {}); }, error); +} + +function stripExtraIndentation(text: string) { + let lines = text.split('\n'); + // Ignore first and last new line + lines = lines.slice(1, lines.length - 1); + const commonIndent = lines.reduce((min, line) => { + const indent = /^( *)/.exec(line)[1].length; + // Ignore empty line + return line.length ? Math.min(min, indent) : min; + }, text.length); + + return lines.map(line => line.substr(commonIndent)).join('\n') + '\n'; +} From 4f609687044a5b7cd15f796bce948e9385b70f7a Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Thu, 1 Mar 2018 10:41:35 -0800 Subject: [PATCH 035/604] test(bazel): Build and test ts-api-guardian locally (#22544) Also use it to test the public API for core and common Once we have an ng_package for every package, we can remove the npm dependency on ts-api-guardian and the gulp-based public api check. PR Close #22544 --- BUILD.bazel | 8 + WORKSPACE | 9 +- tools/BUILD.bazel | 2 + tools/gulp-tasks/format.js | 1 + tools/public_api_guard/BUILD.bazel | 26 + tools/ts-api-guardian/BUILD.bazel | 84 + tools/ts-api-guardian/README.md | 17 + tools/ts-api-guardian/bin/ts-api-guardian | 2 +- tools/ts-api-guardian/index.bzl | 53 + tools/ts-api-guardian/lib/cli.ts | 35 +- tools/ts-api-guardian/lib/main.ts | 8 + tools/ts-api-guardian/lib/serializer.ts | 78 +- tools/ts-api-guardian/package.json | 9 +- tools/ts-api-guardian/test/bootstrap.ts | 12 + tools/ts-api-guardian/test/cli_e2e_test.ts | 26 +- tools/ts-api-guardian/test/cli_unit_test.ts | 8 + tools/ts-api-guardian/test/fixtures/empty.ts | 7 + tools/ts-api-guardian/test/helpers.ts | 8 + .../ts-api-guardian/test/integration_test.ts | 12 +- tools/ts-api-guardian/test/unit_test.ts | 77 +- tools/ts-api-guardian/yarn.lock | 2336 +++++++++++++++++ tools/tsconfig.json | 1 + yarn.lock | 20 +- 23 files changed, 2731 insertions(+), 108 deletions(-) create mode 100644 tools/public_api_guard/BUILD.bazel create mode 100644 tools/ts-api-guardian/BUILD.bazel create mode 100644 tools/ts-api-guardian/index.bzl create mode 100644 tools/ts-api-guardian/test/bootstrap.ts create mode 100644 tools/ts-api-guardian/yarn.lock diff --git a/BUILD.bazel b/BUILD.bazel index 3ae5672cf0..c982d1b877 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -2,8 +2,16 @@ package(default_visibility = ["//visibility:public"]) exports_files([ "tsconfig.json", + "LICENSE", ]) +# Developers should always run `bazel run :install` +# This ensures that package.json in subdirectories get installed as well. +alias( + name = "install", + actual = "@yarn//:yarn", +) + # This rule belongs in node_modules/BUILD # It's here as a workaround for # https://github.com/bazelbuild/bazel/issues/374#issuecomment-296217940 diff --git a/WORKSPACE b/WORKSPACE index dd18ca2191..49e6c208ce 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,9 +1,5 @@ workspace(name = "angular") -# Using a pre-release snapshot to pick up a commit that makes all nodejs_binary -# programs produce source-mapped stack traces and uglify sourcemaps. -RULES_NODEJS_VERSION = "f3fc23b7e1f32984a3e5d0c7eabe3baa127fb32a" - http_archive( name = "build_bazel_rules_nodejs", url = "https://github.com/bazelbuild/rules_nodejs/archive/0.5.0.zip", @@ -14,7 +10,10 @@ http_archive( load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories") check_bazel_version("0.9.0") -node_repositories(package_json = ["//:package.json"]) +node_repositories(package_json = [ + "//:package.json", + "//tools/ts-api-guardian:package.json", +]) http_archive( name = "build_bazel_rules_typescript", diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel index 72655ac4ed..df7b9da7f0 100644 --- a/tools/BUILD.bazel +++ b/tools/BUILD.bazel @@ -1,3 +1,5 @@ +exports_files(["tsconfig.json"]) + # Executes the workspace_status_command and provides the result. # See the section on stamping in docs/BAZEL.md genrule( diff --git a/tools/gulp-tasks/format.js b/tools/gulp-tasks/format.js index afd22a8a6e..1ca2f2a9aa 100644 --- a/tools/gulp-tasks/format.js +++ b/tools/gulp-tasks/format.js @@ -26,6 +26,7 @@ const srcsToFmt = [ `!${I18N_FOLDER}/currencies.ts`, `!${I18N_FOLDER}/locale_en.ts`, '!tools/gulp-tasks/cldr/extract.js', + '!tools/ts-api-guardian/test/fixtures/**', ]; module.exports = { diff --git a/tools/public_api_guard/BUILD.bazel b/tools/public_api_guard/BUILD.bazel new file mode 100644 index 0000000000..1e111c4226 --- /dev/null +++ b/tools/public_api_guard/BUILD.bazel @@ -0,0 +1,26 @@ +load("//tools/ts-api-guardian:index.bzl", "ts_api_guardian_test") + +[ + ts_api_guardian_test( + name = "%s_api" % i.replace("/", "_"), + actual = "packages/%s/npm_package/%s.d.ts" % ( + i.split("/")[0], + "/".join(i.split("/")[1:]), + ), + data = glob([ + "%s/**/*.d.ts" % i.split("/")[0], + ]) + [ + "//packages/%s:npm_package" % i.split("/")[0], + ], + golden = "tools/public_api_guard/%s.d.ts" % i, + ) + for i in [ + "core/core", + "core/testing", + "common/http/testing", + "common/common", + "common/http", + "common/testing", + # TODO(alexeagle): add remaining packages here once they have ng_package's + ] +] diff --git a/tools/ts-api-guardian/BUILD.bazel b/tools/ts-api-guardian/BUILD.bazel new file mode 100644 index 0000000000..05677afbb4 --- /dev/null +++ b/tools/ts-api-guardian/BUILD.bazel @@ -0,0 +1,84 @@ +load( + "@build_bazel_rules_nodejs//:defs.bzl", + "nodejs_binary", + "jasmine_node_test", + "npm_package", + "node_modules_filegroup", +) +load("@build_bazel_rules_typescript//:defs.bzl", "ts_library") + +exports_files(["bin/ts-api-guardian"]) + +node_modules_filegroup( + name = "compile_time_deps", + packages = [ + "chalk", + "typescript", + "@types", + ], +) + +ts_library( + name = "lib", + srcs = glob(["lib/*.ts"]), + module_name = "ts-api-guardian", + node_modules = ":compile_time_deps", + tsconfig = "//tools:tsconfig.json", + visibility = ["//visibility:public"], +) + +node_modules_filegroup( + name = "runtime_deps", + packages = [ + "chai", + "chalk", + "jasmine", + ], + visibility = ["//visibility:public"], +) + +# Copy Angular's license to govern ts-api-guardian as well. +# We use a genrule to put it in this package, so it will be in the right root directory. +genrule( + name = "license", + srcs = ["//:LICENSE"], + outs = ["LICENSE"], + cmd = "cp $< $@", +) + +npm_package( + name = "ts-api-guardian", + srcs = [ + "README.md", + "bin/ts-api-guardian", + "package.json", + ], + deps = [ + ":lib", + ":license", + ], +) + +#######################################3 +# Tests for this package + +ts_library( + name = "test_lib", + testonly = True, + srcs = glob(["test/*.ts"]), + node_modules = ":compile_time_deps", + deps = [":lib"], +) + +jasmine_node_test( + name = "tests", + srcs = [":test_lib"], + bootstrap = ["angular/tools/ts-api-guardian/test/bootstrap.js"], + data = glob([ + "test/fixtures/*.ts", + "test/fixtures/*.patch", + ]) + [ + ":ts-api-guardian", + ], + node_modules = ":runtime_deps", +) diff --git a/tools/ts-api-guardian/README.md b/tools/ts-api-guardian/README.md index 0de5176121..3a2bea89a0 100644 --- a/tools/ts-api-guardian/README.md +++ b/tools/ts-api-guardian/README.md @@ -1,3 +1,20 @@ # Typescript API Guardian Keeps track of public API surface of a typescript library. + +# For developers + +Build and test this library: + +```sh +$ bazel run //:install +$ bazel test //tools/ts-api-guardian:all +``` + +Publish to NPM: + +```sh +$ npm whoami # should be logged in as angular +$ grep version tools/ts-api-guardian/package.json # advance as needed +$ bazel run //tools/ts-api-guardian:ts-api-guardian.publish +``` diff --git a/tools/ts-api-guardian/bin/ts-api-guardian b/tools/ts-api-guardian/bin/ts-api-guardian index 4dc9563ac2..b0cfde463b 100755 --- a/tools/ts-api-guardian/bin/ts-api-guardian +++ b/tools/ts-api-guardian/bin/ts-api-guardian @@ -1,3 +1,3 @@ #!/usr/bin/env node -require('../build/lib/cli').startCli(); +require('../lib/cli').startCli(); diff --git a/tools/ts-api-guardian/index.bzl b/tools/ts-api-guardian/index.bzl new file mode 100644 index 0000000000..e3e4ebdc3b --- /dev/null +++ b/tools/ts-api-guardian/index.bzl @@ -0,0 +1,53 @@ +# Copyright 2017 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Runs ts_api_guardian +""" +load("@build_bazel_rules_nodejs//internal/node:node.bzl", "nodejs_test", "nodejs_binary") + +COMMON_MODULE_IDENTIFIERS = ["angular", "jasmine", "protractor"] + +def ts_api_guardian_test(name, golden, actual, data = [], **kwargs): + """Runs ts_api_guardian + """ + data += [ + "//tools/ts-api-guardian:lib", + "//tools/ts-api-guardian:bin/ts-api-guardian", + ] + + args = [ + "--stripExportPattern", "^\(__\|ɵ\)", + "--onStabilityMissing", "error", + ] + for i in COMMON_MODULE_IDENTIFIERS: + args += ["--allowModuleIdentifiers", i] + + nodejs_test( + name = name, + data = data, + node_modules = "//tools/ts-api-guardian:runtime_deps", + entry_point = "angular/tools/ts-api-guardian/bin/ts-api-guardian", + templated_args = args + ["--verify", golden, actual], + testonly = 1, + **kwargs + ) + + nodejs_binary( + name = name + ".accept", + data = data, + node_modules = "//tools/ts-api-guardian:runtime_deps", + entry_point = "angular/tools/ts-api-guardian/bin/ts-api-guardian", + templated_args = args + ["--out", golden, actual], + **kwargs + ) diff --git a/tools/ts-api-guardian/lib/cli.ts b/tools/ts-api-guardian/lib/cli.ts index 8b0c513e92..cf706dc6e1 100644 --- a/tools/ts-api-guardian/lib/cli.ts +++ b/tools/ts-api-guardian/lib/cli.ts @@ -1,6 +1,17 @@ -import chalk from 'chalk'; +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +// tslint:disable:no-console + +// TODO(alexeagle): why not import chalk from 'chalk'? +// Something to do with TS default export in UMD emit... +const chalk = require('chalk'); import * as minimist from 'minimist'; -import {ParsedArgs} from 'minimist'; import * as path from 'path'; import {SerializationOptions, generateGoldenFile, verifyAgainstGoldenFile} from './main'; @@ -35,7 +46,7 @@ export function startCli() { onStabilityMissing: argv['onStabilityMissing'] || 'none' }; - if (['warn', 'error', 'none'].indexOf(options.onStabilityMissing) < 0) { + if (['warn', 'error', 'none'].indexOf(options.onStabilityMissing as string) < 0) { throw new Error( 'Argument for "--onStabilityMissing" option must be one of: "warn", "error", "none"'); } @@ -65,7 +76,8 @@ export function startCli() { lines.pop(); // Remove trailing newline } for (const line of lines) { - const chalkMap = {'-': chalk.red, '+': chalk.green, '@': chalk.cyan}; + const chalkMap: {[key: string]: + any} = {'-': chalk.red, '+': chalk.green, '@': chalk.cyan}; const chalkFunc = chalkMap[line[0]] || chalk.reset; console.log(chalkFunc(line)); } @@ -73,6 +85,11 @@ export function startCli() { } if (hasDiff) { + // Under bazel, give instructions how to use bazel run to accept the golden file. + if (!!process.env['BAZEL_TARGET']) { + console.error('\n\nAccept the new golden file:'); + console.error(` bazel run ${process.env['BAZEL_TARGET']}.accept`); + } process.exit(1); } } @@ -80,9 +97,9 @@ export function startCli() { } export function parseArguments(input: string[]): - {argv: ParsedArgs, mode: string, errors?: string[]} { + {argv: minimist.ParsedArgs, mode: string, errors: string[]} { let help = false; - const errors = []; + const errors: string[] = []; const argv = minimist(input, { string: [ @@ -95,7 +112,7 @@ export function parseArguments(input: string[]): 'color', 'no-color' ], alias: {'outFile': 'out', 'verifyFile': 'verify'}, - unknown: option => { + unknown: (option: string) => { if (option[0] === '-') { errors.push(`Unknown option: ${option}`); help = true; @@ -171,7 +188,7 @@ Options: } export function generateFileNamePairs( - argv: ParsedArgs, mode: string): {entrypoint: string, goldenFile: string}[] { + argv: minimist.ParsedArgs, mode: string): {entrypoint: string, goldenFile: string}[] { if (argv[mode]) { return [{entrypoint: argv._[0], goldenFile: argv[mode]}]; @@ -179,7 +196,7 @@ export function generateFileNamePairs( let rootDir = argv['rootDir'] || '.'; const goldenDir = argv[mode + 'Dir']; - return argv._.map(fileName => { + return argv._.map((fileName: string) => { return { entrypoint: fileName, goldenFile: path.join(goldenDir, path.relative(rootDir, fileName)) diff --git a/tools/ts-api-guardian/lib/main.ts b/tools/ts-api-guardian/lib/main.ts index 86aa5f86f7..ecba2ca190 100644 --- a/tools/ts-api-guardian/lib/main.ts +++ b/tools/ts-api-guardian/lib/main.ts @@ -1,3 +1,11 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + import {createPatch} from 'diff'; import * as fs from 'fs'; import * as path from 'path'; diff --git a/tools/ts-api-guardian/lib/serializer.ts b/tools/ts-api-guardian/lib/serializer.ts index bb9bda2665..2f8be4ef0e 100644 --- a/tools/ts-api-guardian/lib/serializer.ts +++ b/tools/ts-api-guardian/lib/serializer.ts @@ -1,3 +1,11 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + import * as path from 'path'; import * as ts from 'typescript'; @@ -37,8 +45,8 @@ export function publicApi(fileName: string, options: SerializationOptions = {}): } export function publicApiInternal( - host: ts.CompilerHost, fileName: string, tsOptions: ts.CompilerOptions, - options: SerializationOptions = {}): string { + host: ts.CompilerHost, fileName: string, tsOptions: ts.CompilerOptions, + options: SerializationOptions = {}): string { const entrypoint = path.normalize(fileName); if (!entrypoint.match(/\.d\.ts$/)) { @@ -50,7 +58,7 @@ export function publicApiInternal( } interface Diagnostic { - type: DiagnosticSeverity; + type?: DiagnosticSeverity; message: string; } @@ -87,7 +95,8 @@ class ResolvedDeclarationEmitter { continue; } - let decl: ts.Node = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0]; + let decl: ts.Node|undefined = + symbol.valueDeclaration || symbol.declarations && symbol.declarations[0]; if (!decl) { this.diagnostics.push({ type: 'warn', @@ -115,11 +124,11 @@ class ResolvedDeclarationEmitter { const match = stabilityAnnotationPattern.exec(trivia); if (match) { output += `/** @${match[1]} */\n`; - } else if (['warn', 'error'].indexOf(this.options.onStabilityMissing) >= 0) { + } else if (['warn', 'error'].indexOf(this.options.onStabilityMissing as string) >= 0) { this.diagnostics.push({ type: this.options.onStabilityMissing, message: createErrorMessage( - decl, `No stability annotation found for symbol "${symbol.name}"`) + decl, `No stability annotation found for symbol "${symbol.name}"`) }); } @@ -129,7 +138,7 @@ class ResolvedDeclarationEmitter { this.diagnostics.push({ type: 'warn', message: - createErrorMessage(decl, `No export declaration found for symbol "${symbol.name}"`) + createErrorMessage(decl, `No export declaration found for symbol "${symbol.name}"`) }); } } @@ -161,8 +170,8 @@ class ResolvedDeclarationEmitter { return s; } throw new Error( - `Symbol "${resolvedSymbol.name}" was aliased as "${s.name}". ` + - `Aliases are not supported."`); + `Symbol "${resolvedSymbol.name}" was aliased as "${s.name}". ` + + `Aliases are not supported."`); } return resolvedSymbol; @@ -177,28 +186,31 @@ class ResolvedDeclarationEmitter { return ''; } - const firstQualifier: ts.Identifier = getFirstQualifier(node); + const firstQualifier: ts.Identifier|null = getFirstQualifier(node); if (firstQualifier) { let isAllowed = false; // Try to resolve the qualifier. const resolvedSymbol = this.typeChecker.getSymbolAtLocation(firstQualifier); - if (resolvedSymbol && resolvedSymbol.declarations.length > 0) { - // If the qualifier can be resolved, and it's not a namespaced import, then it should be allowed. - isAllowed = resolvedSymbol.declarations.every(decl => decl.kind !== ts.SyntaxKind.NamespaceImport); + if (resolvedSymbol && resolvedSymbol.declarations && resolvedSymbol.declarations.length > 0) { + // If the qualifier can be resolved, and it's not a namespaced import, then it should be + // allowed. + isAllowed = + resolvedSymbol.declarations.every(decl => decl.kind !== ts.SyntaxKind.NamespaceImport); } // If it is not allowed otherwise, it's allowed if it's on the list of allowed identifiers. - isAllowed = isAllowed || !(!this.options.allowModuleIdentifiers || - this.options.allowModuleIdentifiers.indexOf(firstQualifier.text) < 0); + isAllowed = isAllowed || + !(!this.options.allowModuleIdentifiers || + this.options.allowModuleIdentifiers.indexOf(firstQualifier.text) < 0); if (!isAllowed) { this.diagnostics.push({ type: 'error', message: createErrorMessage( - firstQualifier, - `Module identifier "${firstQualifier.text}" is not allowed. Remove it ` + - `from source or whitelist it via --allowModuleIdentifiers.`) + firstQualifier, + `Module identifier "${firstQualifier.text}" is not allowed. Remove it ` + + `from source or whitelist it via --allowModuleIdentifiers.`) }); } } @@ -220,15 +232,14 @@ class ResolvedDeclarationEmitter { children.sort((a: ts.NamedDeclaration, b: ts.NamedDeclaration) => { // Static after normal return compareFunction( - hasModifier(a, ts.SyntaxKind.StaticKeyword), - hasModifier(b, ts.SyntaxKind.StaticKeyword) - ) || - // Our predefined order - compareFunction( - memberDeclarationOrder[a.kind], memberDeclarationOrder[b.kind]) || - // Alphebetical order - // We need safe dereferencing due to edge cases, e.g. having two call signatures - compareFunction((a.name || a).getText(), (b.name || b).getText()); + hasModifier(a, ts.SyntaxKind.StaticKeyword), + hasModifier(b, ts.SyntaxKind.StaticKeyword)) || + // Our predefined order + compareFunction( + memberDeclarationOrder[a.kind], memberDeclarationOrder[b.kind]) || + // Alphebetical order + // We need safe dereferencing due to edge cases, e.g. having two call signatures + compareFunction((a.name || a).getText(), (b.name || b).getText()); }); } break; @@ -236,10 +247,9 @@ class ResolvedDeclarationEmitter { } } - let output = children - .filter(x => x.kind !== ts.SyntaxKind.JSDocComment) - .map(n => this.emitNode(n)) - .join(''); + let output: string = children.filter(x => x.kind !== ts.SyntaxKind.JSDocComment) + .map(n => this.emitNode(n)) + .join(''); // Print stability annotation for fields if (node.kind in memberDeclarationOrder) { @@ -273,7 +283,7 @@ function compareFunction(a: T, b: T) { return a === b ? 0 : a > b ? 1 : -1; } -const memberDeclarationOrder = { +const memberDeclarationOrder: {[key: number]: number} = { [ts.SyntaxKind.PropertySignature]: 0, [ts.SyntaxKind.PropertyDeclaration]: 0, [ts.SyntaxKind.GetAccessor]: 0, @@ -295,7 +305,7 @@ function stripEmptyLines(text: string): string { /** * Returns the first qualifier if the input node is a dotted expression. */ -function getFirstQualifier(node: ts.Node): ts.Identifier { +function getFirstQualifier(node: ts.Node): ts.Identifier|null { switch (node.kind) { case ts.SyntaxKind.PropertyAccessExpression: { // For expression position @@ -324,7 +334,7 @@ function createErrorMessage(node: ts.Node, message: string): string { const sourceFile = node.getSourceFile(); let position; if (sourceFile) { - const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + const {line, character} = sourceFile.getLineAndCharacterOfPosition(node.getStart()); position = `${sourceFile.fileName}(${line + 1},${character + 1})`; } else { position = ''; diff --git a/tools/ts-api-guardian/package.json b/tools/ts-api-guardian/package.json index 7873bf0494..19001c0e36 100644 --- a/tools/ts-api-guardian/package.json +++ b/tools/ts-api-guardian/package.json @@ -33,14 +33,11 @@ "gulp-typescript": "^4.0.1", "gulp-util": "^3.0.8", "merge2": "^1.2.1", + "jasmine": "^3.1.0", "source-map": "^0.7.1", "source-map-support": "^0.5.3", "typescript": "~2.6.2" }, - "scripts": { - "prepublish": "gulp compile", - "test": "gulp test.unit" - }, "repository": {}, "keywords": [ "typescript" @@ -54,7 +51,7 @@ ], "license": "MIT", "bugs": { - "url": "https://github.com/angular/ts-api-guardian/issues" + "url": "https://github.com/angular/angular/issues" }, - "homepage": "https://github.com/angular/ts-api-guardian" + "homepage": "https://github.com/angular/angular/tools/ts-api-guardian" } diff --git a/tools/ts-api-guardian/test/bootstrap.ts b/tools/ts-api-guardian/test/bootstrap.ts new file mode 100644 index 0000000000..8c6514a266 --- /dev/null +++ b/tools/ts-api-guardian/test/bootstrap.ts @@ -0,0 +1,12 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import * as path from 'path'; +// Before running tests, change directories to our directory under the runfiles +// From runfiles we want to go to the angular/tools/ts-api-guardian subfolder. +process.chdir(path.join(process.env['TEST_SRCDIR'], 'angular', 'tools', 'ts-api-guardian')); diff --git a/tools/ts-api-guardian/test/cli_e2e_test.ts b/tools/ts-api-guardian/test/cli_e2e_test.ts index 81fb4b10b6..292bc6e6d4 100644 --- a/tools/ts-api-guardian/test/cli_e2e_test.ts +++ b/tools/ts-api-guardian/test/cli_e2e_test.ts @@ -1,13 +1,21 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + import chai = require('chai'); import * as child_process from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; import {assertFileEqual, unlinkRecursively} from './helpers'; -const BINARY = path.resolve(__dirname, '../../bin/ts-api-guardian'); +const BINARY = 'ts-api-guardian/bin/ts-api-guardian'; describe('cli: e2e test', () => { - const outDir = path.resolve(__dirname, '../../build/tmp'); + const outDir = path.join(process.env['TEST_TMPDIR'], 'tmp'); beforeEach(() => { if (!fs.existsSync(outDir)) { @@ -29,8 +37,8 @@ describe('cli: e2e test', () => { it('should generate golden file with --out', () => { const simpleFile = path.join(outDir, 'simple.d.ts'); - const {status} = execute(['--out', simpleFile, 'test/fixtures/simple.d.ts']); - chai.assert.equal(status, 0); + const {status, stderr} = execute(['--out', simpleFile, 'test/fixtures/simple.d.ts']); + chai.assert.equal(status, 0, stderr); assertFileEqual(simpleFile, 'test/fixtures/simple_expected.d.ts'); }); @@ -117,7 +125,7 @@ describe('cli: e2e test', () => { stderr, 'test/fixtures/simple.d.ts(1,1): error: No stability annotation found for symbol "A"\n' + 'test/fixtures/simple.d.ts(2,1): error: No stability annotation found for symbol "B"\n'); - chai.assert.equal(status, 0); + chai.assert.equal(status, 0, stderr); }); }); @@ -126,8 +134,12 @@ function copyFile(sourceFile: string, targetFile: string) { } function execute(args: string[]): {stdout: string, stderr: string, status: number} { - const output = child_process.spawnSync(BINARY, args); - chai.assert(!output.error, 'Child process failed or timed out'); + const output = child_process.spawnSync(process.execPath, [path.resolve(BINARY), ...args], { + env: { + 'NODE_PATH': process.cwd(), + } + }); + chai.assert(!output.error, 'Child process failed or timed out: ' + output.error); chai.assert(!output.signal, `Child process killed by signal ${output.signal}`); return { diff --git a/tools/ts-api-guardian/test/cli_unit_test.ts b/tools/ts-api-guardian/test/cli_unit_test.ts index 8e55cf476a..278df98103 100644 --- a/tools/ts-api-guardian/test/cli_unit_test.ts +++ b/tools/ts-api-guardian/test/cli_unit_test.ts @@ -1,3 +1,11 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + import chai = require('chai'); import * as ts from 'typescript'; import {parseArguments, generateFileNamePairs} from '../lib/cli'; diff --git a/tools/ts-api-guardian/test/fixtures/empty.ts b/tools/ts-api-guardian/test/fixtures/empty.ts index e69de29bb2..c91a05e544 100644 --- a/tools/ts-api-guardian/test/fixtures/empty.ts +++ b/tools/ts-api-guardian/test/fixtures/empty.ts @@ -0,0 +1,7 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ diff --git a/tools/ts-api-guardian/test/helpers.ts b/tools/ts-api-guardian/test/helpers.ts index c97fdb7860..9c5e785b7e 100644 --- a/tools/ts-api-guardian/test/helpers.ts +++ b/tools/ts-api-guardian/test/helpers.ts @@ -1,3 +1,11 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + import * as chai from 'chai'; import * as fs from 'fs'; import * as path from 'path'; diff --git a/tools/ts-api-guardian/test/integration_test.ts b/tools/ts-api-guardian/test/integration_test.ts index 1b836e604b..eda21210e1 100644 --- a/tools/ts-api-guardian/test/integration_test.ts +++ b/tools/ts-api-guardian/test/integration_test.ts @@ -1,3 +1,11 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + import * as chai from 'chai'; import * as fs from 'fs'; import * as path from 'path'; @@ -5,7 +13,7 @@ import * as main from '../lib/main'; import {assertFileEqual, unlinkRecursively} from './helpers'; describe('integration test: public api', () => { - let _warn = null; + let _warn: any = null; let warnings: string[] = []; beforeEach(() => { _warn = console.warn; @@ -71,7 +79,7 @@ describe('integration test: public api', () => { }); describe('integration test: generateGoldenFile', () => { - const outDir = path.resolve(__dirname, '../../build/tmp'); + const outDir = path.join(process.env['TEST_TMPDIR'], 'tmp'); const outFile = path.join(outDir, 'out.d.ts'); const deepOutFile = path.join(outDir, 'a/b/c/out.d.ts'); diff --git a/tools/ts-api-guardian/test/unit_test.ts b/tools/ts-api-guardian/test/unit_test.ts index 36bc8f199f..2cfa89c0a2 100644 --- a/tools/ts-api-guardian/test/unit_test.ts +++ b/tools/ts-api-guardian/test/unit_test.ts @@ -1,6 +1,15 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + import * as chai from 'chai'; import * as ts from 'typescript'; -import { publicApiInternal, SerializationOptions } from '../lib/serializer'; + +import {SerializationOptions, publicApiInternal} from '../lib/serializer'; const classesAndInterfaces = ` export declare class A { @@ -20,7 +29,7 @@ const classesAndInterfaces = ` `; describe('unit test', () => { - let _warn = null; + let _warn: any = null; let warnings: string[] = []; beforeEach(() => { _warn = console.warn; @@ -47,7 +56,7 @@ describe('unit test', () => { protected fb(): void; } `; - check({ 'file.d.ts': input }, expected); + check({'file.d.ts': input}, expected); }); it('should ignore private props', () => { @@ -64,7 +73,7 @@ describe('unit test', () => { protected fb: any; } `; - check({ 'file.d.ts': input }, expected); + check({'file.d.ts': input}, expected); }); it('should support imports without capturing imports', () => { @@ -79,7 +88,7 @@ describe('unit test', () => { field: A; } `; - check({ 'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input }, expected); + check({'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input}, expected); }); it('should throw on aliased reexports', () => { @@ -87,8 +96,8 @@ describe('unit test', () => { export { A as Apple } from './classes_and_interfaces'; `; checkThrows( - { 'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input }, - 'Symbol "A" was aliased as "Apple". Aliases are not supported.'); + {'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input}, + 'Symbol "A" was aliased as "Apple". Aliases are not supported.'); }); it('should remove reexported external symbols', () => { @@ -97,9 +106,9 @@ describe('unit test', () => { `; const expected = ` `; - check({ 'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input }, expected); + check({'classes_and_interfaces.d.ts': classesAndInterfaces, 'file.d.ts': input}, expected); chai.assert.deepEqual( - warnings, ['file.d.ts(1,1): error: No export declaration found for symbol "Foo"']); + warnings, ['file.d.ts(1,1): error: No export declaration found for symbol "Foo"']); }); it('should sort exports', () => { @@ -134,7 +143,7 @@ describe('unit test', () => { export declare type E = string; `; - check({ 'file.d.ts': input }, expected); + check({'file.d.ts': input}, expected); }); it('should sort class members', () => { @@ -158,7 +167,7 @@ describe('unit test', () => { static foo(): void; } `; - check({ 'file.d.ts': input }, expected); + check({'file.d.ts': input}, expected); }); it('should sort interface members', () => { @@ -180,7 +189,7 @@ describe('unit test', () => { c(): void; } `; - check({ 'file.d.ts': input }, expected); + check({'file.d.ts': input}, expected); }); it('should sort class members including readonly', () => { @@ -215,7 +224,7 @@ describe('unit test', () => { constructor(nativeNode: any, parent: DebugNode | null, _debugContext: any); } `; - check({ 'file.d.ts': input }, expected); + check({'file.d.ts': input}, expected); }); it('should sort two call signatures', () => { @@ -231,7 +240,7 @@ describe('unit test', () => { (b: number): void; } `; - check({ 'file.d.ts': input }, expected); + check({'file.d.ts': input}, expected); }); it('should sort exports including re-exports', () => { @@ -269,7 +278,7 @@ describe('unit test', () => { export declare type E = string; `; - check({ 'submodule.d.ts': submodule, 'file.d.ts': input }, expected); + check({'submodule.d.ts': submodule, 'file.d.ts': input}, expected); }); it('should remove module comments', () => { @@ -289,7 +298,7 @@ describe('unit test', () => { export declare function foo(): boolean; `; - check({ 'file.d.ts': input }, expected); + check({'file.d.ts': input}, expected); }); it('should remove class and field comments', () => { @@ -314,7 +323,7 @@ describe('unit test', () => { name: string; } `; - check({ 'file.d.ts': input }, expected); + check({'file.d.ts': input}, expected); }); it('should skip symbols matching specified pattern', () => { @@ -327,7 +336,7 @@ describe('unit test', () => { export class B { } `; - check({ 'file.d.ts': input }, expected, { stripExportPattern: /^__.*/ }); + check({'file.d.ts': input}, expected, {stripExportPattern: /^__.*/}); }); it('should throw on using non-whitelisted module imports in expression position', () => { @@ -337,8 +346,8 @@ describe('unit test', () => { } `; checkThrows( - { 'file.d.ts': input }, 'file.d.ts(2,32): error: Module identifier "foo" is not allowed. ' + - 'Remove it from source or whitelist it via --allowModuleIdentifiers.'); + {'file.d.ts': input}, 'file.d.ts(2,32): error: Module identifier "foo" is not allowed. ' + + 'Remove it from source or whitelist it via --allowModuleIdentifiers.'); }); it('should throw on using non-whitelisted module imports in type position', () => { @@ -347,8 +356,8 @@ describe('unit test', () => { export type A = foo.A; `; checkThrows( - { 'file.d.ts': input }, 'file.d.ts(2,17): error: Module identifier "foo" is not allowed. ' + - 'Remove it from source or whitelist it via --allowModuleIdentifiers.'); + {'file.d.ts': input}, 'file.d.ts(2,17): error: Module identifier "foo" is not allowed. ' + + 'Remove it from source or whitelist it via --allowModuleIdentifiers.'); }); it('should not throw on using whitelisted module imports', () => { @@ -361,7 +370,7 @@ describe('unit test', () => { export declare class A extends foo.A { } `; - check({ 'file.d.ts': input }, expected, { allowModuleIdentifiers: ['foo'] }); + check({'file.d.ts': input}, expected, {allowModuleIdentifiers: ['foo']}); }); it('should not throw if non-whitelisted module imports are not written', () => { @@ -374,7 +383,7 @@ describe('unit test', () => { export declare class A { } `; - check({ 'file.d.ts': input }, expected); + check({'file.d.ts': input}, expected); }); it('should keep stability annotations of exports in docstrings', () => { @@ -404,7 +413,7 @@ describe('unit test', () => { /** @stable */ export declare var c: number; `; - check({ 'file.d.ts': input }, expected); + check({'file.d.ts': input}, expected); }); it('should keep stability annotations of fields in docstrings', () => { @@ -431,7 +440,7 @@ describe('unit test', () => { /** @deprecated */ foo(): void; } `; - check({ 'file.d.ts': input }, expected); + check({'file.d.ts': input}, expected); }); it('should warn on onStabilityMissing: warn', () => { @@ -445,20 +454,20 @@ describe('unit test', () => { constructor(); } `; - check({ 'file.d.ts': input }, expected, { onStabilityMissing: 'warn' }); + check({'file.d.ts': input}, expected, {onStabilityMissing: 'warn'}); chai.assert.deepEqual( - warnings, ['file.d.ts(1,1): error: No stability annotation found for symbol "A"']); + warnings, ['file.d.ts(1,1): error: No stability annotation found for symbol "A"']); }); }); -function getMockHost(files: { [name: string]: string }): ts.CompilerHost { +function getMockHost(files: {[name: string]: string}): ts.CompilerHost { return { getSourceFile: (sourceName, languageVersion) => { if (!files[sourceName]) return undefined; return ts.createSourceFile( - sourceName, stripExtraIndentation(files[sourceName]), languageVersion, true); + sourceName, stripExtraIndentation(files[sourceName]), languageVersion, true); }, - writeFile: (name, text, writeByteOrderMark) => { }, + writeFile: (name, text, writeByteOrderMark) => {}, fileExists: (filename) => !!files[filename], readFile: (filename) => stripExtraIndentation(files[filename]), getDefaultLibFileName: () => 'lib.ts', @@ -471,12 +480,12 @@ function getMockHost(files: { [name: string]: string }): ts.CompilerHost { } function check( - files: { [name: string]: string }, expected: string, options: SerializationOptions = {}) { + files: {[name: string]: string}, expected: string, options: SerializationOptions = {}) { const actual = publicApiInternal(getMockHost(files), 'file.d.ts', {}, options); chai.assert.equal(actual.trim(), stripExtraIndentation(expected).trim()); } -function checkThrows(files: { [name: string]: string }, error: string) { +function checkThrows(files: {[name: string]: string}, error: string) { chai.assert.throws(() => { publicApiInternal(getMockHost(files), 'file.d.ts', {}); }, error); } @@ -485,7 +494,7 @@ function stripExtraIndentation(text: string) { // Ignore first and last new line lines = lines.slice(1, lines.length - 1); const commonIndent = lines.reduce((min, line) => { - const indent = /^( *)/.exec(line)[1].length; + const indent = /^( *)/.exec(line) ![1].length; // Ignore empty line return line.length ? Math.min(min, indent) : min; }, text.length); diff --git a/tools/ts-api-guardian/yarn.lock b/tools/ts-api-guardian/yarn.lock new file mode 100644 index 0000000000..feca0347eb --- /dev/null +++ b/tools/ts-api-guardian/yarn.lock @@ -0,0 +1,2336 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@gulp-sourcemaps/identity-map@1.X": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@gulp-sourcemaps/identity-map/-/identity-map-1.0.1.tgz#cfa23bc5840f9104ce32a65e74db7e7a974bbee1" + dependencies: + acorn "^5.0.3" + css "^2.2.1" + normalize-path "^2.1.1" + source-map "^0.5.6" + through2 "^2.0.3" + +"@gulp-sourcemaps/map-sources@1.X": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz#890ae7c5d8c877f6d384860215ace9d7ec945bda" + dependencies: + normalize-path "^2.0.1" + through2 "^2.0.3" + +"@types/chai@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.2.tgz#f1af664769cfb50af805431c407425ed619daa21" + +"@types/diff@^3.2.2": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@types/diff/-/diff-3.2.2.tgz#4d6f45537322a7a420d353a0939513c7e96d14a6" + +"@types/minimist@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" + +"@types/mocha@^2.2.48": + version "2.2.48" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.48.tgz#3523b126a0b049482e1c3c11877460f76622ffab" + +"@types/node@^0.12.15": + version "0.12.15" + resolved "https://registry.yarnpkg.com/@types/node/-/node-0.12.15.tgz#8511cb3edcf3fc7852bc877b0680257ddee70d5c" + +acorn@5.X, acorn@^5.0.3: + version "5.5.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.0.tgz#1abb587fbf051f94e3de20e6b26ef910b1828298" + +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + +ansi-colors@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9" + dependencies: + ansi-wrap "^0.1.0" + +ansi-cyan@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-cyan/-/ansi-cyan-0.1.1.tgz#538ae528af8982f28ae30d86f2f17456d2609873" + dependencies: + ansi-wrap "0.1.0" + +ansi-gray@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251" + dependencies: + ansi-wrap "0.1.0" + +ansi-red@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-red/-/ansi-red-0.1.1.tgz#8c638f9d1080800a353c9c28c8a81ca4705d946c" + dependencies: + ansi-wrap "0.1.0" + +ansi-regex@^2.0.0, ansi-regex@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + +ansi-styles@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" + dependencies: + color-convert "^1.9.0" + +ansi-wrap@0.1.0, ansi-wrap@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" + +append-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" + dependencies: + buffer-equal "^1.0.0" + +archy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + +arr-diff@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-1.1.0.tgz#687c32758163588fef7de7b36fabe495eb1a399a" + dependencies: + arr-flatten "^1.0.1" + array-slice "^0.2.3" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + +arr-flatten@^1.0.1, arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + +arr-union@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-2.1.0.tgz#20f9eab5ec70f5c7d215b1077b1c39161d292c7d" + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + +array-differ@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" + +array-each@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" + +array-slice@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" + +array-slice@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" + +array-uniq@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + +assertion-error@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + +async@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + +atob@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d" + +atob@~1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/atob/-/atob-1.1.3.tgz#95f13629b12c3a51a5d215abdce2aa9f32f80773" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +beeper@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" + +brace-expansion@^1.0.0, brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.1.tgz#7086c913b4e5a08dbe37ac0ee6a2500c4ba691bb" + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + define-property "^1.0.0" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + kind-of "^6.0.2" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +browser-stdout@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" + +buffer-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +chai@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c" + dependencies: + assertion-error "^1.0.1" + check-error "^1.0.1" + deep-eql "^3.0.0" + get-func-name "^2.0.0" + pathval "^1.0.0" + type-detect "^4.0.0" + +chalk@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796" + dependencies: + ansi-styles "^3.2.0" + escape-string-regexp "^1.0.5" + supports-color "^5.2.0" + +check-error@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + +clang-format@^1.0.25, clang-format@^1.0.32: + version "1.2.2" + resolved "https://registry.yarnpkg.com/clang-format/-/clang-format-1.2.2.tgz#a7277a03fce9aa4e387ddaa83b60d99dab115737" + dependencies: + async "^1.5.2" + glob "^7.0.0" + resolve "^1.1.6" + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +cli-color@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-1.2.0.tgz#3a5ae74fd76b6267af666e69e2afbbd01def34d1" + dependencies: + ansi-regex "^2.1.1" + d "1" + es5-ext "^0.10.12" + es6-iterator "2" + memoizee "^0.4.3" + timers-ext "0.1" + +clone-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" + +clone-stats@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" + +clone-stats@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" + +clone@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" + +clone@^1.0.0, clone@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f" + +clone@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" + +cloneable-readable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.0.0.tgz#a6290d413f217a61232f95e458ff38418cfb0117" + dependencies: + inherits "^2.0.1" + process-nextick-args "^1.0.6" + through2 "^2.0.1" + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" + dependencies: + color-name "^1.1.1" + +color-name@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + +color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + +commander@2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" + +component-emitter@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +convert-source-map@1.X, convert-source-map@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + +cross-spawn@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + +css@2.X, css@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/css/-/css-2.2.1.tgz#73a4c81de85db664d4ee674f7d47085e3b2d55dc" + dependencies: + inherits "^2.0.1" + source-map "^0.1.38" + source-map-resolve "^0.3.0" + urix "^0.1.0" + +d@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" + dependencies: + es5-ext "^0.10.9" + +dargs@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dargs/-/dargs-5.1.0.tgz#ec7ea50c78564cd36c9d5ec18f66329fade27829" + +dateformat@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" + +debug-fabulous@1.X: + version "1.0.0" + resolved "https://registry.yarnpkg.com/debug-fabulous/-/debug-fabulous-1.0.0.tgz#57f6648646097b1b0849dcda0017362c1ec00f8b" + dependencies: + debug "3.X" + memoizee "0.4.X" + object-assign "4.X" + +debug@3.1.0, debug@3.X: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + +debug@^2.2.0, debug@^2.3.3: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + +deep-eql@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + dependencies: + type-detect "^4.0.0" + +defaults@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + dependencies: + clone "^1.0.2" + +define-properties@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" + dependencies: + foreach "^2.0.5" + object-keys "^1.0.8" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +deprecated@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/deprecated/-/deprecated-0.0.1.tgz#f9c9af5464afa1e7a971458a8bdef2aa94d5bb19" + +detect-file@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" + +detect-newline@2.X: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" + +diff@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" + +diff@^2.0.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99" + +diff@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" + +duplexer2@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" + dependencies: + readable-stream "~1.1.9" + +duplexer2@~0.1.0: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + dependencies: + readable-stream "^2.0.2" + +duplexer@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" + +duplexify@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.3.tgz#8b5818800df92fd0125b27ab896491912858243e" + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +end-of-stream@^1.0.0, end-of-stream@^1.1.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" + dependencies: + once "^1.4.0" + +end-of-stream@~0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-0.1.5.tgz#8e177206c3c80837d85632e8b9359dfe8b2f6eaf" + dependencies: + once "~1.3.0" + +es5-ext@^0.10.12, es5-ext@^0.10.14, es5-ext@^0.10.30, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.2: + version "0.10.39" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.39.tgz#fca21b67559277ca4ac1a1ed7048b107b6f76d87" + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.1" + +es6-iterator@2, es6-iterator@^2.0.1, es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" + dependencies: + d "1" + es5-ext "~0.10.14" + +es6-weak-map@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" + dependencies: + d "1" + es5-ext "^0.10.14" + es6-iterator "^2.0.1" + es6-symbol "^3.1.1" + +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +event-emitter@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + dependencies: + d "1" + es5-ext "~0.10.14" + +event-stream@^3.1.5: + version "3.3.4" + resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" + dependencies: + duplexer "~0.1.1" + from "~0" + map-stream "~0.1.0" + pause-stream "0.0.11" + split "0.3" + stream-combiner "~0.0.4" + through "~2.3.1" + +execa@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expand-tilde@^2.0.0, expand-tilde@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" + dependencies: + homedir-polyfill "^1.0.1" + +extend-shallow@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-1.1.4.tgz#19d6bf94dfc09d76ba711f39b872d21ff4dd9071" + dependencies: + kind-of "^1.1.0" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +fancy-log@^1.1.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" + dependencies: + ansi-gray "^0.1.1" + color-support "^1.1.3" + time-stamp "^1.0.0" + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +find-index@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4" + +findup-sync@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" + dependencies: + detect-file "^1.0.0" + is-glob "^3.1.0" + micromatch "^3.0.4" + resolve-dir "^1.0.1" + +fined@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fined/-/fined-1.1.0.tgz#b37dc844b76a2f5e7081e884f7c0ae344f153476" + dependencies: + expand-tilde "^2.0.2" + is-plain-object "^2.0.3" + object.defaults "^1.1.0" + object.pick "^1.2.0" + parse-filepath "^1.0.1" + +first-chunk-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e" + +flagged-respawn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.0.tgz#4e79ae9b2eb38bf86b3bb56bf3e0a56aa5fcabd7" + +flush-write-stream@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417" + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.4" + +for-in@^1.0.1, for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + +for-own@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" + dependencies: + for-in "^1.0.1" + +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + dependencies: + map-cache "^0.2.2" + +from@~0: + version "0.1.7" + resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" + +fs-mkdirp-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" + dependencies: + graceful-fs "^4.1.11" + through2 "^2.0.3" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + +gaze@^0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/gaze/-/gaze-0.5.2.tgz#40b709537d24d1d45767db5a908689dfe69ac44f" + dependencies: + globule "~0.1.0" + +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-stream@^3.1.5: + version "3.1.18" + resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-3.1.18.tgz#9170a5f12b790306fdfe598f313f8f7954fd143b" + dependencies: + glob "^4.3.1" + glob2base "^0.0.12" + minimatch "^2.0.1" + ordered-read-streams "^0.1.0" + through2 "^0.6.1" + unique-stream "^1.0.0" + +glob-stream@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4" + dependencies: + extend "^3.0.0" + glob "^7.1.1" + glob-parent "^3.1.0" + is-negated-glob "^1.0.0" + ordered-read-streams "^1.0.0" + pumpify "^1.3.5" + readable-stream "^2.1.5" + remove-trailing-separator "^1.0.1" + to-absolute-glob "^2.0.0" + unique-stream "^2.0.2" + +glob-watcher@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b" + dependencies: + gaze "^0.5.1" + +glob2base@^0.0.12: + version "0.0.12" + resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56" + dependencies: + find-index "^0.1.1" + +glob@7.1.2, glob@^7.0.0, glob@^7.0.6, glob@^7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^4.3.1: + version "4.5.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "^2.0.1" + once "^1.3.0" + +glob@~3.1.21: + version "3.1.21" + resolved "https://registry.yarnpkg.com/glob/-/glob-3.1.21.tgz#d29e0a055dea5138f4d07ed40e8982e83c2066cd" + dependencies: + graceful-fs "~1.2.0" + inherits "1" + minimatch "~0.2.11" + +global-modules@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" + dependencies: + global-prefix "^1.0.1" + is-windows "^1.0.1" + resolve-dir "^1.0.0" + +global-prefix@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" + dependencies: + expand-tilde "^2.0.2" + homedir-polyfill "^1.0.1" + ini "^1.3.4" + is-windows "^1.0.1" + which "^1.2.14" + +globule@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/globule/-/globule-0.1.0.tgz#d9c8edde1da79d125a151b79533b978676346ae5" + dependencies: + glob "~3.1.21" + lodash "~1.0.1" + minimatch "~0.2.11" + +glogg@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.1.tgz#dcf758e44789cc3f3d32c1f3562a3676e6a34810" + dependencies: + sparkles "^1.0.0" + +graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.6: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + +graceful-fs@^3.0.0: + version "3.0.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.11.tgz#7613c778a1afea62f25c630a086d7f3acbbdd818" + dependencies: + natives "^1.1.0" + +graceful-fs@~1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364" + +growl@1.10.3: + version "1.10.3" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" + +gulp-clang-format@^1.0.25: + version "1.0.25" + resolved "https://registry.yarnpkg.com/gulp-clang-format/-/gulp-clang-format-1.0.25.tgz#5176567b9f72067bfb55c9204c8ef03115c3ff49" + dependencies: + clang-format "^1.0.32" + gulp-diff "^1.0.0" + gulp-util "^3.0.4" + pkginfo "^0.3.0" + stream-combiner2 "^1.1.1" + stream-equal "0.1.6" + through2 "^0.6.3" + +gulp-diff@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gulp-diff/-/gulp-diff-1.0.0.tgz#101b23712dd6b107bd07d05ab88ea3ac485fed77" + dependencies: + cli-color "^1.0.0" + diff "^2.0.2" + event-stream "^3.1.5" + gulp-util "^3.0.6" + through2 "^2.0.0" + +gulp-mocha@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/gulp-mocha/-/gulp-mocha-5.0.0.tgz#ed70ebd5fadae6c98d87af13dbbad5a602e217b2" + dependencies: + dargs "^5.1.0" + execa "^0.8.0" + mocha "^4.1.0" + npm-run-path "^2.0.2" + plugin-error "^0.1.2" + through2 "^2.0.3" + +gulp-sourcemaps@^2.6.4: + version "2.6.4" + resolved "https://registry.yarnpkg.com/gulp-sourcemaps/-/gulp-sourcemaps-2.6.4.tgz#cbb2008450b1bcce6cd23bf98337be751bf6e30a" + dependencies: + "@gulp-sourcemaps/identity-map" "1.X" + "@gulp-sourcemaps/map-sources" "1.X" + acorn "5.X" + convert-source-map "1.X" + css "2.X" + debug-fabulous "1.X" + detect-newline "2.X" + graceful-fs "4.X" + source-map "~0.6.0" + strip-bom-string "1.X" + through2 "2.X" + +gulp-typescript@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/gulp-typescript/-/gulp-typescript-4.0.1.tgz#fd9d2e06a06ea3c1c15885b82ebfb037c07d75b2" + dependencies: + ansi-colors "^1.0.1" + plugin-error "^0.1.2" + source-map "^0.6.1" + through2 "^2.0.3" + vinyl "^2.1.0" + vinyl-fs "^3.0.0" + +gulp-util@^3.0.0, gulp-util@^3.0.4, gulp-util@^3.0.6, gulp-util@^3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" + dependencies: + array-differ "^1.0.0" + array-uniq "^1.0.2" + beeper "^1.0.0" + chalk "^1.0.0" + dateformat "^2.0.0" + fancy-log "^1.1.0" + gulplog "^1.0.0" + has-gulplog "^0.1.0" + lodash._reescape "^3.0.0" + lodash._reevaluate "^3.0.0" + lodash._reinterpolate "^3.0.0" + lodash.template "^3.0.0" + minimist "^1.1.0" + multipipe "^0.1.2" + object-assign "^3.0.0" + replace-ext "0.0.1" + through2 "^2.0.0" + vinyl "^0.5.0" + +gulp@^3.8.11: + version "3.9.1" + resolved "https://registry.yarnpkg.com/gulp/-/gulp-3.9.1.tgz#571ce45928dd40af6514fc4011866016c13845b4" + dependencies: + archy "^1.0.0" + chalk "^1.0.0" + deprecated "^0.0.1" + gulp-util "^3.0.0" + interpret "^1.0.0" + liftoff "^2.1.0" + minimist "^1.1.0" + orchestrator "^0.3.0" + pretty-hrtime "^1.0.0" + semver "^4.1.0" + tildify "^1.0.0" + v8flags "^2.0.2" + vinyl-fs "^0.3.0" + +gulplog@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gulplog/-/gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5" + dependencies: + glogg "^1.0.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + dependencies: + ansi-regex "^2.0.0" + +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + +has-gulplog@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce" + dependencies: + sparkles "^1.0.0" + +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +he@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" + +homedir-polyfill@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" + dependencies: + parse-passwd "^1.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +ini@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + +interpret@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" + +is-absolute@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" + dependencies: + is-relative "^1.0.0" + is-windows "^1.0.1" + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + dependencies: + kind-of "^6.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + dependencies: + kind-of "^6.0.0" + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + dependencies: + is-extglob "^2.1.0" + +is-negated-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + dependencies: + kind-of "^3.0.2" + +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + +is-odd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24" + dependencies: + is-number "^4.0.0" + +is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + dependencies: + isobject "^3.0.1" + +is-promise@^2.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + +is-relative@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" + dependencies: + is-unc-path "^1.0.0" + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + +is-unc-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" + dependencies: + unc-path-regex "^0.1.2" + +is-utf8@^0.2.0, is-utf8@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + +is-valid-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" + +is-windows@^1.0.1, is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + +isarray@1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + +jasmine-core@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.1.0.tgz#a4785e135d5df65024dfc9224953df585bd2766c" + +jasmine@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.1.0.tgz#2bd59fd7ec6ec0e8acb64e09f45a68ed2ad1952a" + dependencies: + glob "^7.0.6" + jasmine-core "~3.1.0" + +json-stable-stringify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + +kind-of@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.1.0, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" + +lazy-cache@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264" + dependencies: + set-getter "^0.1.0" + +lazystream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" + dependencies: + readable-stream "^2.0.5" + +lead@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" + dependencies: + flush-write-stream "^1.0.2" + +liftoff@^2.1.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.5.0.tgz#2009291bb31cea861bbf10a7c15a28caf75c31ec" + dependencies: + extend "^3.0.0" + findup-sync "^2.0.0" + fined "^1.0.1" + flagged-respawn "^1.0.0" + is-plain-object "^2.0.4" + object.map "^1.0.0" + rechoir "^0.6.2" + resolve "^1.1.7" + +lodash._basecopy@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" + +lodash._basetostring@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5" + +lodash._basevalues@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7" + +lodash._getnative@^3.0.0: + version "3.9.1" + resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" + +lodash._isiterateecall@^3.0.0: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" + +lodash._reescape@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a" + +lodash._reevaluate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed" + +lodash._reinterpolate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + +lodash._root@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" + +lodash.escape@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698" + dependencies: + lodash._root "^3.0.0" + +lodash.isarguments@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + +lodash.isarray@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" + +lodash.keys@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" + dependencies: + lodash._getnative "^3.0.0" + lodash.isarguments "^3.0.0" + lodash.isarray "^3.0.0" + +lodash.restparam@^3.0.0: + version "3.6.1" + resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" + +lodash.template@^3.0.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" + dependencies: + lodash._basecopy "^3.0.0" + lodash._basetostring "^3.0.0" + lodash._basevalues "^3.0.0" + lodash._isiterateecall "^3.0.0" + lodash._reinterpolate "^3.0.0" + lodash.escape "^3.0.0" + lodash.keys "^3.0.0" + lodash.restparam "^3.0.0" + lodash.templatesettings "^3.0.0" + +lodash.templatesettings@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5" + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.escape "^3.0.0" + +lodash@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" + +lru-cache@2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" + +lru-cache@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +lru-queue@0.1: + version "0.1.0" + resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" + dependencies: + es5-ext "~0.10.2" + +make-iterator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.0.tgz#57bef5dc85d23923ba23767324d8e8f8f3d9694b" + dependencies: + kind-of "^3.1.0" + +map-cache@^0.2.0, map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + +map-stream@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + dependencies: + object-visit "^1.0.0" + +memoizee@0.4.X, memoizee@^0.4.3: + version "0.4.12" + resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.12.tgz#780e99a219c50c549be6d0fc61765080975c58fb" + dependencies: + d "1" + es5-ext "^0.10.30" + es6-weak-map "^2.0.2" + event-emitter "^0.3.5" + is-promise "^2.1" + lru-queue "0.1" + next-tick "1" + timers-ext "^0.1.2" + +merge2@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.1.tgz#271d2516ff52d4af7f7b710b8bf3e16e183fef66" + +micromatch@^3.0.4: + version "3.1.9" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.9.tgz#15dc93175ae39e52e93087847096effc73efcf89" + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +minimatch@^2.0.1: + version "2.0.10" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7" + dependencies: + brace-expansion "^1.0.0" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimatch@~0.2.11: + version "0.2.14" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a" + dependencies: + lru-cache "2" + sigmund "~1.0.0" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@^1.1.0, minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +mixin-deep@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@0.5.1, mkdirp@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +mocha@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.1.0.tgz#7d86cfbcf35cb829e2754c32e17355ec05338794" + dependencies: + browser-stdout "1.3.0" + commander "2.11.0" + debug "3.1.0" + diff "3.3.1" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.3" + he "1.1.1" + mkdirp "0.5.1" + supports-color "4.4.0" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +multipipe@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" + dependencies: + duplexer2 "0.0.2" + +nanomatch@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2" + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-odd "^2.0.0" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +natives@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.1.tgz#011acce1f7cbd87f7ba6b3093d6cd9392be1c574" + +next-tick@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + +normalize-path@^2.0.1, normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + dependencies: + remove-trailing-separator "^1.0.1" + +now-and-later@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.0.tgz#bc61cbb456d79cb32207ce47ca05136ff2e7d6ee" + dependencies: + once "^1.3.2" + +npm-run-path@^2.0.0, npm-run-path@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + dependencies: + path-key "^2.0.0" + +object-assign@4.X: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +object-assign@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-keys@^1.0.11, object-keys@^1.0.8: + version "1.0.11" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + dependencies: + isobject "^3.0.0" + +object.assign@^4.0.4: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.defaults@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" + dependencies: + array-each "^1.0.1" + array-slice "^1.0.0" + for-own "^1.0.0" + isobject "^3.0.0" + +object.map@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37" + dependencies: + for-own "^1.0.0" + make-iterator "^1.0.0" + +object.pick@^1.2.0, object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + dependencies: + isobject "^3.0.1" + +once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +once@~1.3.0: + version "1.3.3" + resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20" + dependencies: + wrappy "1" + +orchestrator@^0.3.0: + version "0.3.8" + resolved "https://registry.yarnpkg.com/orchestrator/-/orchestrator-0.3.8.tgz#14e7e9e2764f7315fbac184e506c7aa6df94ad7e" + dependencies: + end-of-stream "~0.1.5" + sequencify "~0.0.7" + stream-consume "~0.1.0" + +ordered-read-streams@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz#fd565a9af8eb4473ba69b6ed8a34352cb552f126" + +ordered-read-streams@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e" + dependencies: + readable-stream "^2.0.1" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + +parse-filepath@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" + dependencies: + is-absolute "^1.0.0" + map-cache "^0.2.0" + path-root "^0.1.1" + +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-key@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + +path-parse@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + +path-root-regex@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" + +path-root@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" + dependencies: + path-root-regex "^0.1.0" + +pathval@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" + +pause-stream@0.0.11: + version "0.0.11" + resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" + dependencies: + through "~2.3" + +pkginfo@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21" + +plugin-error@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace" + dependencies: + ansi-cyan "^0.1.1" + ansi-red "^0.1.1" + arr-diff "^1.0.1" + arr-union "^2.0.1" + extend-shallow "^1.1.2" + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + +pretty-hrtime@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" + +process-nextick-args@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.5: + version "1.4.0" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.4.0.tgz#80b7c5df7e24153d03f0e7ac8a05a5d068bd07fb" + dependencies: + duplexify "^3.5.3" + inherits "^2.0.3" + pump "^2.0.0" + +"readable-stream@>=1.0.33-1 <1.1.0-0": + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.3.3: + version "2.3.4" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.4.tgz#c946c3f47fa7d8eabc0b6150f4a12f69a4574071" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.0.3" + util-deprecate "~1.0.1" + +readable-stream@~1.1.9: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + dependencies: + resolve "^1.1.6" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +remove-bom-buffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" + dependencies: + is-buffer "^1.1.5" + is-utf8 "^0.2.1" + +remove-bom-stream@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz#05f1a593f16e42e1fb90ebf59de8e569525f9523" + dependencies: + remove-bom-buffer "^3.0.0" + safe-buffer "^5.1.0" + through2 "^2.0.3" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + +repeat-element@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + +replace-ext@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" + +replace-ext@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" + +resolve-dir@^1.0.0, resolve-dir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" + dependencies: + expand-tilde "^2.0.0" + global-modules "^1.0.0" + +resolve-options@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" + dependencies: + value-or-function "^3.0.0" + +resolve-url@^0.2.1, resolve-url@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + +resolve@^1.1.6, resolve@^1.1.7: + version "1.5.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" + dependencies: + path-parse "^1.0.5" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + +safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + dependencies: + ret "~0.1.10" + +semver@^4.1.0: + version "4.3.6" + resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" + +sequencify@~0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/sequencify/-/sequencify-0.0.7.tgz#90cff19d02e07027fd767f5ead3e7b95d1e7380c" + +set-getter@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376" + dependencies: + to-object-path "^0.3.0" + +set-value@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.1" + to-object-path "^0.3.0" + +set-value@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + +sigmund@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" + +signal-exit@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.1.tgz#e12b5487faded3e3dea0ac91e9400bf75b401370" + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^2.0.0" + +source-map-resolve@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.3.1.tgz#610f6122a445b8dd51535a2a71b783dfc1248761" + dependencies: + atob "~1.1.0" + resolve-url "~0.2.1" + source-map-url "~0.3.0" + urix "~0.1.0" + +source-map-resolve@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.1.tgz#7ad0f593f2281598e854df80f19aae4b92d7a11a" + dependencies: + atob "^2.0.0" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.3.tgz#2b3d5fff298cfa4d1afd7d4352d569e9a0158e76" + dependencies: + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + +source-map-url@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.3.0.tgz#7ecaf13b57bcd09da8a40c5d269db33799d4aaf9" + +source-map@^0.1.38: + version "0.1.43" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" + dependencies: + amdefine ">=0.0.4" + +source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + +source-map@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.2.tgz#115c3e891aaa9a484869fd2b89391a225feba344" + +sparkles@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + dependencies: + extend-shallow "^3.0.0" + +split@0.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" + dependencies: + through "2" + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +stream-combiner2@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" + dependencies: + duplexer2 "~0.1.0" + readable-stream "^2.0.2" + +stream-combiner@~0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" + dependencies: + duplexer "~0.1.1" + +stream-consume@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.1.tgz#d3bdb598c2bd0ae82b8cac7ac50b1107a7996c48" + +stream-equal@0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/stream-equal/-/stream-equal-0.1.6.tgz#cc522fab38516012e4d4ee47513b147b72359019" + +stream-shift@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + +string_decoder@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +strip-bom-string@1.X: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" + +strip-bom@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-1.0.0.tgz#85b8862f3844b5a6d5ec8467a93598173a36f794" + dependencies: + first-chunk-stream "^1.0.0" + is-utf8 "^0.2.0" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + +supports-color@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" + dependencies: + has-flag "^2.0.0" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + +supports-color@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.2.0.tgz#b0d5333b1184dd3666cbe5aa0b45c5ac7ac17a4a" + dependencies: + has-flag "^3.0.0" + +through2-filter@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec" + dependencies: + through2 "~2.0.0" + xtend "~4.0.0" + +through2@2.X, through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" + dependencies: + readable-stream "^2.1.5" + xtend "~4.0.1" + +through2@^0.6.1, through2@^0.6.3: + version "0.6.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" + dependencies: + readable-stream ">=1.0.33-1 <1.1.0-0" + xtend ">=4.0.0 <4.1.0-0" + +through@2, through@~2.3, through@~2.3.1: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + +tildify@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tildify/-/tildify-1.2.0.tgz#dcec03f55dca9b7aa3e5b04f21817eb56e63588a" + dependencies: + os-homedir "^1.0.0" + +time-stamp@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" + +timers-ext@0.1, timers-ext@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.2.tgz#61cc47a76c1abd3195f14527f978d58ae94c5204" + dependencies: + es5-ext "~0.10.14" + next-tick "1" + +to-absolute-glob@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b" + dependencies: + is-absolute "^1.0.0" + is-negated-glob "^1.0.0" + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +to-through@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6" + dependencies: + through2 "^2.0.3" + +type-detect@^4.0.0: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + +typescript@~2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" + +unc-path-regex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + +union-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^0.4.3" + +unique-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-1.0.0.tgz#d59a4a75427447d9aa6c91e70263f8d26a4b104b" + +unique-stream@^2.0.2: + version "2.2.1" + resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369" + dependencies: + json-stable-stringify "^1.0.0" + through2-filter "^2.0.0" + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +urix@^0.1.0, urix@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + +use@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8" + dependencies: + define-property "^0.2.5" + isobject "^3.0.0" + lazy-cache "^2.0.2" + +user-home@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +v8flags@^2.0.2: + version "2.1.1" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" + dependencies: + user-home "^1.1.1" + +value-or-function@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" + +vinyl-fs@^0.3.0: + version "0.3.14" + resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-0.3.14.tgz#9a6851ce1cac1c1cea5fe86c0931d620c2cfa9e6" + dependencies: + defaults "^1.0.0" + glob-stream "^3.1.5" + glob-watcher "^0.0.6" + graceful-fs "^3.0.0" + mkdirp "^0.5.0" + strip-bom "^1.0.0" + through2 "^0.6.1" + vinyl "^0.4.0" + +vinyl-fs@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.2.tgz#1b86258844383f57581fcaac081fe09ef6d6d752" + dependencies: + fs-mkdirp-stream "^1.0.0" + glob-stream "^6.1.0" + graceful-fs "^4.0.0" + is-valid-glob "^1.0.0" + lazystream "^1.0.0" + lead "^1.0.0" + object.assign "^4.0.4" + pumpify "^1.3.5" + readable-stream "^2.3.3" + remove-bom-buffer "^3.0.0" + remove-bom-stream "^1.2.0" + resolve-options "^1.1.0" + through2 "^2.0.0" + to-through "^2.0.0" + value-or-function "^3.0.0" + vinyl "^2.0.0" + vinyl-sourcemap "^1.1.0" + +vinyl-sourcemap@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz#92a800593a38703a8cdb11d8b300ad4be63b3e16" + dependencies: + append-buffer "^1.0.2" + convert-source-map "^1.5.0" + graceful-fs "^4.1.6" + normalize-path "^2.1.1" + now-and-later "^2.0.0" + remove-bom-buffer "^3.0.0" + vinyl "^2.0.0" + +vinyl@^0.4.0: + version "0.4.6" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" + dependencies: + clone "^0.2.0" + clone-stats "^0.0.1" + +vinyl@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde" + dependencies: + clone "^1.0.0" + clone-stats "^0.0.1" + replace-ext "0.0.1" + +vinyl@^2.0.0, vinyl@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.1.0.tgz#021f9c2cf951d6b939943c89eb5ee5add4fd924c" + dependencies: + clone "^2.1.1" + clone-buffer "^1.0.0" + clone-stats "^1.0.0" + cloneable-readable "^1.0.0" + remove-trailing-separator "^1.0.1" + replace-ext "^1.0.0" + +which@^1.2.14, which@^1.2.9: + version "1.3.0" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" + dependencies: + isexe "^2.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +"xtend@>=4.0.0 <4.1.0-0", xtend@~4.0.0, xtend@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" diff --git a/tools/tsconfig.json b/tools/tsconfig.json index 8ee95a79b3..e83d703b6b 100644 --- a/tools/tsconfig.json +++ b/tools/tsconfig.json @@ -23,6 +23,7 @@ "exclude": [ "testing", "node_modules", + "ts-api-guardian", "typings-test", "public_api_guard", "docs" diff --git a/yarn.lock b/yarn.lock index e3ff22494d..fbd2acb040 100644 --- a/yarn.lock +++ b/yarn.lock @@ -288,9 +288,9 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" -ansi-styles@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" dependencies: color-convert "^1.9.0" @@ -1008,12 +1008,12 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: supports-color "^2.0.0" chalk@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796" + version "2.3.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65" dependencies: - ansi-styles "^3.2.0" + ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" - supports-color "^5.2.0" + supports-color "^5.3.0" char-spinner@^1.0.1: version "1.0.1" @@ -6871,9 +6871,9 @@ supports-color@^3.1.0: dependencies: has-flag "^1.0.0" -supports-color@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.2.0.tgz#b0d5333b1184dd3666cbe5aa0b45c5ac7ac17a4a" +supports-color@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.3.0.tgz#5b24ac15db80fa927cf5227a4a33fd3c4c7676c0" dependencies: has-flag "^3.0.0" From 69d359bb51a4b495b0354a3cbac749a177396dd5 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 28 Feb 2018 21:28:54 -0800 Subject: [PATCH 036/604] refactor(ivy): break compiler canonical test into smaller files (#22510) PR Close #22510 --- .../compiler_canonical_spec.ts | 1628 ----------------- .../component_directives_spec.ts | 844 +++++++++ .../content_projection_spec.ts | 94 + .../compiler_canonical/elements_spec.ts | 54 + .../compiler_canonical/injection_spec.ts | 63 + .../compiler_canonical/life_cycle_spec.ts | 107 ++ .../local_reference_spec.ts | 44 + .../compiler_canonical/ng_module_spec.ts | 78 + .../render3/compiler_canonical/normative.md | 8 +- .../render3/compiler_canonical/pipes_spec.ts | 80 + .../render3/compiler_canonical/query_spec.ts | 168 ++ .../compiler_canonical/small_app_spec.ts | 1 + .../template_variables_spec.ts | 213 +++ 13 files changed, 1753 insertions(+), 1629 deletions(-) delete mode 100644 packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts create mode 100644 packages/core/test/render3/compiler_canonical/component_directives_spec.ts create mode 100644 packages/core/test/render3/compiler_canonical/content_projection_spec.ts create mode 100644 packages/core/test/render3/compiler_canonical/elements_spec.ts create mode 100644 packages/core/test/render3/compiler_canonical/injection_spec.ts create mode 100644 packages/core/test/render3/compiler_canonical/life_cycle_spec.ts create mode 100644 packages/core/test/render3/compiler_canonical/local_reference_spec.ts create mode 100644 packages/core/test/render3/compiler_canonical/ng_module_spec.ts create mode 100644 packages/core/test/render3/compiler_canonical/pipes_spec.ts create mode 100644 packages/core/test/render3/compiler_canonical/query_spec.ts create mode 100644 packages/core/test/render3/compiler_canonical/template_variables_spec.ts diff --git a/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts b/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts deleted file mode 100644 index b44e41b93b..0000000000 --- a/packages/core/test/render3/compiler_canonical/compiler_canonical_spec.ts +++ /dev/null @@ -1,1628 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; -import * as $r3$ from '../../../src/core_render3_private_export'; -import {renderComponent, toHtml} from '../render_util'; - -/** - * NORMATIVE => /NORMATIVE: Designates what the compiler is expected to generate. - * - * All local variable names are considered non-normative (informative). They should be - * wrapped in $ on each end to simplify testing on the compiler side. - */ - -describe('compiler specification', () => { - // Saving type as $boolean$, etc to simplify testing for compiler, as types aren't saved - type $boolean$ = boolean; - type $any$ = any; - type $number$ = number; - - describe('elements', () => { - it('should translate DOM structure', () => { - type $MyComponent$ = MyComponent; - - // Important: keep arrays outside of function to not create new instances. - const $e0_attrs$ = ['class', 'my-app', 'title', 'Hello']; - - @Component({ - selector: 'my-component', - template: `
Hello World!
` - }) - class MyComponent { - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyComponent, - tag: 'my-component', - factory: () => new MyComponent(), - template: function(ctx: $MyComponent$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, 'div', $e0_attrs$); - $r3$.ɵT(1, 'Hello '); - $r3$.ɵE(2, 'b'); - $r3$.ɵT(3, 'World'); - $r3$.ɵe(); - $r3$.ɵT(4, '!'); - $r3$.ɵe(); - } - } - }); - // /NORMATIVE - } - - expect(renderComp(MyComponent)) - .toEqual('
Hello World!
'); - }); - }); - - describe('components & directives', () => { - it('should instantiate directives', () => { - type $ChildComponent$ = ChildComponent; - type $MyComponent$ = MyComponent; - - const log: string[] = []; - @Component({selector: 'child', template: 'child-view'}) - class ChildComponent { - constructor() { log.push('ChildComponent'); } - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: ChildComponent, - tag: `child`, - factory: () => new ChildComponent(), - template: function(ctx: $ChildComponent$, cm: $boolean$) { - if (cm) { - $r3$.ɵT(0, 'child-view'); - } - } - }); - // /NORMATIVE - } - - @Directive({ - selector: '[some-directive]', - }) - class SomeDirective { - constructor() { log.push('SomeDirective'); } - // NORMATIVE - static ngDirectiveDef = $r3$.ɵdefineDirective({ - type: SomeDirective, - factory: () => new SomeDirective(), - }); - // /NORMATIVE - } - - // Important: keep arrays outside of function to not create new instances. - // NORMATIVE - const $e0_attrs$ = ['some-directive', '']; - const $e0_dirs$ = [SomeDirective]; - // /NORMATIVE - - @Component({selector: 'my-component', template: `!`}) - class MyComponent { - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyComponent, - tag: 'my-component', - factory: () => new MyComponent(), - template: function(ctx: $MyComponent$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, ChildComponent, $e0_attrs$, $e0_dirs$); - $r3$.ɵe(); - $r3$.ɵT(3, '!'); - } - ChildComponent.ngComponentDef.h(1, 0); - SomeDirective.ngDirectiveDef.h(2, 0); - $r3$.ɵr(1, 0); - $r3$.ɵr(2, 0); - } - }); - // /NORMATIVE - } - - expect(renderComp(MyComponent)).toEqual('child-view!'); - expect(log).toEqual(['ChildComponent', 'SomeDirective']); - }); - - it('should support host bindings', () => { - type $MyApp$ = MyApp; - - @Directive({selector: '[hostBindingDir]'}) - class HostBindingDir { - @HostBinding('id') dirId = 'some id'; - - // NORMATIVE - static ngDirectiveDef = $r3$.ɵdefineDirective({ - type: HostBindingDir, - factory: function HostBindingDir_Factory() { return new HostBindingDir(); }, - hostBindings: function HostBindingDir_HostBindings( - dirIndex: $number$, elIndex: $number$) { - $r3$.ɵp(elIndex, 'id', $r3$.ɵb($r3$.ɵld(dirIndex).dirId)); - } - }); - // /NORMATIVE - } - - const $e0_attrs$ = ['hostBindingDir', '']; - const $e0_dirs$ = [HostBindingDir]; - - @Component({ - selector: 'my-app', - template: ` -
- ` - }) - class MyApp { - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyApp, - tag: 'my-app', - factory: function MyApp_Factory() { return new MyApp(); }, - template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, 'div', $e0_attrs$, $e0_dirs$); - $r3$.ɵe(); - } - HostBindingDir.ngDirectiveDef.h(1, 0); - $r3$.ɵr(1, 0); - } - }); - } - - expect(renderComp(MyApp)).toEqual(`
`); - }); - - it('should support host listeners', () => { - type $MyApp$ = MyApp; - - @Directive({selector: '[hostlistenerDir]'}) - class HostListenerDir { - @HostListener('click') - onClick() {} - - // NORMATIVE - static ngDirectiveDef = $r3$.ɵdefineDirective({ - type: HostListenerDir, - factory: function HostListenerDir_Factory() { - const $dir$ = new HostListenerDir(); - $r3$.ɵL( - 'click', function HostListenerDir_click_Handler(event: any) { $dir$.onClick(); }); - return $dir$; - }, - }); - // /NORMATIVE - } - - const $e0_attrs$ = ['hostListenerDir', '']; - const $e0_dirs$ = [HostListenerDir]; - - @Component({ - selector: 'my-app', - template: ` - - ` - }) - class MyApp { - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyApp, - tag: 'my-app', - factory: function MyApp_Factory() { return new MyApp(); }, - template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, 'button', $e0_attrs$, $e0_dirs$); - $r3$.ɵT(2, 'Click'); - $r3$.ɵe(); - } - HostListenerDir.ngDirectiveDef.h(1, 0); - $r3$.ɵr(1, 0); - } - }); - } - - expect(renderComp(MyApp)).toEqual(``); - }); - - - it('should support setting of host attributes', () => { - type $MyApp$ = MyApp; - - @Directive({selector: '[hostAttributeDir]', host: {'role': 'listbox'}}) - class HostAttributeDir { - // NORMATIVE - static ngDirectiveDef = $r3$.ɵdefineDirective({ - type: HostAttributeDir, - factory: function HostAttributeDir_Factory() { return new HostAttributeDir(); }, - attributes: ['role', 'listbox'] - }); - // /NORMATIVE - } - - const $e0_attrs$ = ['hostAttributeDir', '']; - const $e0_dirs$ = [HostAttributeDir]; - - @Component({ - selector: 'my-app', - template: ` -
- ` - }) - class MyApp { - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyApp, - tag: 'my-app', - factory: function MyApp_Factory() { return new MyApp(); }, - template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, 'div', $e0_attrs$, $e0_dirs$); - $r3$.ɵe(); - } - HostAttributeDir.ngDirectiveDef.h(1, 0); - $r3$.ɵr(1, 0); - } - }); - } - - expect(renderComp(MyApp)).toEqual(`
`); - }); - - it('should support bindings of host attributes', () => { - type $MyApp$ = MyApp; - - @Directive({selector: '[hostBindingDir]'}) - class HostBindingDir { - @HostBinding('attr.aria-label') label = 'some label'; - - // NORMATIVE - static ngDirectiveDef = $r3$.ɵdefineDirective({ - type: HostBindingDir, - factory: function HostBindingDir_Factory() { return new HostBindingDir(); }, - hostBindings: function HostBindingDir_HostBindings( - dirIndex: $number$, elIndex: $number$) { - $r3$.ɵa(elIndex, 'aria-label', $r3$.ɵb($r3$.ɵld(dirIndex).label)); - } - }); - // /NORMATIVE - } - - const $e0_attrs$ = ['hostBindingDir', '']; - const $e0_dirs$ = [HostBindingDir]; - - @Component({ - selector: 'my-app', - template: ` -
- ` - }) - class MyApp { - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyApp, - tag: 'my-app', - factory: function MyApp_Factory() { return new MyApp(); }, - template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, 'div', $e0_attrs$, $e0_dirs$); - $r3$.ɵe(); - } - HostBindingDir.ngDirectiveDef.h(1, 0); - $r3$.ɵr(1, 0); - } - }); - } - - expect(renderComp(MyApp)).toEqual(`
`); - }); - - it('should support onPush components', () => { - type $MyApp$ = MyApp; - type $MyComp$ = MyComp; - - @Component({ - selector: 'my-comp', - template: ` - {{ name }} - `, - changeDetection: ChangeDetectionStrategy.OnPush - }) - class MyComp { - @Input() name: string; - - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyComp, - tag: 'my-comp', - factory: function MyComp_Factory() { return new MyComp(); }, - template: function MyComp_Template(ctx: $MyComp$, cm: $boolean$) { - if (cm) { - $r3$.ɵT(0); - } - $r3$.ɵt(0, $r3$.ɵb(ctx.name)); - }, - inputs: {name: 'name'}, - changeDetection: ChangeDetectionStrategy.OnPush - }); - // /NORMATIVE - } - - @Component({ - selector: 'my-app', - template: ` - - ` - }) - class MyApp { - name = 'some name'; - - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyApp, - tag: 'my-app', - factory: function MyApp_Factory() { return new MyApp(); }, - template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, MyComp); - $r3$.ɵe(); - } - $r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name)); - MyComp.ngComponentDef.h(1, 0); - $r3$.ɵr(1, 0); - } - }); - } - - expect(renderComp(MyApp)).toEqual(`some name`); - }); - - xit('should support structural directives', () => { - type $MyComponent$ = MyComponent; - - const log: string[] = []; - @Directive({ - selector: '[if]', - }) - class IfDirective { - constructor(template: TemplateRef) { log.push('ifDirective'); } - // NORMATIVE - static ngDirectiveDef = $r3$.ɵdefineDirective({ - type: IfDirective, - factory: () => new IfDirective($r3$.ɵinjectTemplateRef()), - }); - // /NORMATIVE - } - - // Important: keep arrays outside of function to not create new instances. - // NORMATIVE - const $e0_locals$ = ['foo', '']; - const $c1_dirs$ = [IfDirective]; - // /NORMATIVE - - @Component( - {selector: 'my-component', template: `
  • {{salutation}} {{foo}}
`}) - class MyComponent { - salutation = 'Hello'; - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyComponent, - tag: 'my-component', - factory: () => new MyComponent(), - template: function(ctx: $MyComponent$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, 'ul', null, null, $e0_locals$); - $r3$.ɵC(2, $c1_dirs$, C1); - $r3$.ɵe(); - } - let $foo$ = $r3$.ɵld(1); - $r3$.ɵcR(2); - $r3$.ɵr(3, 2); - $r3$.ɵcr(); - - function C1(ctx1: $any$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, 'li'); - $r3$.ɵT(1); - $r3$.ɵe(); - } - $r3$.ɵt(1, $r3$.ɵi2('', ctx.salutation, ' ', $foo$, '')); - } - } - }); - // /NORMATIVE - } - - expect(renderComp(MyComponent)).toEqual('child-view!'); - expect(log).toEqual(['ChildComponent', 'SomeDirective']); - }); - - describe('value composition', () => { - type $MyArrayComp$ = MyArrayComp; - - @Component({ - selector: 'my-array-comp', - template: ` - {{ names[0] }} {{ names[1] }} - ` - }) - class MyArrayComp { - @Input() names: string[]; - - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyArrayComp, - tag: 'my-array-comp', - factory: function MyArrayComp_Factory() { return new MyArrayComp(); }, - template: function MyArrayComp_Template(ctx: $MyArrayComp$, cm: $boolean$) { - if (cm) { - $r3$.ɵT(0); - } - $r3$.ɵt(0, $r3$.ɵi2('', ctx.names[0], ' ', ctx.names[1], '')); - }, - inputs: {names: 'names'} - }); - } - - it('should support array literals of constants', () => { - type $MyApp$ = MyApp; - - // NORMATIVE - const $e0_arr$ = ['Nancy', 'Bess']; - // /NORMATIVE - - @Component({ - selector: 'my-app', - template: ` - - ` - }) - class MyApp { - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyApp, - tag: 'my-app', - factory: function MyApp_Factory() { return new MyApp(); }, - template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, MyArrayComp); - $r3$.ɵe(); - } - $r3$.ɵp(0, 'names', cm ? $e0_arr$ : $r3$.ɵNC); - MyArrayComp.ngComponentDef.h(1, 0); - $r3$.ɵr(1, 0); - } - }); - // /NORMATIVE - } - - expect(renderComp(MyApp)).toEqual(`Nancy Bess`); - }); - - it('should support array literals of constants inside function calls', () => { - type $MyApp$ = MyApp; - - // NORMATIVE - const $e0_ff$ = () => ['Nancy', 'Bess']; - // /NORMATIVE - - @Component({ - selector: 'my-app', - template: ` - - ` - }) - class MyApp { - someFn(arr: string[]): string[] { - arr[0] = arr[0].toUpperCase(); - return arr; - } - - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyApp, - tag: 'my-app', - factory: function MyApp_Factory() { return new MyApp(); }, - template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, MyArrayComp); - $r3$.ɵe(); - } - $r3$.ɵp(0, 'names', $r3$.ɵb(ctx.someFn($r3$.ɵf0($e0_ff$)))); - MyArrayComp.ngComponentDef.h(1, 0); - $r3$.ɵr(1, 0); - } - }); - // /NORMATIVE - } - - expect(renderComp(MyApp)).toEqual(`NANCY Bess`); - }); - - it('should support array literals of constants inside expressions', () => { - type $MyApp$ = MyApp; - type $MyComp$ = MyComp; - - @Component({selector: 'my-comp', template: `{{ num }}`}) - class MyComp { - num: number; - - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyComp, - tag: 'my-comp', - factory: function MyComp_Factory() { return new MyComp(); }, - template: function MyComp_Template(ctx: $MyComp$, cm: $boolean$) { - if (cm) { - $r3$.ɵT(0); - } - $r3$.ɵt(0, $r3$.ɵb(ctx.num)); - }, - inputs: {num: 'num'} - }); - } - - // NORMATIVE - const $e0_ff$ = () => ['Nancy', 'Bess']; - // /NORMATIVE - - @Component({ - selector: 'my-app', - template: ` - - ` - }) - class MyApp { - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyApp, - tag: 'my-app', - factory: function MyApp_Factory() { return new MyApp(); }, - template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, MyComp); - $r3$.ɵe(); - } - $r3$.ɵp(0, 'num', $r3$.ɵb($r3$.ɵf0($e0_ff$).length + 1)); - MyComp.ngComponentDef.h(1, 0); - $r3$.ɵr(1, 0); - } - }); - // /NORMATIVE - } - - expect(renderComp(MyApp)).toEqual(`3`); - }); - - - it('should support array literals', () => { - type $MyApp$ = MyApp; - - // NORMATIVE - const $e0_ff$ = (v: any) => ['Nancy', v]; - // /NORMATIVE - - @Component({ - selector: 'my-app', - template: ` - - ` - }) - class MyApp { - customName = 'Bess'; - - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyApp, - tag: 'my-app', - factory: function MyApp_Factory() { return new MyApp(); }, - template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, MyArrayComp); - $r3$.ɵe(); - } - $r3$.ɵp(0, 'names', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.customName))); - MyArrayComp.ngComponentDef.h(1, 0); - $r3$.ɵr(1, 0); - } - }); - // /NORMATIVE - } - - expect(renderComp(MyApp)).toEqual(`Nancy Bess`); - }); - - it('should support 9+ bindings in array literals', () => { - type $MyComp$ = MyComp; - - @Component({ - selector: 'my-comp', - template: ` - {{ names[0] }} - {{ names[1] }} - {{ names[3] }} - {{ names[4] }} - {{ names[5] }} - {{ names[6] }} - {{ names[7] }} - {{ names[8] }} - {{ names[9] }} - {{ names[10] }} - {{ names[11] }} - ` - }) - class MyComp { - @Input() names: string[]; - - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyComp, - tag: 'my-comp', - factory: function MyComp_Factory() { return new MyComp(); }, - template: function MyComp_Template(ctx: $MyComp$, cm: $boolean$) { - if (cm) { - $r3$.ɵT(0); - $r3$.ɵT(1); - $r3$.ɵT(2); - $r3$.ɵT(3); - $r3$.ɵT(4); - $r3$.ɵT(5); - $r3$.ɵT(6); - $r3$.ɵT(7); - $r3$.ɵT(8); - $r3$.ɵT(9); - $r3$.ɵT(10); - $r3$.ɵT(11); - } - $r3$.ɵt(0, $r3$.ɵb(ctx.names[0])); - $r3$.ɵt(1, $r3$.ɵb(ctx.names[1])); - $r3$.ɵt(2, $r3$.ɵb(ctx.names[2])); - $r3$.ɵt(3, $r3$.ɵb(ctx.names[3])); - $r3$.ɵt(4, $r3$.ɵb(ctx.names[4])); - $r3$.ɵt(5, $r3$.ɵb(ctx.names[5])); - $r3$.ɵt(6, $r3$.ɵb(ctx.names[6])); - $r3$.ɵt(7, $r3$.ɵb(ctx.names[7])); - $r3$.ɵt(8, $r3$.ɵb(ctx.names[8])); - $r3$.ɵt(9, $r3$.ɵb(ctx.names[9])); - $r3$.ɵt(10, $r3$.ɵb(ctx.names[10])); - $r3$.ɵt(11, $r3$.ɵb(ctx.names[11])); - }, - inputs: {names: 'names'} - }); - } - - // NORMATIVE - const $e0_ff$ = - (v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, - v8: any) => ['start-', v0, v1, v2, v3, v4, '-middle-', v5, v6, v7, v8, '-end']; - // /NORMATIVE - - @Component({ - selector: 'my-app', - template: ` - - - ` - }) - class MyApp { - n0 = 'a'; - n1 = 'b'; - n2 = 'c'; - n3 = 'd'; - n4 = 'e'; - n5 = 'f'; - n6 = 'g'; - n7 = 'h'; - n8 = 'i'; - - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyApp, - tag: 'my-app', - factory: function MyApp_Factory() { return new MyApp(); }, - template: function MyApp_Template(c: MyApp, cm: boolean) { - if (cm) { - $r3$.ɵE(0, MyComp); - $r3$.ɵe(); - } - $r3$.ɵp( - 0, 'names', - $r3$.ɵb( - $r3$.ɵfV($e0_ff$, [c.n0, c.n1, c.n2, c.n3, c.n4, c.n5, c.n6, c.n7, c.n8]))); - MyComp.ngComponentDef.h(1, 0); - $r3$.ɵr(1, 0); - } - }); - // /NORMATIVE - } - - expect(renderComp(MyApp)).toEqual(`start-abcde-middle-fghi-end`); - }); - - it('should support object literals', () => { - type $ObjectComp$ = ObjectComp; - type $MyApp$ = MyApp; - - @Component({ - selector: 'object-comp', - template: ` -

{{ config['duration'] }}

-

{{ config.animation }}

- ` - }) - class ObjectComp { - config: {[key: string]: any}; - - static ngComponentDef = $r3$.ɵdefineComponent({ - type: ObjectComp, - tag: 'object-comp', - factory: function ObjectComp_Factory() { return new ObjectComp(); }, - template: function ObjectComp_Template(ctx: $ObjectComp$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, 'p'); - $r3$.ɵT(1); - $r3$.ɵe(); - $r3$.ɵE(2, 'p'); - $r3$.ɵT(3); - $r3$.ɵe(); - } - $r3$.ɵt(1, $r3$.ɵb(ctx.config['duration'])); - $r3$.ɵt(3, $r3$.ɵb(ctx.config.animation)); - }, - inputs: {config: 'config'} - }); - } - - // NORMATIVE - const $e0_ff$ = (v: any) => { return {'duration': 500, animation: v}; }; - // /NORMATIVE - - @Component({ - selector: 'my-app', - template: ` - - ` - }) - class MyApp { - name = 'slide'; - - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyApp, - tag: 'my-app', - factory: function MyApp_Factory() { return new MyApp(); }, - template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, ObjectComp); - $r3$.ɵe(); - } - $r3$.ɵp(0, 'config', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.name))); - ObjectComp.ngComponentDef.h(1, 0); - $r3$.ɵr(1, 0); - } - }); - // /NORMATIVE - } - - expect(renderComp(MyApp)).toEqual(`

500

slide

`); - }); - - it('should support expressions nested deeply in object/array literals', () => { - type $NestedComp$ = NestedComp; - type $MyApp$ = MyApp; - - @Component({ - selector: 'nested-comp', - template: ` -

{{ config.animation }}

-

{{config.actions[0].opacity }}

-

{{config.actions[1].duration }}

- ` - }) - class NestedComp { - config: {[key: string]: any}; - - static ngComponentDef = $r3$.ɵdefineComponent({ - type: NestedComp, - tag: 'nested-comp', - factory: function NestedComp_Factory() { return new NestedComp(); }, - template: function NestedComp_Template(ctx: $NestedComp$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, 'p'); - $r3$.ɵT(1); - $r3$.ɵe(); - $r3$.ɵE(2, 'p'); - $r3$.ɵT(3); - $r3$.ɵe(); - $r3$.ɵE(4, 'p'); - $r3$.ɵT(5); - $r3$.ɵe(); - } - $r3$.ɵt(1, $r3$.ɵb(ctx.config.animation)); - $r3$.ɵt(3, $r3$.ɵb(ctx.config.actions[0].opacity)); - $r3$.ɵt(5, $r3$.ɵb(ctx.config.actions[1].duration)); - }, - inputs: {config: 'config'} - }); - } - - // NORMATIVE - const $e0_ff$ = (v: any) => { return {opacity: 1, duration: v}; }; - const $c0$ = {opacity: 0, duration: 0}; - const $e0_ff_1$ = (v: any) => [$c0$, v]; - const $e0_ff_2$ = (v1: any, v2: any) => { return {animation: v1, actions: v2}; }; - // /NORMATIVE - - @Component({ - selector: 'my-app', - template: ` - - - ` - }) - class MyApp { - name = 'slide'; - duration = 100; - - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyApp, - tag: 'my-app', - factory: function MyApp_Factory() { return new MyApp(); }, - template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, NestedComp); - $r3$.ɵe(); - } - $r3$.ɵp( - 0, 'config', $r3$.ɵf2( - $e0_ff_2$, ctx.name, - $r3$.ɵb($r3$.ɵf1($e0_ff_1$, $r3$.ɵf1($e0_ff$, ctx.duration))))); - NestedComp.ngComponentDef.h(1, 0); - $r3$.ɵr(1, 0); - } - }); - // /NORMATIVE - } - - expect(renderComp(MyApp)) - .toEqual(`

slide

0

100

`); - }); - - }); - - it('should support content projection', () => { - type $SimpleComponent$ = SimpleComponent; - type $ComplexComponent$ = ComplexComponent; - type $MyApp$ = MyApp; - - @Component({selector: 'simple', template: `
`}) - class SimpleComponent { - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: SimpleComponent, - tag: 'simple', - factory: () => new SimpleComponent(), - template: function(ctx: $SimpleComponent$, cm: $boolean$) { - if (cm) { - $r3$.ɵpD(0); - $r3$.ɵE(1, 'div'); - $r3$.ɵP(2, 0); - $r3$.ɵe(); - } - } - }); - // /NORMATIVE - } - - // NORMATIVE - const $pD_0$: $r3$.ɵCssSelector[] = - [[[['span', 'title', 'toFirst'], null]], [[['span', 'title', 'toSecond'], null]]]; - // /NORMATIVE - - @Component({ - selector: 'complex', - template: ` -
-
` - }) - class ComplexComponent { - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: ComplexComponent, - tag: 'complex', - factory: () => new ComplexComponent(), - template: function(ctx: $ComplexComponent$, cm: $boolean$) { - if (cm) { - $r3$.ɵpD(0, $pD_0$); - $r3$.ɵE(1, 'div', ['id', 'first']); - $r3$.ɵP(2, 0, 1); - $r3$.ɵe(); - $r3$.ɵE(3, 'div', ['id', 'second']); - $r3$.ɵP(4, 0, 2); - $r3$.ɵe(); - } - } - }); - // /NORMATIVE - } - - @Component({ - selector: 'my-app', - template: `content - ` - }) - class MyApp { - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyApp, - tag: 'my-app', - factory: () => new MyApp(), - template: function(ctx: $MyApp$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, SimpleComponent); - $r3$.ɵT(2, 'content'); - $r3$.ɵe(); - } - } - }); - } - }); - - describe('queries', () => { - let someDir: SomeDirective; - - @Directive({ - selector: '[someDir]', - }) - class SomeDirective { - static ngDirectiveDef = $r3$.ɵdefineDirective({ - type: SomeDirective, - factory: function SomeDirective_Factory() { return someDir = new SomeDirective(); }, - features: [$r3$.ɵPublicFeature] - }); - } - - it('should support view queries', () => { - type $ViewQueryComponent$ = ViewQueryComponent; - - // NORMATIVE - const $e1_attrs$ = ['someDir', '']; - const $e1_dirs$ = [SomeDirective]; - // /NORMATIVE - - @Component({ - selector: 'view-query-component', - template: ` -
- ` - }) - class ViewQueryComponent { - @ViewChild(SomeDirective) someDir: SomeDirective; - @ViewChildren(SomeDirective) someDirList: QueryList; - - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: ViewQueryComponent, - tag: 'view-query-component', - factory: function ViewQueryComponent_Factory() { return new ViewQueryComponent(); }, - template: function ViewQueryComponent_Template( - ctx: $ViewQueryComponent$, cm: $boolean$) { - let $tmp$: any; - if (cm) { - $r3$.ɵQ(0, SomeDirective, false); - $r3$.ɵQ(1, SomeDirective, false); - $r3$.ɵE(2, 'div', $e1_attrs$, $e1_dirs$); - $r3$.ɵe(); - } - - $r3$.ɵqR($tmp$ = $r3$.ɵld>(0)) && (ctx.someDir = $tmp$.first); - $r3$.ɵqR($tmp$ = $r3$.ɵld>(1)) && - (ctx.someDirList = $tmp$ as QueryList); - SomeDirective.ngDirectiveDef.h(3, 2); - $r3$.ɵr(3, 2); - } - }); - // /NORMATIVE - } - - - const viewQueryComp = renderComponent(ViewQueryComponent); - expect(viewQueryComp.someDir).toEqual(someDir); - expect((viewQueryComp.someDirList as QueryList).toArray()).toEqual([ - someDir ! - ]); - }); - - it('should support content queries', () => { - type $MyApp$ = MyApp; - type $ContentQueryComponent$ = ContentQueryComponent; - - let contentQueryComp: ContentQueryComponent; - - @Component({ - selector: 'content-query-component', - template: ` -
- ` - }) - class ContentQueryComponent { - @ContentChild(SomeDirective) someDir: SomeDirective; - @ContentChildren(SomeDirective) someDirList: QueryList; - - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: ContentQueryComponent, - tag: 'content-query-component', - factory: function ContentQueryComponent_Factory() { - return [ - new ContentQueryComponent(), $r3$.ɵQ(null, SomeDirective, false), - $r3$.ɵQ(null, SomeDirective, false) - ]; - }, - hostBindings: function ContentQueryComponent_HostBindings( - dirIndex: $number$, elIndex: $number$) { - let $tmp$: any; - const $instance$ = $r3$.ɵld(dirIndex)[0]; - $r3$.ɵqR($tmp$ = $r3$.ɵld(dirIndex)[1]) && ($instance$.someDir = $tmp$.first); - $r3$.ɵqR($tmp$ = $r3$.ɵld(dirIndex)[2]) && ($instance$.someDirList = $tmp$); - }, - template: function ContentQueryComponent_Template( - ctx: $ContentQueryComponent$, cm: $boolean$) { - if (cm) { - $r3$.ɵpD(0); - $r3$.ɵE(1, 'div'); - $r3$.ɵP(2, 0); - $r3$.ɵe(); - } - } - }); - // /NORMATIVE - } - - const $e2_attrs$ = ['someDir', '']; - const $e2_dirs$ = [SomeDirective]; - - @Component({ - selector: 'my-app', - template: ` - -
-
- ` - }) - class MyApp { - // NON-NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyApp, - tag: 'my-app', - factory: function MyApp_Factory() { return new MyApp(); }, - template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, ContentQueryComponent); - contentQueryComp = $r3$.ɵld(1)[0]; - $r3$.ɵE(2, 'div', $e2_attrs$, $e2_dirs$); - $r3$.ɵe(); - $r3$.ɵe(); - } - ContentQueryComponent.ngComponentDef.h(1, 0); - SomeDirective.ngDirectiveDef.h(3, 2); - $r3$.ɵr(1, 0); - $r3$.ɵr(3, 2); - } - }); - // /NON-NORMATIVE - } - - - expect(renderComp(MyApp)) - .toEqual( - `
`); - expect(contentQueryComp !.someDir).toEqual(someDir !); - expect((contentQueryComp !.someDirList as QueryList).toArray()).toEqual([ - someDir ! - ]); - }); - - }); - - }); - - xdescribe('pipes', () => { - type $MyApp$ = MyApp; - - @Pipe({ - name: 'myPipe', - pure: false, - }) - class MyPipe implements PipeTransform, - OnDestroy { - transform(value: any, ...args: any[]) { throw new Error('Method not implemented.'); } - ngOnDestroy(): void { throw new Error('Method not implemented.'); } - - // NORMATIVE - static ngPipeDef = $r3$.ɵdefinePipe({ - type: MyPipe, - factory: function MyPipe_Factory() { return new MyPipe(); }, - pure: false, - }); - // /NORMATIVE - } - - @Pipe({ - name: 'myPurePipe', - }) - class MyPurePipe implements PipeTransform { - transform(value: any, ...args: any[]) { throw new Error('Method not implemented.'); } - - // NORMATIVE - static ngPipeDef = $r3$.ɵdefinePipe({ - type: MyPurePipe, - factory: function MyPurePipe_Factory() { return new MyPurePipe(); }, - }); - // /NORMATIVE - } - - // NORMATIVE - const $MyPurePipe_ngPipeDef$ = MyPurePipe.ngPipeDef; - const $MyPipe_ngPipeDef$ = MyPipe.ngPipeDef; - // /NORMATIVE - - @Component({template: `{{name | myPipe:size | myPurePipe:size }}`}) - class MyApp { - name = 'World'; - size = 0; - - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyApp, - tag: 'my-app', - factory: function MyApp_Factory() { return new MyApp(); }, - template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { - if (cm) { - $r3$.ɵT(0); - $r3$.ɵPp(1, $MyPurePipe_ngPipeDef$, $MyPurePipe_ngPipeDef$.n()); - $r3$.ɵPp(2, $MyPipe_ngPipeDef$, $MyPipe_ngPipeDef$.n()); - } - $r3$.ɵt(2, $r3$.ɵi1('', $r3$.ɵpb2(1, $r3$.ɵpb2(2, ctx.name, ctx.size), ctx.size), '')); - } - }); - // /NORMATIVE - } - - it('should render pipes', () => { - // TODO(misko): write a test once pipes runtime is implemented. - }); - }); - - describe('local references', () => { - // TODO(misko): currently disabled until local refs are working - xit('should translate DOM structure', () => { - type $MyComponent$ = MyComponent; - - @Component({selector: 'my-component', template: `Hello {{user.value}}!`}) - class MyComponent { - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyComponent, - tag: 'my-component', - factory: () => new MyComponent, - template: function(ctx: $MyComponent$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, 'input', null, null, ['user', '']); - $r3$.ɵe(); - $r3$.ɵT(2); - } - const l1_user = $r3$.ɵld(1); - $r3$.ɵt(2, $r3$.ɵi1('Hello ', l1_user.value, '!')); - } - }); - // NORMATIVE - } - - expect(renderComp(MyComponent)) - .toEqual('
Hello World!
'); - }); - }); - - describe('lifecycle hooks', () => { - let events: string[] = []; - let simpleLayout: SimpleLayout; - - type $LifecycleComp$ = LifecycleComp; - type $SimpleLayout$ = SimpleLayout; - - beforeEach(() => { events = []; }); - - @Component({selector: 'lifecycle-comp', template: ``}) - class LifecycleComp { - @Input('name') nameMin: string; - - ngOnChanges() { events.push('changes' + this.nameMin); } - - ngOnInit() { events.push('init' + this.nameMin); } - ngDoCheck() { events.push('check' + this.nameMin); } - - ngAfterContentInit() { events.push('content init' + this.nameMin); } - ngAfterContentChecked() { events.push('content check' + this.nameMin); } - - ngAfterViewInit() { events.push('view init' + this.nameMin); } - ngAfterViewChecked() { events.push('view check' + this.nameMin); } - - ngOnDestroy() { events.push(this.nameMin); } - - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: LifecycleComp, - tag: 'lifecycle-comp', - factory: function LifecycleComp_Factory() { return new LifecycleComp(); }, - template: function LifecycleComp_Template(ctx: $LifecycleComp$, cm: $boolean$) {}, - inputs: {nameMin: 'name'}, - inputsPropertyName: {nameMin: 'nameMin'}, - features: [$r3$.ɵNgOnChangesFeature] - }); - // /NORMATIVE - } - - @Component({ - selector: 'simple-layout', - template: ` - - - ` - }) - class SimpleLayout { - name1 = '1'; - name2 = '2'; - - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: SimpleLayout, - tag: 'simple-layout', - factory: function SimpleLayout_Factory() { return simpleLayout = new SimpleLayout(); }, - template: function SimpleLayout_Template(ctx: $SimpleLayout$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, LifecycleComp); - $r3$.ɵe(); - $r3$.ɵE(2, LifecycleComp); - $r3$.ɵe(); - } - $r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1)); - $r3$.ɵp(2, 'name', $r3$.ɵb(ctx.name2)); - LifecycleComp.ngComponentDef.h(1, 0); - LifecycleComp.ngComponentDef.h(3, 2); - $r3$.ɵr(1, 0); - $r3$.ɵr(3, 2); - } - }); - // /NORMATIVE - } - - it('should gen hooks with a few simple components', () => { - expect(renderComp(SimpleLayout)) - .toEqual(``); - expect(events).toEqual([ - 'changes1', 'init1', 'check1', 'changes2', 'init2', 'check2', 'content init1', - 'content check1', 'content init2', 'content check2', 'view init1', 'view check1', - 'view init2', 'view check2' - ]); - - events = []; - simpleLayout.name1 = '-one'; - simpleLayout.name2 = '-two'; - $r3$.ɵdetectChanges(simpleLayout); - expect(events).toEqual([ - 'changes-one', 'check-one', 'changes-two', 'check-two', 'content check-one', - 'content check-two', 'view check-one', 'view check-two' - ]); - }); - - }); - - it('should inject ChangeDetectorRef', () => { - type $MyComp$ = MyComp; - type $MyApp$ = MyApp; - - @Component({selector: 'my-comp', template: `{{ value }}`}) - class MyComp { - value: string; - constructor(public cdr: ChangeDetectorRef) { this.value = (cdr.constructor as any).name; } - - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyComp, - tag: 'my-comp', - factory: function MyComp_Factory() { return new MyComp($r3$.ɵinjectChangeDetectorRef()); }, - template: function MyComp_Template(ctx: $MyComp$, cm: $boolean$) { - if (cm) { - $r3$.ɵT(0); - } - $r3$.ɵt(0, $r3$.ɵb(ctx.value)); - } - }); - // /NORMATIVE - } - - class MyApp { - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyApp, - tag: 'my-app', - factory: function MyApp_Factory() { return new MyApp(); }, - /** */ - template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, MyComp); - $r3$.ɵe(); - } - MyComp.ngComponentDef.h(1, 0); - $r3$.ɵr(1, 0); - } - }); - } - - const app = renderComponent(MyApp); - // ChangeDetectorRef is the token, ViewRef is historically the constructor - expect(toHtml(app)).toEqual('ViewRef'); - }); - - describe('template variables', () => { - - interface ForOfContext { - $implicit: any; - index: number; - even: boolean; - odd: boolean; - } - - @Directive({selector: '[forOf]'}) - class ForOfDirective { - private previous: any[]; - - constructor(private view: ViewContainerRef, private template: TemplateRef) {} - - @Input() forOf: any[]; - - ngOnChanges(simpleChanges: SimpleChanges) { - if ('forOf' in simpleChanges) { - this.update(); - } - } - - ngDoCheck(): void { - const previous = this.previous; - const current = this.forOf; - if (!previous || previous.length != current.length || - previous.some((value: any, index: number) => current[index] !== previous[index])) { - this.update(); - } - } - - private update() { - // TODO(chuckj): Not implemented yet - // this.view.clear(); - if (this.forOf) { - const current = this.forOf; - for (let i = 0; i < current.length; i++) { - const context = {$implicit: current[i], index: i, even: i % 2 == 0, odd: i % 2 == 1}; - // TODO(chuckj): Not implemented yet - // this.view.createEmbeddedView(this.template, context); - } - this.previous = [...this.forOf]; - } - } - - // NORMATIVE - static ngDirectiveDef = $r3$.ɵdefineDirective({ - type: ForOfDirective, - factory: function ForOfDirective_Factory() { - return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef()); - }, - // TODO(chuckj): Enable when ngForOf enabling lands. - // features: [NgOnChangesFeature(NgForOf)], - inputs: {forOf: 'forOf'} - }); - // /NORMATIVE - } - - it('should support a let variable and reference', () => { - type $MyComponent$ = MyComponent; - - interface Item { - name: string; - } - - // NORMATIVE - const $c1_dirs$ = [ForOfDirective]; - // /NORMATIVE - - @Component({ - selector: 'my-component', - template: `
  • {{item.name}}
` - }) - class MyComponent { - items = [{name: 'one'}, {name: 'two'}]; - - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyComponent, - tag: 'my-component', - factory: function MyComponent_Factory() { return new MyComponent(); }, - template: function MyComponent_Template(ctx: $MyComponent$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, 'ul'); - $r3$.ɵC(1, $c1_dirs$, MyComponent_ForOfDirective_Template_1); - $r3$.ɵe(); - } - $r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items)); - $r3$.ɵcR(1); - $r3$.ɵr(2, 1); - $r3$.ɵcr(); - - function MyComponent_ForOfDirective_Template_1(ctx1: $any$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, 'li'); - $r3$.ɵT(1); - $r3$.ɵe(); - } - const $l0_item$ = ctx1.$implicit; - $r3$.ɵt(1, $r3$.ɵi1('', $l0_item$.name, '')); - } - } - }); - // /NORMATIVE - } - - // TODO(chuckj): update when the changes to enable ngForOf lands. - expect(renderComp(MyComponent)).toEqual('
    '); - }); - - it('should support accessing parent template variables', () => { - type $MyComponent$ = MyComponent; - - interface Info { - description: string; - } - interface Item { - name: string; - infos: Info[]; - } - - // NORMATIVE - const $c1_dirs$ = [ForOfDirective]; - // /NORMATIVE - - @Component({ - selector: 'my-component', - template: ` -
      -
    • -
      {{item.name}}
      -
        -
      • - {{item.name}}: {{info.description}} -
      • -
      -
    • -
    ` - }) - class MyComponent { - items: Item[] = [ - {name: 'one', infos: [{description: '11'}, {description: '12'}]}, - {name: 'two', infos: [{description: '21'}, {description: '22'}]} - ]; - - // NORMATIVE - static ngComponentDef = $r3$.ɵdefineComponent({ - type: MyComponent, - tag: 'my-component', - factory: function MyComponent_Factory() { return new MyComponent(); }, - template: function MyComponent_Template(ctx: $MyComponent$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, 'ul'); - $r3$.ɵC(1, $c1_dirs$, MyComponent_ForOfDirective_Template_1); - $r3$.ɵe(); - } - $r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items)); - $r3$.ɵcR(1); - $r3$.ɵr(2, 1); - $r3$.ɵcr(); - - function MyComponent_ForOfDirective_Template_1(ctx1: $any$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, 'li'); - $r3$.ɵE(1, 'div'); - $r3$.ɵT(2); - $r3$.ɵe(); - $r3$.ɵE(3, 'ul'); - $r3$.ɵC(4, $c1_dirs$, MyComponent_ForOfDirective_ForOfDirective_Template_3); - $r3$.ɵe(); - $r3$.ɵe(); - } - const $l0_item$ = ctx1.$implicit; - $r3$.ɵp(4, 'forOf', $r3$.ɵb($l0_item$.infos)); - $r3$.ɵt(2, $r3$.ɵi1('', $l0_item$.name, '')); - $r3$.ɵcR(4); - $r3$.ɵr(5, 4); - $r3$.ɵcr(); - - function MyComponent_ForOfDirective_ForOfDirective_Template_3( - ctx2: $any$, cm: $boolean$) { - if (cm) { - $r3$.ɵE(0, 'li'); - $r3$.ɵT(1); - $r3$.ɵe(); - } - const $l0_info$ = ctx2.$implicit; - $r3$.ɵt(1, $r3$.ɵi2(' ', $l0_item$.name, ': ', $l0_info$.description, ' ')); - } - } - } - }); - // /NORMATIVE - } - }); - }); -}); - -xdescribe('NgModule', () => { - - interface Injectable { - scope?: /*InjectorDefType*/ any; - factory: Function; - } - - function defineInjectable(opts: Injectable): Injectable { - // This class should be imported from https://github.com/angular/angular/pull/20850 - return opts; - } - function defineInjector(opts: any): any { - // This class should be imported from https://github.com/angular/angular/pull/20850 - return opts; - } - it('should convert module', () => { - @Injectable() - class Toast { - constructor(name: String) {} - // NORMATIVE - static ngInjectableDef = defineInjectable({ - factory: () => new Toast($r3$.ɵinject(String)), - }); - // /NORMATIVE - } - - class CommonModule { - // NORMATIVE - static ngInjectorDef = defineInjector({}); - // /NORMATIVE - } - - @NgModule({ - providers: [Toast, {provide: String, useValue: 'Hello'}], - imports: [CommonModule], - }) - class MyModule { - constructor(toast: Toast) {} - // NORMATIVE - static ngInjectorDef = defineInjector({ - factory: () => new MyModule($r3$.ɵinject(Toast)), - provider: [ - {provide: Toast, deps: [String]}, // If Toast has metadata generate this line - Toast, // If Toast has no metadata generate this line. - {provide: String, useValue: 'Hello'} - ], - imports: [CommonModule] - }); - // /NORMATIVE - } - - @Injectable(/*{MyModule}*/) - class BurntToast { - constructor(@Optional() toast: Toast|null, name: String) {} - // NORMATIVE - static ngInjectableDef = defineInjectable({ - scope: MyModule, - factory: () => new BurntToast( - $r3$.ɵinject(Toast, $r3$.ɵInjectFlags.Optional), $r3$.ɵinject(String)), - }); - // /NORMATIVE - } - - }); -}); - -function renderComp(type: $r3$.ɵComponentType): string { - return toHtml(renderComponent(type)); -} diff --git a/packages/core/test/render3/compiler_canonical/component_directives_spec.ts b/packages/core/test/render3/compiler_canonical/component_directives_spec.ts new file mode 100644 index 0000000000..92678172e4 --- /dev/null +++ b/packages/core/test/render3/compiler_canonical/component_directives_spec.ts @@ -0,0 +1,844 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; +import * as $r3$ from '../../../src/core_render3_private_export'; +import {renderComponent, toHtml} from '../render_util'; + +/// See: `normative.md` +describe('components & directives', () => { + type $boolean$ = boolean; + type $any$ = any; + type $number$ = number; + + + it('should instantiate directives', () => { + type $ChildComponent$ = ChildComponent; + type $MyComponent$ = MyComponent; + + const log: string[] = []; + @Component({selector: 'child', template: 'child-view'}) + class ChildComponent { + constructor() { log.push('ChildComponent'); } + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: ChildComponent, + tag: `child`, + factory: () => new ChildComponent(), + template: function(ctx: $ChildComponent$, cm: $boolean$) { + if (cm) { + $r3$.ɵT(0, 'child-view'); + } + } + }); + // /NORMATIVE + } + + @Directive({ + selector: '[some-directive]', + }) + class SomeDirective { + constructor() { log.push('SomeDirective'); } + // NORMATIVE + static ngDirectiveDef = $r3$.ɵdefineDirective({ + type: SomeDirective, + factory: () => new SomeDirective(), + }); + // /NORMATIVE + } + + // Important: keep arrays outside of function to not create new instances. + // NORMATIVE + const $e0_attrs$ = ['some-directive', '']; + const $e0_dirs$ = [SomeDirective]; + // /NORMATIVE + + @Component({selector: 'my-component', template: `!`}) + class MyComponent { + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComponent, + tag: 'my-component', + factory: () => new MyComponent(), + template: function(ctx: $MyComponent$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, ChildComponent, $e0_attrs$, $e0_dirs$); + $r3$.ɵe(); + $r3$.ɵT(3, '!'); + } + ChildComponent.ngComponentDef.h(1, 0); + SomeDirective.ngDirectiveDef.h(2, 0); + $r3$.ɵr(1, 0); + $r3$.ɵr(2, 0); + } + }); + // /NORMATIVE + } + + expect(renderComp(MyComponent)).toEqual('child-view!'); + expect(log).toEqual(['ChildComponent', 'SomeDirective']); + }); + + it('should support host bindings', () => { + type $MyApp$ = MyApp; + + @Directive({selector: '[hostBindingDir]'}) + class HostBindingDir { + @HostBinding('id') dirId = 'some id'; + + // NORMATIVE + static ngDirectiveDef = $r3$.ɵdefineDirective({ + type: HostBindingDir, + factory: function HostBindingDir_Factory() { return new HostBindingDir(); }, + hostBindings: function HostBindingDir_HostBindings(dirIndex: $number$, elIndex: $number$) { + $r3$.ɵp(elIndex, 'id', $r3$.ɵb($r3$.ɵld(dirIndex).dirId)); + } + }); + // /NORMATIVE + } + + const $e0_attrs$ = ['hostBindingDir', '']; + const $e0_dirs$ = [HostBindingDir]; + + @Component({ + selector: 'my-app', + template: ` +
    + ` + }) + class MyApp { + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'div', $e0_attrs$, $e0_dirs$); + $r3$.ɵe(); + } + HostBindingDir.ngDirectiveDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + } + + expect(renderComp(MyApp)).toEqual(`
    `); + }); + + it('should support host listeners', () => { + type $MyApp$ = MyApp; + + @Directive({selector: '[hostlistenerDir]'}) + class HostListenerDir { + @HostListener('click') + onClick() {} + + // NORMATIVE + static ngDirectiveDef = $r3$.ɵdefineDirective({ + type: HostListenerDir, + factory: function HostListenerDir_Factory() { + const $dir$ = new HostListenerDir(); + $r3$.ɵL('click', function HostListenerDir_click_Handler(event: any) { $dir$.onClick(); }); + return $dir$; + }, + }); + // /NORMATIVE + } + + const $e0_attrs$ = ['hostListenerDir', '']; + const $e0_dirs$ = [HostListenerDir]; + + @Component({ + selector: 'my-app', + template: ` + + ` + }) + class MyApp { + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'button', $e0_attrs$, $e0_dirs$); + $r3$.ɵT(2, 'Click'); + $r3$.ɵe(); + } + HostListenerDir.ngDirectiveDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + } + + expect(renderComp(MyApp)).toEqual(``); + }); + + + it('should support setting of host attributes', () => { + type $MyApp$ = MyApp; + + @Directive({selector: '[hostAttributeDir]', host: {'role': 'listbox'}}) + class HostAttributeDir { + // NORMATIVE + static ngDirectiveDef = $r3$.ɵdefineDirective({ + type: HostAttributeDir, + factory: function HostAttributeDir_Factory() { return new HostAttributeDir(); }, + attributes: ['role', 'listbox'] + }); + // /NORMATIVE + } + + const $e0_attrs$ = ['hostAttributeDir', '']; + const $e0_dirs$ = [HostAttributeDir]; + + @Component({ + selector: 'my-app', + template: ` +
    + ` + }) + class MyApp { + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'div', $e0_attrs$, $e0_dirs$); + $r3$.ɵe(); + } + HostAttributeDir.ngDirectiveDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + } + + expect(renderComp(MyApp)).toEqual(`
    `); + }); + + it('should support bindings of host attributes', () => { + type $MyApp$ = MyApp; + + @Directive({selector: '[hostBindingDir]'}) + class HostBindingDir { + @HostBinding('attr.aria-label') label = 'some label'; + + // NORMATIVE + static ngDirectiveDef = $r3$.ɵdefineDirective({ + type: HostBindingDir, + factory: function HostBindingDir_Factory() { return new HostBindingDir(); }, + hostBindings: function HostBindingDir_HostBindings(dirIndex: $number$, elIndex: $number$) { + $r3$.ɵa(elIndex, 'aria-label', $r3$.ɵb($r3$.ɵld(dirIndex).label)); + } + }); + // /NORMATIVE + } + + const $e0_attrs$ = ['hostBindingDir', '']; + const $e0_dirs$ = [HostBindingDir]; + + @Component({ + selector: 'my-app', + template: ` +
    + ` + }) + class MyApp { + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'div', $e0_attrs$, $e0_dirs$); + $r3$.ɵe(); + } + HostBindingDir.ngDirectiveDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + } + + expect(renderComp(MyApp)).toEqual(`
    `); + }); + + it('should support onPush components', () => { + type $MyApp$ = MyApp; + type $MyComp$ = MyComp; + + @Component({ + selector: 'my-comp', + template: ` + {{ name }} + `, + changeDetection: ChangeDetectionStrategy.OnPush + }) + class MyComp { + @Input() name: string; + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComp, + tag: 'my-comp', + factory: function MyComp_Factory() { return new MyComp(); }, + template: function MyComp_Template(ctx: $MyComp$, cm: $boolean$) { + if (cm) { + $r3$.ɵT(0); + } + $r3$.ɵt(0, $r3$.ɵb(ctx.name)); + }, + inputs: {name: 'name'}, + changeDetection: ChangeDetectionStrategy.OnPush + }); + // /NORMATIVE + } + + @Component({ + selector: 'my-app', + template: ` + + ` + }) + class MyApp { + name = 'some name'; + + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, MyComp); + $r3$.ɵe(); + } + $r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name)); + MyComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + } + + expect(renderComp(MyApp)).toEqual(`some name`); + }); + + xit('should support structural directives', () => { + type $MyComponent$ = MyComponent; + + const log: string[] = []; + @Directive({ + selector: '[if]', + }) + class IfDirective { + constructor(template: TemplateRef) { log.push('ifDirective'); } + // NORMATIVE + static ngDirectiveDef = $r3$.ɵdefineDirective({ + type: IfDirective, + factory: () => new IfDirective($r3$.ɵinjectTemplateRef()), + }); + // /NORMATIVE + } + + // Important: keep arrays outside of function to not create new instances. + // NORMATIVE + const $e0_locals$ = ['foo', '']; + const $c1_dirs$ = [IfDirective]; + // /NORMATIVE + + @Component( + {selector: 'my-component', template: `
    • {{salutation}} {{foo}}
    `}) + class MyComponent { + salutation = 'Hello'; + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComponent, + tag: 'my-component', + factory: () => new MyComponent(), + template: function(ctx: $MyComponent$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'ul', null, null, $e0_locals$); + $r3$.ɵC(2, $c1_dirs$, C1); + $r3$.ɵe(); + } + let $foo$ = $r3$.ɵld(1); + $r3$.ɵcR(2); + $r3$.ɵr(3, 2); + $r3$.ɵcr(); + + function C1(ctx1: $any$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'li'); + $r3$.ɵT(1); + $r3$.ɵe(); + } + $r3$.ɵt(1, $r3$.ɵi2('', ctx.salutation, ' ', $foo$, '')); + } + } + }); + // /NORMATIVE + } + + expect(renderComp(MyComponent)).toEqual('child-view!'); + expect(log).toEqual(['ChildComponent', 'SomeDirective']); + }); + + describe('value composition', () => { + type $MyArrayComp$ = MyArrayComp; + + @Component({ + selector: 'my-array-comp', + template: ` + {{ names[0] }} {{ names[1] }} + ` + }) + class MyArrayComp { + @Input() names: string[]; + + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyArrayComp, + tag: 'my-array-comp', + factory: function MyArrayComp_Factory() { return new MyArrayComp(); }, + template: function MyArrayComp_Template(ctx: $MyArrayComp$, cm: $boolean$) { + if (cm) { + $r3$.ɵT(0); + } + $r3$.ɵt(0, $r3$.ɵi2('', ctx.names[0], ' ', ctx.names[1], '')); + }, + inputs: {names: 'names'} + }); + } + + it('should support array literals of constants', () => { + type $MyApp$ = MyApp; + + // NORMATIVE + const $e0_arr$ = ['Nancy', 'Bess']; + // /NORMATIVE + + @Component({ + selector: 'my-app', + template: ` + + ` + }) + class MyApp { + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, MyArrayComp); + $r3$.ɵe(); + } + $r3$.ɵp(0, 'names', cm ? $e0_arr$ : $r3$.ɵNC); + MyArrayComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + // /NORMATIVE + } + + expect(renderComp(MyApp)).toEqual(`Nancy Bess`); + }); + + it('should support array literals of constants inside function calls', () => { + type $MyApp$ = MyApp; + + // NORMATIVE + const $e0_ff$ = () => ['Nancy', 'Bess']; + // /NORMATIVE + + @Component({ + selector: 'my-app', + template: ` + + ` + }) + class MyApp { + someFn(arr: string[]): string[] { + arr[0] = arr[0].toUpperCase(); + return arr; + } + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, MyArrayComp); + $r3$.ɵe(); + } + $r3$.ɵp(0, 'names', $r3$.ɵb(ctx.someFn($r3$.ɵf0($e0_ff$)))); + MyArrayComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + // /NORMATIVE + } + + expect(renderComp(MyApp)).toEqual(`NANCY Bess`); + }); + + it('should support array literals of constants inside expressions', () => { + type $MyApp$ = MyApp; + type $MyComp$ = MyComp; + + @Component({selector: 'my-comp', template: `{{ num }}`}) + class MyComp { + num: number; + + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComp, + tag: 'my-comp', + factory: function MyComp_Factory() { return new MyComp(); }, + template: function MyComp_Template(ctx: $MyComp$, cm: $boolean$) { + if (cm) { + $r3$.ɵT(0); + } + $r3$.ɵt(0, $r3$.ɵb(ctx.num)); + }, + inputs: {num: 'num'} + }); + } + + // NORMATIVE + const $e0_ff$ = () => ['Nancy', 'Bess']; + // /NORMATIVE + + @Component({ + selector: 'my-app', + template: ` + + ` + }) + class MyApp { + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, MyComp); + $r3$.ɵe(); + } + $r3$.ɵp(0, 'num', $r3$.ɵb($r3$.ɵf0($e0_ff$).length + 1)); + MyComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + // /NORMATIVE + } + + expect(renderComp(MyApp)).toEqual(`3`); + }); + + + it('should support array literals', () => { + type $MyApp$ = MyApp; + + // NORMATIVE + const $e0_ff$ = (v: any) => ['Nancy', v]; + // /NORMATIVE + + @Component({ + selector: 'my-app', + template: ` + + ` + }) + class MyApp { + customName = 'Bess'; + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, MyArrayComp); + $r3$.ɵe(); + } + $r3$.ɵp(0, 'names', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.customName))); + MyArrayComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + // /NORMATIVE + } + + expect(renderComp(MyApp)).toEqual(`Nancy Bess`); + }); + + it('should support 9+ bindings in array literals', () => { + type $MyComp$ = MyComp; + + @Component({ + selector: 'my-comp', + template: ` + {{ names[0] }} + {{ names[1] }} + {{ names[3] }} + {{ names[4] }} + {{ names[5] }} + {{ names[6] }} + {{ names[7] }} + {{ names[8] }} + {{ names[9] }} + {{ names[10] }} + {{ names[11] }} + ` + }) + class MyComp { + @Input() names: string[]; + + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComp, + tag: 'my-comp', + factory: function MyComp_Factory() { return new MyComp(); }, + template: function MyComp_Template(ctx: $MyComp$, cm: $boolean$) { + if (cm) { + $r3$.ɵT(0); + $r3$.ɵT(1); + $r3$.ɵT(2); + $r3$.ɵT(3); + $r3$.ɵT(4); + $r3$.ɵT(5); + $r3$.ɵT(6); + $r3$.ɵT(7); + $r3$.ɵT(8); + $r3$.ɵT(9); + $r3$.ɵT(10); + $r3$.ɵT(11); + } + $r3$.ɵt(0, $r3$.ɵb(ctx.names[0])); + $r3$.ɵt(1, $r3$.ɵb(ctx.names[1])); + $r3$.ɵt(2, $r3$.ɵb(ctx.names[2])); + $r3$.ɵt(3, $r3$.ɵb(ctx.names[3])); + $r3$.ɵt(4, $r3$.ɵb(ctx.names[4])); + $r3$.ɵt(5, $r3$.ɵb(ctx.names[5])); + $r3$.ɵt(6, $r3$.ɵb(ctx.names[6])); + $r3$.ɵt(7, $r3$.ɵb(ctx.names[7])); + $r3$.ɵt(8, $r3$.ɵb(ctx.names[8])); + $r3$.ɵt(9, $r3$.ɵb(ctx.names[9])); + $r3$.ɵt(10, $r3$.ɵb(ctx.names[10])); + $r3$.ɵt(11, $r3$.ɵb(ctx.names[11])); + }, + inputs: {names: 'names'} + }); + } + + // NORMATIVE + const $e0_ff$ = + (v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, + v8: any) => ['start-', v0, v1, v2, v3, v4, '-middle-', v5, v6, v7, v8, '-end']; + // /NORMATIVE + + @Component({ + selector: 'my-app', + template: ` + + + ` + }) + class MyApp { + n0 = 'a'; + n1 = 'b'; + n2 = 'c'; + n3 = 'd'; + n4 = 'e'; + n5 = 'f'; + n6 = 'g'; + n7 = 'h'; + n8 = 'i'; + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(c: MyApp, cm: boolean) { + if (cm) { + $r3$.ɵE(0, MyComp); + $r3$.ɵe(); + } + $r3$.ɵp( + 0, 'names', + $r3$.ɵb($r3$.ɵfV($e0_ff$, [c.n0, c.n1, c.n2, c.n3, c.n4, c.n5, c.n6, c.n7, c.n8]))); + MyComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + // /NORMATIVE + } + + expect(renderComp(MyApp)).toEqual(`start-abcde-middle-fghi-end`); + }); + + it('should support object literals', () => { + type $ObjectComp$ = ObjectComp; + type $MyApp$ = MyApp; + + @Component({ + selector: 'object-comp', + template: ` +

    {{ config['duration'] }}

    +

    {{ config.animation }}

    + ` + }) + class ObjectComp { + config: {[key: string]: any}; + + static ngComponentDef = $r3$.ɵdefineComponent({ + type: ObjectComp, + tag: 'object-comp', + factory: function ObjectComp_Factory() { return new ObjectComp(); }, + template: function ObjectComp_Template(ctx: $ObjectComp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'p'); + $r3$.ɵT(1); + $r3$.ɵe(); + $r3$.ɵE(2, 'p'); + $r3$.ɵT(3); + $r3$.ɵe(); + } + $r3$.ɵt(1, $r3$.ɵb(ctx.config['duration'])); + $r3$.ɵt(3, $r3$.ɵb(ctx.config.animation)); + }, + inputs: {config: 'config'} + }); + } + + // NORMATIVE + const $e0_ff$ = (v: any) => { return {'duration': 500, animation: v}; }; + // /NORMATIVE + + @Component({ + selector: 'my-app', + template: ` + + ` + }) + class MyApp { + name = 'slide'; + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, ObjectComp); + $r3$.ɵe(); + } + $r3$.ɵp(0, 'config', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.name))); + ObjectComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + // /NORMATIVE + } + + expect(renderComp(MyApp)).toEqual(`

    500

    slide

    `); + }); + + it('should support expressions nested deeply in object/array literals', () => { + type $NestedComp$ = NestedComp; + type $MyApp$ = MyApp; + + @Component({ + selector: 'nested-comp', + template: ` +

    {{ config.animation }}

    +

    {{config.actions[0].opacity }}

    +

    {{config.actions[1].duration }}

    + ` + }) + class NestedComp { + config: {[key: string]: any}; + + static ngComponentDef = $r3$.ɵdefineComponent({ + type: NestedComp, + tag: 'nested-comp', + factory: function NestedComp_Factory() { return new NestedComp(); }, + template: function NestedComp_Template(ctx: $NestedComp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'p'); + $r3$.ɵT(1); + $r3$.ɵe(); + $r3$.ɵE(2, 'p'); + $r3$.ɵT(3); + $r3$.ɵe(); + $r3$.ɵE(4, 'p'); + $r3$.ɵT(5); + $r3$.ɵe(); + } + $r3$.ɵt(1, $r3$.ɵb(ctx.config.animation)); + $r3$.ɵt(3, $r3$.ɵb(ctx.config.actions[0].opacity)); + $r3$.ɵt(5, $r3$.ɵb(ctx.config.actions[1].duration)); + }, + inputs: {config: 'config'} + }); + } + + // NORMATIVE + const $e0_ff$ = (v: any) => { return {opacity: 1, duration: v}; }; + const $c0$ = {opacity: 0, duration: 0}; + const $e0_ff_1$ = (v: any) => [$c0$, v]; + const $e0_ff_2$ = (v1: any, v2: any) => { return {animation: v1, actions: v2}; }; + // /NORMATIVE + + @Component({ + selector: 'my-app', + template: ` + + + ` + }) + class MyApp { + name = 'slide'; + duration = 100; + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, NestedComp); + $r3$.ɵe(); + } + $r3$.ɵp( + 0, 'config', $r3$.ɵf2( + $e0_ff_2$, ctx.name, + $r3$.ɵb($r3$.ɵf1($e0_ff_1$, $r3$.ɵf1($e0_ff$, ctx.duration))))); + NestedComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + // /NORMATIVE + } + + expect(renderComp(MyApp)) + .toEqual(`

    slide

    0

    100

    `); + }); + + }); + +}); + +function renderComp(type: $r3$.ɵComponentType): string { + return toHtml(renderComponent(type)); +} diff --git a/packages/core/test/render3/compiler_canonical/content_projection_spec.ts b/packages/core/test/render3/compiler_canonical/content_projection_spec.ts new file mode 100644 index 0000000000..ed64e0bf20 --- /dev/null +++ b/packages/core/test/render3/compiler_canonical/content_projection_spec.ts @@ -0,0 +1,94 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; +import * as $r3$ from '../../../src/core_render3_private_export'; +import {renderComponent, toHtml} from '../render_util'; + +/// See: `normative.md` +describe('content projection', () => { + type $boolean$ = boolean; + + it('should support content projection', () => { + type $SimpleComponent$ = SimpleComponent; + type $ComplexComponent$ = ComplexComponent; + type $MyApp$ = MyApp; + + @Component({selector: 'simple', template: `
    `}) + class SimpleComponent { + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: SimpleComponent, + tag: 'simple', + factory: () => new SimpleComponent(), + template: function(ctx: $SimpleComponent$, cm: $boolean$) { + if (cm) { + $r3$.ɵpD(0); + $r3$.ɵE(1, 'div'); + $r3$.ɵP(2, 0); + $r3$.ɵe(); + } + } + }); + // /NORMATIVE + } + + // NORMATIVE + const $pD_0$: $r3$.ɵCssSelector[] = + [[[['span', 'title', 'toFirst'], null]], [[['span', 'title', 'toSecond'], null]]]; + // /NORMATIVE + + @Component({ + selector: 'complex', + template: ` +
    +
    ` + }) + class ComplexComponent { + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: ComplexComponent, + tag: 'complex', + factory: () => new ComplexComponent(), + template: function(ctx: $ComplexComponent$, cm: $boolean$) { + if (cm) { + $r3$.ɵpD(0, $pD_0$); + $r3$.ɵE(1, 'div', ['id', 'first']); + $r3$.ɵP(2, 0, 1); + $r3$.ɵe(); + $r3$.ɵE(3, 'div', ['id', 'second']); + $r3$.ɵP(4, 0, 2); + $r3$.ɵe(); + } + } + }); + // /NORMATIVE + } + + @Component({ + selector: 'my-app', + template: `content + ` + }) + class MyApp { + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: () => new MyApp(), + template: function(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, SimpleComponent); + $r3$.ɵT(2, 'content'); + $r3$.ɵe(); + } + } + }); + } + }); + +}); \ No newline at end of file diff --git a/packages/core/test/render3/compiler_canonical/elements_spec.ts b/packages/core/test/render3/compiler_canonical/elements_spec.ts new file mode 100644 index 0000000000..e626248543 --- /dev/null +++ b/packages/core/test/render3/compiler_canonical/elements_spec.ts @@ -0,0 +1,54 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; +import * as $r3$ from '../../../src/core_render3_private_export'; +import {renderComponent, toHtml} from '../render_util'; + +/// See: `normative.md` +describe('elements', () => { + // Saving type as $boolean$, etc to simplify testing for compiler, as types aren't saved + type $boolean$ = boolean; + type $any$ = any; + type $number$ = number; + + it('should translate DOM structure', () => { + type $MyComponent$ = MyComponent; + + // Important: keep arrays outside of function to not create new instances. + const $e0_attrs$ = ['class', 'my-app', 'title', 'Hello']; + + @Component({ + selector: 'my-component', + template: `
    Hello World!
    ` + }) + class MyComponent { + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComponent, + tag: 'my-component', + factory: () => new MyComponent(), + template: function(ctx: $MyComponent$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'div', $e0_attrs$); + $r3$.ɵT(1, 'Hello '); + $r3$.ɵE(2, 'b'); + $r3$.ɵT(3, 'World'); + $r3$.ɵe(); + $r3$.ɵT(4, '!'); + $r3$.ɵe(); + } + } + }); + // /NORMATIVE + } + + expect(toHtml(renderComponent(MyComponent))) + .toEqual('
    Hello World!
    '); + }); +}); diff --git a/packages/core/test/render3/compiler_canonical/injection_spec.ts b/packages/core/test/render3/compiler_canonical/injection_spec.ts new file mode 100644 index 0000000000..463a4a7b60 --- /dev/null +++ b/packages/core/test/render3/compiler_canonical/injection_spec.ts @@ -0,0 +1,63 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; +import * as $r3$ from '../../../src/core_render3_private_export'; +import {renderComponent, toHtml} from '../render_util'; + +/// See: `normative.md` +describe('injection', () => { + type $boolean$ = boolean; + + it('should inject ChangeDetectorRef', () => { + type $MyComp$ = MyComp; + type $MyApp$ = MyApp; + + @Component({selector: 'my-comp', template: `{{ value }}`}) + class MyComp { + value: string; + constructor(public cdr: ChangeDetectorRef) { this.value = (cdr.constructor as any).name; } + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComp, + tag: 'my-comp', + factory: function MyComp_Factory() { return new MyComp($r3$.ɵinjectChangeDetectorRef()); }, + template: function MyComp_Template(ctx: $MyComp$, cm: $boolean$) { + if (cm) { + $r3$.ɵT(0); + } + $r3$.ɵt(0, $r3$.ɵb(ctx.value)); + } + }); + // /NORMATIVE + } + + class MyApp { + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + /** */ + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, MyComp); + $r3$.ɵe(); + } + MyComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + } + + const app = renderComponent(MyApp); + // ChangeDetectorRef is the token, ViewRef is historically the constructor + expect(toHtml(app)).toEqual('ViewRef'); + }); + +}); \ No newline at end of file diff --git a/packages/core/test/render3/compiler_canonical/life_cycle_spec.ts b/packages/core/test/render3/compiler_canonical/life_cycle_spec.ts new file mode 100644 index 0000000000..1296e9e93f --- /dev/null +++ b/packages/core/test/render3/compiler_canonical/life_cycle_spec.ts @@ -0,0 +1,107 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; +import * as $r3$ from '../../../src/core_render3_private_export'; +import {renderComponent, toHtml} from '../render_util'; + +/// See: `normative.md` +describe('lifecycle hooks', () => { + let events: string[] = []; + let simpleLayout: SimpleLayout; + + type $boolean$ = boolean; + type $LifecycleComp$ = LifecycleComp; + type $SimpleLayout$ = SimpleLayout; + + beforeEach(() => { events = []; }); + + @Component({selector: 'lifecycle-comp', template: ``}) + class LifecycleComp { + @Input('name') nameMin: string; + + ngOnChanges() { events.push('changes' + this.nameMin); } + + ngOnInit() { events.push('init' + this.nameMin); } + ngDoCheck() { events.push('check' + this.nameMin); } + + ngAfterContentInit() { events.push('content init' + this.nameMin); } + ngAfterContentChecked() { events.push('content check' + this.nameMin); } + + ngAfterViewInit() { events.push('view init' + this.nameMin); } + ngAfterViewChecked() { events.push('view check' + this.nameMin); } + + ngOnDestroy() { events.push(this.nameMin); } + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: LifecycleComp, + tag: 'lifecycle-comp', + factory: function LifecycleComp_Factory() { return new LifecycleComp(); }, + template: function LifecycleComp_Template(ctx: $LifecycleComp$, cm: $boolean$) {}, + inputs: {nameMin: 'name'}, + inputsPropertyName: {nameMin: 'nameMin'}, + features: [$r3$.ɵNgOnChangesFeature] + }); + // /NORMATIVE + } + + @Component({ + selector: 'simple-layout', + template: ` + + + ` + }) + class SimpleLayout { + name1 = '1'; + name2 = '2'; + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: SimpleLayout, + tag: 'simple-layout', + factory: function SimpleLayout_Factory() { return simpleLayout = new SimpleLayout(); }, + template: function SimpleLayout_Template(ctx: $SimpleLayout$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, LifecycleComp); + $r3$.ɵe(); + $r3$.ɵE(2, LifecycleComp); + $r3$.ɵe(); + } + $r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1)); + $r3$.ɵp(2, 'name', $r3$.ɵb(ctx.name2)); + LifecycleComp.ngComponentDef.h(1, 0); + LifecycleComp.ngComponentDef.h(3, 2); + $r3$.ɵr(1, 0); + $r3$.ɵr(3, 2); + } + }); + // /NORMATIVE + } + + it('should gen hooks with a few simple components', () => { + expect(toHtml(renderComponent(SimpleLayout))) + .toEqual(``); + expect(events).toEqual([ + 'changes1', 'init1', 'check1', 'changes2', 'init2', 'check2', 'content init1', + 'content check1', 'content init2', 'content check2', 'view init1', 'view check1', + 'view init2', 'view check2' + ]); + + events = []; + simpleLayout.name1 = '-one'; + simpleLayout.name2 = '-two'; + $r3$.ɵdetectChanges(simpleLayout); + expect(events).toEqual([ + 'changes-one', 'check-one', 'changes-two', 'check-two', 'content check-one', + 'content check-two', 'view check-one', 'view check-two' + ]); + }); + +}); diff --git a/packages/core/test/render3/compiler_canonical/local_reference_spec.ts b/packages/core/test/render3/compiler_canonical/local_reference_spec.ts new file mode 100644 index 0000000000..5e5de98243 --- /dev/null +++ b/packages/core/test/render3/compiler_canonical/local_reference_spec.ts @@ -0,0 +1,44 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; +import * as $r3$ from '../../../src/core_render3_private_export'; +import {renderComponent, toHtml} from '../render_util'; + +/// See: `normative.md` +describe('local references', () => { + type $boolean$ = boolean; + + // TODO(misko): currently disabled until local refs are working + xit('should translate DOM structure', () => { + type $MyComponent$ = MyComponent; + + @Component({selector: 'my-component', template: `Hello {{user.value}}!`}) + class MyComponent { + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComponent, + tag: 'my-component', + factory: () => new MyComponent, + template: function(ctx: $MyComponent$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'input', null, null, ['user', '']); + $r3$.ɵe(); + $r3$.ɵT(2); + } + const l1_user = $r3$.ɵld(1); + $r3$.ɵt(2, $r3$.ɵi1('Hello ', l1_user.value, '!')); + } + }); + // NORMATIVE + } + + expect(toHtml(renderComponent(MyComponent))) + .toEqual('
    Hello World!
    '); + }); +}); diff --git a/packages/core/test/render3/compiler_canonical/ng_module_spec.ts b/packages/core/test/render3/compiler_canonical/ng_module_spec.ts new file mode 100644 index 0000000000..554b5cad39 --- /dev/null +++ b/packages/core/test/render3/compiler_canonical/ng_module_spec.ts @@ -0,0 +1,78 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; +import * as $r3$ from '../../../src/core_render3_private_export'; +import {renderComponent, toHtml} from '../render_util'; + +/// See: `normative.md` +xdescribe('NgModule', () => { + + interface Injectable { + scope?: /*InjectorDefType*/ any; + factory: Function; + } + + function defineInjectable(opts: Injectable): Injectable { + // This class should be imported from https://github.com/angular/angular/pull/20850 + return opts; + } + function defineInjector(opts: any): any { + // This class should be imported from https://github.com/angular/angular/pull/20850 + return opts; + } + it('should convert module', () => { + @Injectable() + class Toast { + constructor(name: String) {} + // NORMATIVE + static ngInjectableDef = defineInjectable({ + factory: () => new Toast($r3$.ɵinject(String)), + }); + // /NORMATIVE + } + + class CommonModule { + // NORMATIVE + static ngInjectorDef = defineInjector({}); + // /NORMATIVE + } + + @NgModule({ + providers: [Toast, {provide: String, useValue: 'Hello'}], + imports: [CommonModule], + }) + class MyModule { + constructor(toast: Toast) {} + // NORMATIVE + static ngInjectorDef = defineInjector({ + factory: () => new MyModule($r3$.ɵinject(Toast)), + provider: [ + {provide: Toast, deps: [String]}, // If Toast has metadata generate this line + Toast, // If Toast has no metadata generate this line. + {provide: String, useValue: 'Hello'} + ], + imports: [CommonModule] + }); + // /NORMATIVE + } + + @Injectable(/*{MyModule}*/) + class BurntToast { + constructor(@Optional() toast: Toast|null, name: String) {} + // NORMATIVE + static ngInjectableDef = defineInjectable({ + scope: MyModule, + factory: () => new BurntToast( + $r3$.ɵinject(Toast, $r3$.ɵInjectFlags.Optional), $r3$.ɵinject(String)), + }); + // /NORMATIVE + } + + }); +}); diff --git a/packages/core/test/render3/compiler_canonical/normative.md b/packages/core/test/render3/compiler_canonical/normative.md index 96653d9d74..992bf86d3f 100644 --- a/packages/core/test/render3/compiler_canonical/normative.md +++ b/packages/core/test/render3/compiler_canonical/normative.md @@ -1,5 +1,11 @@ This folder contains canonical examples of how the Ivy compiler translates annotations into code - The specs are marked with `NORMATIVE` => `/NORMATIVE` comments which designates what the compiler is expected to generate. -- All local variable names are considered non-normative (informative). +- All local variable names are considered non-normative (informative). They should be wrapped in `$` on each end to simplify testing on the compiler side. +A common trick in spec files is to map types to `$x$` (such as `boolean` => `$boolean$`, etc) to simplify testing for compiler, as types aren't saved. (See bullet above). +``` +type $boolean$ = boolean; +type $any$ = any; +type $number$ = number; +``` \ No newline at end of file diff --git a/packages/core/test/render3/compiler_canonical/pipes_spec.ts b/packages/core/test/render3/compiler_canonical/pipes_spec.ts new file mode 100644 index 0000000000..5cdf0a5ca9 --- /dev/null +++ b/packages/core/test/render3/compiler_canonical/pipes_spec.ts @@ -0,0 +1,80 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; +import * as $r3$ from '../../../src/core_render3_private_export'; +import {renderComponent, toHtml} from '../render_util'; + +/// See: `normative.md` +xdescribe('pipes', () => { + type $MyApp$ = MyApp; + type $boolean$ = boolean; + + @Pipe({ + name: 'myPipe', + pure: false, + }) + class MyPipe implements PipeTransform, + OnDestroy { + transform(value: any, ...args: any[]) { throw new Error('Method not implemented.'); } + ngOnDestroy(): void { throw new Error('Method not implemented.'); } + + // NORMATIVE + static ngPipeDef = $r3$.ɵdefinePipe({ + type: MyPipe, + factory: function MyPipe_Factory() { return new MyPipe(); }, + pure: false, + }); + // /NORMATIVE + } + + @Pipe({ + name: 'myPurePipe', + }) + class MyPurePipe implements PipeTransform { + transform(value: any, ...args: any[]) { throw new Error('Method not implemented.'); } + + // NORMATIVE + static ngPipeDef = $r3$.ɵdefinePipe({ + type: MyPurePipe, + factory: function MyPurePipe_Factory() { return new MyPurePipe(); }, + }); + // /NORMATIVE + } + + // NORMATIVE + const $MyPurePipe_ngPipeDef$ = MyPurePipe.ngPipeDef; + const $MyPipe_ngPipeDef$ = MyPipe.ngPipeDef; + // /NORMATIVE + + @Component({template: `{{name | myPipe:size | myPurePipe:size }}`}) + class MyApp { + name = 'World'; + size = 0; + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵT(0); + $r3$.ɵPp(1, $MyPurePipe_ngPipeDef$, $MyPurePipe_ngPipeDef$.n()); + $r3$.ɵPp(2, $MyPipe_ngPipeDef$, $MyPipe_ngPipeDef$.n()); + } + $r3$.ɵt(2, $r3$.ɵi1('', $r3$.ɵpb2(1, $r3$.ɵpb2(2, ctx.name, ctx.size), ctx.size), '')); + } + }); + // /NORMATIVE + } + + it('should render pipes', () => { + // TODO(misko): write a test once pipes runtime is implemented. + }); +}); diff --git a/packages/core/test/render3/compiler_canonical/query_spec.ts b/packages/core/test/render3/compiler_canonical/query_spec.ts new file mode 100644 index 0000000000..90bd835415 --- /dev/null +++ b/packages/core/test/render3/compiler_canonical/query_spec.ts @@ -0,0 +1,168 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; +import * as $r3$ from '../../../src/core_render3_private_export'; +import {renderComponent, toHtml} from '../render_util'; + +/// See: `normative.md` +describe('queries', () => { + type $boolean$ = boolean; + type $number$ = number; + let someDir: SomeDirective; + + @Directive({ + selector: '[someDir]', + }) + class SomeDirective { + static ngDirectiveDef = $r3$.ɵdefineDirective({ + type: SomeDirective, + factory: function SomeDirective_Factory() { return someDir = new SomeDirective(); }, + features: [$r3$.ɵPublicFeature] + }); + } + + it('should support view queries', () => { + type $ViewQueryComponent$ = ViewQueryComponent; + + // NORMATIVE + const $e1_attrs$ = ['someDir', '']; + const $e1_dirs$ = [SomeDirective]; + // /NORMATIVE + + @Component({ + selector: 'view-query-component', + template: ` +
    + ` + }) + class ViewQueryComponent { + @ViewChild(SomeDirective) someDir: SomeDirective; + @ViewChildren(SomeDirective) someDirList: QueryList; + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: ViewQueryComponent, + tag: 'view-query-component', + factory: function ViewQueryComponent_Factory() { return new ViewQueryComponent(); }, + template: function ViewQueryComponent_Template(ctx: $ViewQueryComponent$, cm: $boolean$) { + let $tmp$: any; + if (cm) { + $r3$.ɵQ(0, SomeDirective, false); + $r3$.ɵQ(1, SomeDirective, false); + $r3$.ɵE(2, 'div', $e1_attrs$, $e1_dirs$); + $r3$.ɵe(); + } + + $r3$.ɵqR($tmp$ = $r3$.ɵld>(0)) && (ctx.someDir = $tmp$.first); + $r3$.ɵqR($tmp$ = $r3$.ɵld>(1)) && + (ctx.someDirList = $tmp$ as QueryList); + SomeDirective.ngDirectiveDef.h(3, 2); + $r3$.ɵr(3, 2); + } + }); + // /NORMATIVE + } + + + const viewQueryComp = renderComponent(ViewQueryComponent); + expect(viewQueryComp.someDir).toEqual(someDir); + expect((viewQueryComp.someDirList as QueryList).toArray()).toEqual([someDir !]); + }); + + it('should support content queries', () => { + type $MyApp$ = MyApp; + type $ContentQueryComponent$ = ContentQueryComponent; + + let contentQueryComp: ContentQueryComponent; + + @Component({ + selector: 'content-query-component', + template: ` +
    + ` + }) + class ContentQueryComponent { + @ContentChild(SomeDirective) someDir: SomeDirective; + @ContentChildren(SomeDirective) someDirList: QueryList; + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: ContentQueryComponent, + tag: 'content-query-component', + factory: function ContentQueryComponent_Factory() { + return [ + new ContentQueryComponent(), $r3$.ɵQ(null, SomeDirective, false), + $r3$.ɵQ(null, SomeDirective, false) + ]; + }, + hostBindings: function ContentQueryComponent_HostBindings( + dirIndex: $number$, elIndex: $number$) { + let $tmp$: any; + const $instance$ = $r3$.ɵld(dirIndex)[0]; + $r3$.ɵqR($tmp$ = $r3$.ɵld(dirIndex)[1]) && ($instance$.someDir = $tmp$.first); + $r3$.ɵqR($tmp$ = $r3$.ɵld(dirIndex)[2]) && ($instance$.someDirList = $tmp$); + }, + template: function ContentQueryComponent_Template( + ctx: $ContentQueryComponent$, cm: $boolean$) { + if (cm) { + $r3$.ɵpD(0); + $r3$.ɵE(1, 'div'); + $r3$.ɵP(2, 0); + $r3$.ɵe(); + } + } + }); + // /NORMATIVE + } + + const $e2_attrs$ = ['someDir', '']; + const $e2_dirs$ = [SomeDirective]; + + @Component({ + selector: 'my-app', + template: ` + +
    +
    + ` + }) + class MyApp { + // NON-NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, ContentQueryComponent); + contentQueryComp = $r3$.ɵld(1)[0]; + $r3$.ɵE(2, 'div', $e2_attrs$, $e2_dirs$); + $r3$.ɵe(); + $r3$.ɵe(); + } + ContentQueryComponent.ngComponentDef.h(1, 0); + SomeDirective.ngDirectiveDef.h(3, 2); + $r3$.ɵr(1, 0); + $r3$.ɵr(3, 2); + } + }); + // /NON-NORMATIVE + } + + + expect(toHtml(renderComponent(MyApp))) + .toEqual( + `
    `); + expect(contentQueryComp !.someDir).toEqual(someDir !); + expect((contentQueryComp !.someDirList as QueryList).toArray()).toEqual([ + someDir ! + ]); + }); + +}); diff --git a/packages/core/test/render3/compiler_canonical/small_app_spec.ts b/packages/core/test/render3/compiler_canonical/small_app_spec.ts index c551db697f..4c094cb3e6 100644 --- a/packages/core/test/render3/compiler_canonical/small_app_spec.ts +++ b/packages/core/test/render3/compiler_canonical/small_app_spec.ts @@ -12,6 +12,7 @@ import {withBody} from '@angular/core/testing'; import * as r3 from '../../../src/render3/index'; +/// See: `normative.md` // TODO: remove once https://github.com/angular/angular/pull/22005 lands diff --git a/packages/core/test/render3/compiler_canonical/template_variables_spec.ts b/packages/core/test/render3/compiler_canonical/template_variables_spec.ts new file mode 100644 index 0000000000..209282d091 --- /dev/null +++ b/packages/core/test/render3/compiler_canonical/template_variables_spec.ts @@ -0,0 +1,213 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; +import * as $r3$ from '../../../src/core_render3_private_export'; +import {renderComponent, toHtml} from '../render_util'; + +/// See: `normative.md` +describe('template variables', () => { + type $boolean$ = boolean; + type $any$ = any; + type $number$ = number; + + interface ForOfContext { + $implicit: any; + index: number; + even: boolean; + odd: boolean; + } + + @Directive({selector: '[forOf]'}) + class ForOfDirective { + private previous: any[]; + + constructor(private view: ViewContainerRef, private template: TemplateRef) {} + + @Input() forOf: any[]; + + ngOnChanges(simpleChanges: SimpleChanges) { + if ('forOf' in simpleChanges) { + this.update(); + } + } + + ngDoCheck(): void { + const previous = this.previous; + const current = this.forOf; + if (!previous || previous.length != current.length || + previous.some((value: any, index: number) => current[index] !== previous[index])) { + this.update(); + } + } + + private update() { + // TODO(chuckj): Not implemented yet + // this.view.clear(); + if (this.forOf) { + const current = this.forOf; + for (let i = 0; i < current.length; i++) { + const context = {$implicit: current[i], index: i, even: i % 2 == 0, odd: i % 2 == 1}; + // TODO(chuckj): Not implemented yet + // this.view.createEmbeddedView(this.template, context); + } + this.previous = [...this.forOf]; + } + } + + // NORMATIVE + static ngDirectiveDef = $r3$.ɵdefineDirective({ + type: ForOfDirective, + factory: function ForOfDirective_Factory() { + return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef()); + }, + // TODO(chuckj): Enable when ngForOf enabling lands. + // features: [NgOnChangesFeature(NgForOf)], + inputs: {forOf: 'forOf'} + }); + // /NORMATIVE + } + + it('should support a let variable and reference', () => { + type $MyComponent$ = MyComponent; + + interface Item { + name: string; + } + + // NORMATIVE + const $c1_dirs$ = [ForOfDirective]; + // /NORMATIVE + + @Component({ + selector: 'my-component', + template: `
    • {{item.name}}
    ` + }) + class MyComponent { + items = [{name: 'one'}, {name: 'two'}]; + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComponent, + tag: 'my-component', + factory: function MyComponent_Factory() { return new MyComponent(); }, + template: function MyComponent_Template(ctx: $MyComponent$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'ul'); + $r3$.ɵC(1, $c1_dirs$, MyComponent_ForOfDirective_Template_1); + $r3$.ɵe(); + } + $r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items)); + $r3$.ɵcR(1); + $r3$.ɵr(2, 1); + $r3$.ɵcr(); + + function MyComponent_ForOfDirective_Template_1(ctx1: $any$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'li'); + $r3$.ɵT(1); + $r3$.ɵe(); + } + const $l0_item$ = ctx1.$implicit; + $r3$.ɵt(1, $r3$.ɵi1('', $l0_item$.name, '')); + } + } + }); + // /NORMATIVE + } + + // TODO(chuckj): update when the changes to enable ngForOf lands. + expect(toHtml(renderComponent(MyComponent))).toEqual('
      '); + }); + + it('should support accessing parent template variables', () => { + type $MyComponent$ = MyComponent; + + interface Info { + description: string; + } + interface Item { + name: string; + infos: Info[]; + } + + // NORMATIVE + const $c1_dirs$ = [ForOfDirective]; + // /NORMATIVE + + @Component({ + selector: 'my-component', + template: ` +
        +
      • +
        {{item.name}}
        +
          +
        • + {{item.name}}: {{info.description}} +
        • +
        +
      • +
      ` + }) + class MyComponent { + items: Item[] = [ + {name: 'one', infos: [{description: '11'}, {description: '12'}]}, + {name: 'two', infos: [{description: '21'}, {description: '22'}]} + ]; + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComponent, + tag: 'my-component', + factory: function MyComponent_Factory() { return new MyComponent(); }, + template: function MyComponent_Template(ctx: $MyComponent$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'ul'); + $r3$.ɵC(1, $c1_dirs$, MyComponent_ForOfDirective_Template_1); + $r3$.ɵe(); + } + $r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items)); + $r3$.ɵcR(1); + $r3$.ɵr(2, 1); + $r3$.ɵcr(); + + function MyComponent_ForOfDirective_Template_1(ctx1: $any$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'li'); + $r3$.ɵE(1, 'div'); + $r3$.ɵT(2); + $r3$.ɵe(); + $r3$.ɵE(3, 'ul'); + $r3$.ɵC(4, $c1_dirs$, MyComponent_ForOfDirective_ForOfDirective_Template_3); + $r3$.ɵe(); + $r3$.ɵe(); + } + const $l0_item$ = ctx1.$implicit; + $r3$.ɵp(4, 'forOf', $r3$.ɵb($l0_item$.infos)); + $r3$.ɵt(2, $r3$.ɵi1('', $l0_item$.name, '')); + $r3$.ɵcR(4); + $r3$.ɵr(5, 4); + $r3$.ɵcr(); + + function MyComponent_ForOfDirective_ForOfDirective_Template_3( + ctx2: $any$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'li'); + $r3$.ɵT(1); + $r3$.ɵe(); + } + const $l0_info$ = ctx2.$implicit; + $r3$.ɵt(1, $r3$.ɵi2(' ', $l0_item$.name, ': ', $l0_info$.description, ' ')); + } + } + } + }); + // /NORMATIVE + } + }); +}); From 51ca643c276dae1336ed4420267ed4a3b0e0676b Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 28 Feb 2018 22:18:34 -0800 Subject: [PATCH 037/604] test(ivy): add injectAttribute spec (#22510) PR Close #22510 --- .../core/src/core_render3_private_export.ts | 1 + packages/core/src/render3/di.ts | 50 ++++++++++++++++++- packages/core/src/render3/index.ts | 3 +- .../compiler_canonical/injection_spec.ts | 47 ++++++++++++++++- packages/core/test/render3/di_spec.ts | 25 +++++++++- 5 files changed, 122 insertions(+), 4 deletions(-) diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index fedee6c755..41a4a91bce 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -18,6 +18,7 @@ export { injectTemplateRef as ɵinjectTemplateRef, injectViewContainerRef as ɵinjectViewContainerRef, injectChangeDetectorRef as ɵinjectChangeDetectorRef, + injectAttribute as ɵinjectAttribute, InjectFlags as ɵInjectFlags, PublicFeature as ɵPublicFeature, NgOnChangesFeature as ɵNgOnChangesFeature, diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 27446d1503..120b7d00e2 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -18,7 +18,7 @@ import {ViewContainerRef as viewEngine_ViewContainerRef} from '../linker/view_co import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_ViewRef} from '../linker/view_ref'; import {Type} from '../type'; -import {assertLessThan} from './assert'; +import {assertLessThan, assertNotNull} from './assert'; import {assertPreviousIsParent, getDirectiveInstance, getPreviousOrParentNode, getRenderer, renderEmbeddedTemplate} from './instructions'; import {ComponentTemplate, DirectiveDef} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; @@ -234,6 +234,54 @@ export function injectChangeDetectorRef(): viewEngine_ChangeDetectorRef { return getOrCreateChangeDetectorRef(getOrCreateNodeInjector(), null); } +/** + * Inject static attribute value into directive constructor. + * + * This method is used with `factory` functions which are generated as part of + * `defineDirective` or `defineComponent`. The method retrieves the static value + * of an attribute. (Dynamic attributes are not supported since they are not resolved + * at the time of injection and can change over time.) + * + * # Example + * Given: + * ``` + * @Component(...) + * class MyComponent { + * constructor(@Attribute('title') title: string) { ... } + * } + * ``` + * When instantiated with + * ``` + * + * ``` + * + * Then factory method generated is: + * ``` + * MyComponent.ngComponentDef = defineComponent({ + * factory: () => new MyComponent(injectAttribute('title')) + * ... + * }) + * ``` + * + * @experimental + */ +export function injectAttribute(attrName: string): string|undefined { + ngDevMode && assertPreviousIsParent(); + const lElement = getPreviousOrParentNode() as LElementNode; + ngDevMode && assertNodeType(lElement, LNodeFlags.Element); + const tElement = lElement.tNode !; + ngDevMode && assertNotNull(tElement, 'expecting tNode'); + const attrs = tElement.attrs; + if (attrs) { + for (let i = 0; i < attrs.length; i = i + 2) { + if (attrs[i] == attrName) { + return attrs[i + 1]; + } + } + } + return undefined; +} + /** * Creates a ViewRef and stores it on the injector as ChangeDetectorRef (public alias). * Or, if it already exists, retrieves the existing instance. diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 6391cb5a9b..7d764f6f34 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -11,10 +11,11 @@ import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, def import {InjectFlags} from './di'; import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './interfaces/definition'; -export {InjectFlags, QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, inject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di'; +export {InjectFlags, QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, inject, injectAttribute, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di'; export {CssSelector} from './interfaces/projection'; + // Naming scheme: // - Capital letters are for creating things: T(Text), E(Element), D(Directive), V(View), // C(Container), L(Listener) diff --git a/packages/core/test/render3/compiler_canonical/injection_spec.ts b/packages/core/test/render3/compiler_canonical/injection_spec.ts index 463a4a7b60..5e69202be0 100644 --- a/packages/core/test/render3/compiler_canonical/injection_spec.ts +++ b/packages/core/test/render3/compiler_canonical/injection_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; +import {Attribute, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, ContentChildren, Directive, HostBinding, HostListener, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, ViewChild, ViewChildren, ViewContainerRef} from '../../../src/core'; import * as $r3$ from '../../../src/core_render3_private_export'; import {renderComponent, toHtml} from '../render_util'; @@ -60,4 +60,49 @@ describe('injection', () => { expect(toHtml(app)).toEqual('ViewRef'); }); + it('should inject attributes', () => { + type $MyComp$ = MyComp; + type $MyApp$ = MyApp; + + @Component({selector: 'my-comp', template: `{{ title }}`}) + class MyComp { + constructor(@Attribute('title') public title: string|undefined) {} + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComp, + tag: 'my-comp', + factory: function MyComp_Factory() { return new MyComp($r3$.ɵinjectAttribute('title')); }, + template: function MyComp_Template(ctx: $MyComp$, cm: $boolean$) { + if (cm) { + $r3$.ɵT(0); + } + $r3$.ɵt(0, $r3$.ɵb(ctx.title)); + } + }); + // /NORMATIVE + } + + class MyApp { + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + /** */ + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, MyComp, e0_attrs); + $r3$.ɵe(); + } + MyComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + } + const e0_attrs = ['title', 'WORKS']; + const app = renderComponent(MyApp); + // ChangeDetectorRef is the token, ViewRef is historically the constructor + expect(toHtml(app)).toEqual('WORKS'); + }); + }); \ No newline at end of file diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 97c79c2bb7..33b97de204 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -9,7 +9,7 @@ import {ChangeDetectorRef, ElementRef, TemplateRef, ViewContainerRef} from '@angular/core'; import {defineComponent} from '../../src/render3/definition'; -import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector} from '../../src/render3/di'; +import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di'; import {NgOnChangesFeature, PublicFeature, defineDirective, inject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, directiveRefresh, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {LInjector} from '../../src/render3/interfaces/injector'; @@ -465,6 +465,29 @@ describe('di', () => { expect(dir !.cdr).toBe(dirSameInstance !.cdr); }); + it('should injectAttribute', () => { + let exist: string|undefined = 'wrong'; + let nonExist: string|undefined = 'wrong'; + class MyApp { + static ngComponentDef = defineComponent({ + type: MyApp, + tag: 'my-app', + factory: () => new MyApp(), + template: function(ctx: MyApp, cm: boolean) { + if (cm) { + elementStart(0, 'div', ['exist', 'existValue', 'other', 'ignore']); + exist = injectAttribute('exist'); + nonExist = injectAttribute('nonExist'); + } + } + }); + } + + const app = renderComponent(MyApp); + expect(exist).toEqual('existValue'); + expect(nonExist).toEqual(undefined); + }); + }); describe('inject', () => { From 12be311618e2ae32df15a523782237907725d51a Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Thu, 1 Mar 2018 10:56:32 +0000 Subject: [PATCH 038/604] fix(aio): remove all links from toc titles (#22533) The previous approach just removed the first `a` tag that was found, but now that the header-link anchor is not at the start of the heading, it could fail. Closes #22493 PR Close #22533 --- aio/src/app/shared/toc.service.spec.ts | 28 +++++++++++++++----------- aio/src/app/shared/toc.service.ts | 24 +++++++++++++++------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/aio/src/app/shared/toc.service.spec.ts b/aio/src/app/shared/toc.service.spec.ts index 8b19fc9371..f3af3d071c 100644 --- a/aio/src/app/shared/toc.service.spec.ts +++ b/aio/src/app/shared/toc.service.spec.ts @@ -291,23 +291,20 @@ describe('TocService', () => { }); }); - describe('TocItem for an h2 with anchor link and extra whitespace', () => { + describe('TocItem for an h2 with links and extra whitespace', () => { let docId: string; - let docEl: HTMLDivElement; let tocItem: TocItem; - let expectedTocContent: string; beforeEach(() => { docId = 'fizz/buzz/'; - expectedTocContent = 'Setup to develop locally.'; // An almost-actual

      ... with extra whitespace - docEl = callGenToc(` + callGenToc(`

      - develop locally. + - ${expectedTocContent}

      `, docId); @@ -331,7 +328,7 @@ describe('TocService', () => { it('should have bypassed HTML sanitizing of heading\'s innerHTML ', () => { const domSanitizer: TestDomSanitizer = injector.get(DomSanitizer); expect(domSanitizer.bypassSecurityTrustHtml) - .toHaveBeenCalledWith(expectedTocContent); + .toHaveBeenCalledWith('Setup to develop locally.'); }); }); }); @@ -352,13 +349,20 @@ class TestDomSanitizer { } class MockScrollSpyService { - $lastInfo: { + private $$lastInfo: { active: Subject, - unspy: jasmine.Spy - }; + unspy: jasmine.Spy, + } | undefined; + + get $lastInfo() { + if (!this.$$lastInfo) { + throw new Error('$lastInfo is not yet defined. You must call `spyOn` first.'); + } + return this.$$lastInfo; + } spyOn(headings: HTMLHeadingElement[]): ScrollSpyInfo { - return this.$lastInfo = { + return this.$$lastInfo = { active: new Subject(), unspy: jasmine.createSpy('unspy'), }; diff --git a/aio/src/app/shared/toc.service.ts b/aio/src/app/shared/toc.service.ts index a321f9b9fb..df5f50b94f 100644 --- a/aio/src/app/shared/toc.service.ts +++ b/aio/src/app/shared/toc.service.ts @@ -16,7 +16,7 @@ export interface TocItem { export class TocService { tocList = new ReplaySubject(1); activeItemIndex = new ReplaySubject(1); - private scrollSpyInfo: ScrollSpyInfo | null; + private scrollSpyInfo: ScrollSpyInfo | null = null; constructor( @Inject(DOCUMENT) private document: any, @@ -53,15 +53,25 @@ export class TocService { // This bad boy exists only to strip off the anchor link attached to a heading private extractHeadingSafeHtml(heading: HTMLHeadingElement) { - const a = this.document.createElement('a') as HTMLAnchorElement; - a.innerHTML = heading.innerHTML; - const anchorLink = a.querySelector('a'); - if (anchorLink) { - a.removeChild(anchorLink); + const div: HTMLDivElement = this.document.createElement('div'); + div.innerHTML = heading.innerHTML; + const anchorLinks: NodeListOf = div.querySelectorAll('a'); + for (let i = 0; i < anchorLinks.length; i++) { + const anchorLink = anchorLinks[i]; + if (!anchorLink.classList.contains('header-link')) { + // this is an anchor that contains actual content that we want to keep + // move the contents of the anchor into its parent + const parent = anchorLink.parentNode!; + while (anchorLink.childNodes.length) { + parent.insertBefore(anchorLink.childNodes[0], anchorLink); + } + } + // now remove the anchor + anchorLink.remove(); } // security: the document element which provides this heading content // is always authored by the documentation team and is considered to be safe - return this.domSanitizer.bypassSecurityTrustHtml(a.innerHTML.trim()); + return this.domSanitizer.bypassSecurityTrustHtml(div.innerHTML.trim()); } private findTocHeadings(docElement: Element): HTMLHeadingElement[] { From 1d2bdcb4d0f817a4024d90d0385cb71e6a227e80 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Thu, 1 Mar 2018 20:19:18 +0000 Subject: [PATCH 039/604] build(aio): render param descriptions for function exports (#22534) Closes #22501 PR Close #22534 --- .../templates/api/function.template.html | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/aio/tools/transforms/templates/api/function.template.html b/aio/tools/transforms/templates/api/function.template.html index ea75648360..c5707f1f2a 100644 --- a/aio/tools/transforms/templates/api/function.template.html +++ b/aio/tools/transforms/templates/api/function.template.html @@ -1,24 +1,33 @@ +{% import "lib/memberHelpers.html" as memberHelpers -%} {% import "lib/paramList.html" as params -%} {% extends 'export-base.template.html' -%} {% block overview %} - -function {$ doc.name $}{$ doc.typeParameters | escape $}{$ params.paramList(doc.parameters) $} -{%- if doc.type %}: {$ doc.type | escape $}{% endif %}; - +{% if doc.overloads.length > 0 and doc.overloads < 3 -%} + {% for overload in doc.overloads -%} + {$ memberHelpers.renderOverloadInfo(overload, 'function-overload', doc) $} + {% if not loop.last %}
      {% endif %} + {% endfor -%} +{% else %} + {$ memberHelpers.renderOverloadInfo(doc, 'function-overload', doc) $} +{% endif %} {% endblock %} + {% block details %} {% include "includes/description.html" %} -{% if doc.overloads.length %} -

      Overloads

      {% for overload in doc.overloads %} - - function {$ overload.name $}{$ doc.typeParameters | escape $}{$ params.paramList(overload.parameters) $} - {%- if overload.type %}: {$ overload.type | escape $}{% endif %}; - -
      - {$ overload.description | trimBlankLines | marked $} -
      -{% endfor %} +{% if doc.overloads.length >= 3 %} +
      +

      Overloads

      + + {% for overload in doc.overloads %} + + + + {% endfor %} +
      + {$ memberHelpers.renderOverloadInfo(overload, 'function-overload', doc) $} +
      +
      {% endif %} {% endblock %} \ No newline at end of file From b64139650ca3a7a44d4998ab6401f8685b505a35 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Thu, 1 Mar 2018 20:19:23 +0000 Subject: [PATCH 040/604] build(aio): class API template once again extends export-base (#22534) PR Close #22534 --- .../templates/api/class.template.html | 40 ++++++++----------- .../templates/api/export-base.template.html | 2 +- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/aio/tools/transforms/templates/api/class.template.html b/aio/tools/transforms/templates/api/class.template.html index 60445b3372..175144103f 100644 --- a/aio/tools/transforms/templates/api/class.template.html +++ b/aio/tools/transforms/templates/api/class.template.html @@ -1,29 +1,23 @@ {% import "lib/memberHelpers.html" as memberHelpers -%} {% import "lib/descendants.html" as descendants -%} {% import "lib/paramList.html" as params -%} -{% extends 'base.template.html' -%} +{% extends 'export-base.template.html' -%} -{% block body %} -

      {$ doc.shortDescription | marked $}

      - {% include "includes/security-notes.html" %} - {% include "includes/deprecation.html" %} - {% block overview %} +{% block overview %} {% include "includes/class-overview.html" %} - {% endblock %} - {% block details %} - {% block additional %}{% endblock %} - {% include "includes/description.html" %} - {$ memberHelpers.renderProperties(doc.staticProperties, 'static-properties', 'static-property', 'Static Properties') $} - {$ memberHelpers.renderMethodDetails(doc.staticMethods, 'static-methods', 'static-method', 'Static Methods') $} - {% if doc.constructorDoc %} -

      Constructor

      - {$ memberHelpers.renderMethodDetail(doc.constructorDoc, 'constructor') $}{% endif %} - - {$ memberHelpers.renderProperties(doc.properties, 'instance-properties', 'instance-property', 'Properties') $} - - {$ memberHelpers.renderMethodDetails(doc.methods, 'instance-methods', 'instance-method', 'Methods') $} - - {% block annotations %}{% include "includes/annotations.html" %}{% endblock %} - {% endblock %} - {% include "includes/usageNotes.html" %} +{% endblock %} +{% block details %} + {% block additional %}{% endblock %} + {% include "includes/description.html" %} + {$ memberHelpers.renderProperties(doc.staticProperties, 'static-properties', 'static-property', 'Static Properties') $} + {$ memberHelpers.renderMethodDetails(doc.staticMethods, 'static-methods', 'static-method', 'Static Methods') $} + {% if doc.constructorDoc %} +

      Constructor

      + {$ memberHelpers.renderMethodDetail(doc.constructorDoc, 'constructor') $}{% endif %} + + {$ memberHelpers.renderProperties(doc.properties, 'instance-properties', 'instance-property', 'Properties') $} + + {$ memberHelpers.renderMethodDetails(doc.methods, 'instance-methods', 'instance-method', 'Methods') $} + + {% block annotations %}{% include "includes/annotations.html" %}{% endblock %} {% endblock %} diff --git a/aio/tools/transforms/templates/api/export-base.template.html b/aio/tools/transforms/templates/api/export-base.template.html index b2e24aa799..df5e0293fa 100644 --- a/aio/tools/transforms/templates/api/export-base.template.html +++ b/aio/tools/transforms/templates/api/export-base.template.html @@ -5,6 +5,6 @@ {% include "includes/security-notes.html" %} {% include "includes/deprecation.html" %} {% block overview %}{% endblock %} - {% include "includes/usageNotes.html" %} {% block details %}{% endblock %} + {% include "includes/usageNotes.html" %} {% endblock %} From 3aea8fd5f3c51dab8b21009600dbf0e11f97ae58 Mon Sep 17 00:00:00 2001 From: "SangKa.Z" Date: Fri, 2 Mar 2018 21:52:58 +0800 Subject: [PATCH 041/604] docs(aio): fix table header (#22553) PR Close #22553 --- aio/content/guide/comparing-observables.md | 28 ++++++++++++---------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/aio/content/guide/comparing-observables.md b/aio/content/guide/comparing-observables.md index 9cb663cd56..5e7b9b0ca4 100644 --- a/aio/content/guide/comparing-observables.md +++ b/aio/content/guide/comparing-observables.md @@ -89,11 +89,11 @@ promise.then(() => { The following code snippets illustrate how the same kind of operation is defined using observables and promises. - - - - + + + + +
      - OperationObservablePromise
      OperationObservablePromise
      Creation @@ -141,10 +141,11 @@ Using observables to handle events and asynchronous operations can have the adva Here are some code samples that illustrate how the same kind of operation is defined using observables and the events API. - - - + + + + + From 363dfa5437bd096ae1c0891f534ff542ef51ddeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=A1ko=20Hevery?= Date: Tue, 13 Feb 2018 14:25:13 -0800 Subject: [PATCH 058/604] test(ivy): Back patch example (#22235) PR Close #22235 --- .../back_patch_types_specs.ts | 138 ++++++++++++++++++ .../compiler_canonical/patch_types_spec.ts | 78 ++++++++++ 2 files changed, 216 insertions(+) create mode 100644 packages/core/test/render3/compiler_canonical/back_patch_types_specs.ts create mode 100644 packages/core/test/render3/compiler_canonical/patch_types_spec.ts diff --git a/packages/core/test/render3/compiler_canonical/back_patch_types_specs.ts b/packages/core/test/render3/compiler_canonical/back_patch_types_specs.ts new file mode 100644 index 0000000000..7c3e597dfe --- /dev/null +++ b/packages/core/test/render3/compiler_canonical/back_patch_types_specs.ts @@ -0,0 +1,138 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Component, ContentChild, Directive, Injectable, Injector, Input, NgModule, NgModuleFactory, NgModuleRef, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef} from '../../../src/core'; +import * as r3 from '../../../src/render3/index'; + +import {pending_pull_22005} from './small_app_spec'; + +const details_elided = { + type: Object, +} as any; +export type $ComponentDef$ = any; + +/////////// +// Lib A - Compiled pre-Ivy +// "enableIvy": false +////////// + +// BEGIN FILE: node_modules/libA/module.ts (Compiled without Ivy) +@Component({}) +export class LibAComponent { +} + +@NgModule({declarations: [LibAComponent], imports: []}) +export class LibAModule { +} +// END FILE: node_modules/libA/module.ts +// BEGIN FILE: node_modules/libA/module.metadata.json +// Abridged version of metadata +const node_modules_libA_module_metadata = { + 'LibAModule': { + refs: ['LibAComponent'], + constructorDes: [], + }, + 'LibAComponent': { + constructorDes: [], + } +}; +// END FILE: node_modules/libA/module.metadata.json + + +/////////// +// Lib B - Compiled with Ivy +// "enableIvy": true +////////// + + +// BEGIN FILE: node_modules/libB/module.ts (Compiled with Ivy) +@Component({}) +export class LibBComponent { + // COMPILER GENERATED + static ngComponentDef: $ComponentDef$ = r3.defineComponent(details_elided); +} + +@NgModule({declarations: [LibAComponent], imports: []}) +export class LibBModule { + // COMPILER GENERATED + static ngInjectorDef = pending_pull_22005.defineInjector(details_elided); +} +// END FILE: node_modules/libB/module.ts +// BEGIN FILE: node_modules/libB/module.metadata.json +// Abridged version of metadata +// Must still generate metadata in case it should be consumed with non-ivy application +// Must mark the metadata with `hasNgDef: true` so that Ivy knows to ignore it. +const node_modules_libB_module_metadata = { + 'LibBModule': {refs: ['LibBComponent'], constructorDes: [], hasNgDef: true}, + 'LibBComponent': {constructorDes: [], hasNgDef: true} +}; +// END FILE: node_modules/libA/module.metadata.json + + + +/////////// +// Lib B - Compiled with Ivy +// "enableIvy": true +// "enableIvyBackPatch": true +////////// + + +// BEGIN FILE: src/app.ts (Compiled with Ivy) +@Component({}) +export class AppComponent { + // COMPILER GENERATED + static ngComponentDef: $ComponentDef$ = r3.defineComponent(details_elided); +} + +@NgModule({declarations: [LibAComponent], imports: []}) +export class AppModule { + // COMPILER GENERATED + static ngInjectorDef = pending_pull_22005.defineInjector(details_elided); +} +// END FILE: src/app.ts + +// BEGIN FILE: src/main.ts +// platformBrowserDynamic().bootstrapModule(AppModule); +// CLI rewrites it later to: +// platformBrowser().bootstrapModuleFactory(AppModuleFactory); +// END FILE: src/main.ts + +// BEGIN FILE: src/app.ngfactory.ts +function ngBackPatch_node_modules_libB_module() { + ngBackPatch_node_modules_libB_module_LibAComponent(); + ngBackPatch_node_modules_libB_module_LibAModule(); +} + +function ngBackPatch_node_modules_libB_module_LibAComponent() { + (LibAComponent as any).ngComponentDef = r3.defineComponent(details_elided); +} + +function ngBackPatch_node_modules_libB_module_LibAModule() { + (LibAModule as any).ngInjectorDef = pending_pull_22005.defineInjector(details_elided); +} + +export const AppModuleFactory: NgModuleFactory&{patchedDeps: boolean} = { + moduleType: AppModule, + patchedDeps: false, + create(parentInjector: Injector | null): NgModuleRef{ + this.patchedDeps && ngBackPatch_node_modules_libB_module() && (this.patchedDeps = true); + return details_elided;} +}; +// BEGIN FILE: src/app.ngfactory.ts + + +// ISSUE: I don't think this works. The issue is that multiple modules get flattened into single +// module and hence we can't patch transitively. +// ISSUE: can non-ivy @NgModule import Ivy @NgModule? I assume no, since the flattening of modules +// happens during compilation. + +// BEGIN FILE: src/main.ts +// platformBrowserDynamic().bootstrapModule(AppModule); +// CLI rewrites it to: +// platformBrowser().bootstrapModuleFactory(AppModuleFactory); +// END FILE: src/main.ts diff --git a/packages/core/test/render3/compiler_canonical/patch_types_spec.ts b/packages/core/test/render3/compiler_canonical/patch_types_spec.ts new file mode 100644 index 0000000000..ca4e37374c --- /dev/null +++ b/packages/core/test/render3/compiler_canonical/patch_types_spec.ts @@ -0,0 +1,78 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Component, ContentChild, Directive, Injectable, Input, NgModule, OnDestroy, Optional, Pipe, PipeTransform, QueryList, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef} from '../../../src/core'; +import * as r3 from '../../../src/render3/index'; +import {pending_pull_22005} from './small_app_spec'; + +/** + * GOALS: + * - Patch types in tree shakable way + * - Generate these types for files which have `metadata.json` (since those are the files which + * have not been compiled with Ivy) + * - Have build optimizer hoist the patch functions into corresponding types to allow tree-shaking. + */ + +// File: node_modules/some_library/path/public.ts +// Implies metadata: node_modules/some_library/path/public.metadata.json +// Assume: node_modules/some_library/index.js re-exports ./path/public.ts#ThirdPartyClass +@Injectable() +class ThirdPartyClass { +} + + +@Injectable() +class CompiledWithIvy { + // NORMATIVE + static ngInjectableDef = pending_pull_22005.defineInjectable( + {factory: function CompileWithIvy_Factory() { return new CompiledWithIvy(); }}); + // /NORMATIVE +} + +// import { CompiledWithIvy } from 'some_library'; +@NgModule({providers: [ThirdPartyClass, CompiledWithIvy]}) +class CompiledWithIvyModule { + // NORMATIVE + static ngInjectorDef = pending_pull_22005.defineInjector({ + providers: [ThirdPartyClass, CompiledWithIvy], + factory: function CompiledWithIvyModule_Factory() { return new CompiledWithIvyModule(); } + }); + // /NORMATIVE +} + +/** + * Below is a function which should be generated right next to the `@NgModule` which + * imports types which have `.metadata.json` files. + * + * # JIT Mode + * - Because the `ngPatch_CompiledWithIvyModule` is invoked all parts get patched. + * + * # AOT Mode + * - Build Optimizer detects `@__BUILD_OPTIMIZER_COLOCATE__` annotation and moves the + * code from the current location to the destination. + * - The resulting `ngPatch_CompiledWithIvyModule` becomes empty and eligible for tree-shaking. + * - Uglify removes the `ngPatch_CompiledWithIvyModule` since it is empty. + * + * # AOT Closure Mode + * - Option A: not supported. (Preferred option) + * - Externally very few people use closure they will just have to wait until all of their + * libraries are Ivy. + * - Internally (g3) we build from source hence everyone switches to Ivy at the same time. + * - Option B: Write a closure pass similar to Build Optimizer which would move the code. + */ +// NORMATIVE +ngPatch_depsOf_CompiledWithIvyModule(); +function ngPatch_depsOf_CompiledWithIvyModule() { + ngPatch_node_modules_some_library_path_public_CompileWithIvy(); +} +function ngPatch_node_modules_some_library_path_public_CompileWithIvy() { + /** @__BUILD_OPTIMIZER_COLOCATE__ */ + (ThirdPartyClass as any).ngInjectableDef = pending_pull_22005.defineInjectable( + {factory: function CompileWithIvy_Factory() { return new ThirdPartyClass(); }}); +} +// /NORMATIVE From 505ae752b61a44ca41396838a49f83cfe7badb3b Mon Sep 17 00:00:00 2001 From: yerkebulan Date: Fri, 2 Feb 2018 16:06:30 +0600 Subject: [PATCH 059/604] docs(aio): update deprecated Http reference to HttpClientModule, remove Http reference because another context is used (#21984) docs(aio): change HttpClientModule reference to HttpClient docs(aio): capitalize Http to HTTP docs(aio): fix typo mistake in 'universal' guide docs(aio): gets rid of the parentheses and the "e.g." in 'universal' guide PR Close #21984 --- aio/content/guide/universal.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aio/content/guide/universal.md b/aio/content/guide/universal.md index 3fecd8c9a8..746299e07c 100644 --- a/aio/content/guide/universal.md +++ b/aio/content/guide/universal.md @@ -210,10 +210,10 @@ You can get runtime information about the current platform and the `appId` by in #### Absolute HTTP URLs -The tutorial's `HeroService` and `HeroSearchService` delegate to the Angular `Http` module to fetch application data. +The tutorial's `HeroService` and `HeroSearchService` delegate to the Angular `HttpClient` module to fetch application data. These services send requests to _relative_ URLs such as `api/heroes`. -In a Universal app, `Http` URLs must be _absolute_ (e.g., `https://my-server.com/api/heroes`) +In a Universal app, HTTP URLs must be _absolute_, for example, `https://my-server.com/api/heroes` even when the Universal web server is capable of handling those requests. You'll have to change the services to make requests with absolute URLs when running on the server @@ -416,7 +416,7 @@ Create a `tsconfig.server.json` file in the project root directory to configure This config extends from the root's `tsconfig.json` file. Certain settings are noteworthy for their differences. -* The `module` property must be **commonjs** which can be require()'d into our server application. +* The `module` property must be **commonjs** which can be required into our server application. * The `angularCompilerOptions` section guides the AOT compiler: * `entryModule` - the root module of the server application, expressed as `path/to/file#ClassName`. From 4f744cc66f690892980ce1a5a8a9d71c3c57883c Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Tue, 6 Mar 2018 15:26:05 -0800 Subject: [PATCH 060/604] docs: update RELEASE_SCHEDULE.md by pushing out v6 rc by one week We are pushing RC and Final out by one week because of RxJS v6 complications that are blocking the release. No further delays are currently expected. --- docs/RELEASE_SCHEDULE.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/RELEASE_SCHEDULE.md b/docs/RELEASE_SCHEDULE.md index d873bbc041..4c988528d4 100644 --- a/docs/RELEASE_SCHEDULE.md +++ b/docs/RELEASE_SCHEDULE.md @@ -40,13 +40,14 @@ Week Of | Stable Release
      (@latest npm tag) | Beta/RC Release
      (@n 2018-02-14   | 5.2.5                               | 6.0.0‑beta.4                       | 2018-02-21   | 5.2.6                               | 6.0.0‑beta.5                       | 2018-02-28   | 5.2.7                               | 6.0.0‑beta.6                       | -2018-03-07   | 5.2.8                               | 6.0.0‑rc.0                         | -2018-03-14   | 5.2.9                               | 6.0.0‑rc.1                         | -2018-03-21   | 5.2.10                              | 6.0.0‑rc.2                         | -2018-03-28   | 6.0.0                               | -                                  | Major Release -2018-04-04   | 6.0.1                               | -                                  | -2018-04-11   | 6.0.2                               | -                                  | -2018-04-18   | -                                   | -                                  | [ng-conf](https://www.ng-conf.org/) +2018-03-07   | 5.2.8                               | 6.0.0‑beta.7                       | +2018-03-14   | 5.2.9                               | 6.0.0‑rc.0                         | +2018-03-21   | 5.2.10                              | 6.0.0‑rc.1                         | +2018-03-28   | 5.2.11                              | 6.0.0‑rc.2                         | +2018-04-04   | 6.0.0                               | -                                  | Major Release +2018-04-11   | 6.0.1                               | -                                  | +2018-04-18   | 6.0.2                               | -                                  | [ng-conf](https://www.ng-conf.org/) + ## Tentative Schedule After April 2018 From fcb8c492d6ac1544f1a36fbe23776496f6a556ae Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Wed, 7 Mar 2018 09:04:27 -0800 Subject: [PATCH 061/604] build: add an npm_package rule for @angular/bazel (#22628) PR Close #22628 --- WORKSPACE | 6 +++--- packages/bazel/BUILD.bazel | 24 +++++++++++++++++++++- packages/bazel/src/BUILD.bazel | 9 ++++++++ packages/bazel/src/ng_package/BUILD.bazel | 6 ++++++ packages/bazel/src/ngc-wrapped/BUILD.bazel | 6 ++++++ 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 088edb56e2..a9beb56096 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -2,9 +2,9 @@ workspace(name = "angular") http_archive( name = "build_bazel_rules_nodejs", - url = "https://github.com/bazelbuild/rules_nodejs/archive/0.5.0.zip", - strip_prefix = "rules_nodejs-0.5.0", - sha256 = "06aabb253c3867d51724386ac5622a0a238bbd82e2c70ce1d09ee3ceac4c31d6", + url = "https://github.com/bazelbuild/rules_nodejs/archive/0.5.1.zip", + strip_prefix = "rules_nodejs-0.5.1", + sha256 = "dabd1a596a6f00939875762dcb1de93b5ada0515069244198aa6792bc37bb92a", ) load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories") diff --git a/packages/bazel/BUILD.bazel b/packages/bazel/BUILD.bazel index 00301d3053..6704aeaef2 100644 --- a/packages/bazel/BUILD.bazel +++ b/packages/bazel/BUILD.bazel @@ -1 +1,23 @@ -# Empty marker file, indicating this directory is a Bazel package. +load("@build_bazel_rules_nodejs//:defs.bzl", "npm_package") + +genrule( + name = "workspace", + outs = ["WORKSPACE"], + cmd = "echo 'workspace(name=\"angular\")' > $@", +) + +npm_package( + name = "npm_package", + srcs = [ + "package.json", + "index.bzl", + "//packages/bazel/src:package_assets", + ], + deps = [":workspace"], + # Re-host //packages/bazel/ which is just // in the public distro + replacements = { + "//packages/bazel/": "//", + "angular/packages/bazel/": "angular/", + }, + stamp_data = "//tools:stamp_data", +) diff --git a/packages/bazel/src/BUILD.bazel b/packages/bazel/src/BUILD.bazel index 5234f7f060..661afb4bdf 100644 --- a/packages/bazel/src/BUILD.bazel +++ b/packages/bazel/src/BUILD.bazel @@ -1,5 +1,14 @@ package(default_visibility = ["//visibility:public"]) +filegroup( + name = "package_assets", + srcs = glob(["*"]) + [ + "//packages/bazel/src/ng_package:package_assets", + "//packages/bazel/src/ngc-wrapped:package_assets", + ], + visibility = ["//packages/bazel:__subpackages__"], +) + load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") nodejs_binary( diff --git a/packages/bazel/src/ng_package/BUILD.bazel b/packages/bazel/src/ng_package/BUILD.bazel index 41f2782aff..1080489e22 100644 --- a/packages/bazel/src/ng_package/BUILD.bazel +++ b/packages/bazel/src/ng_package/BUILD.bazel @@ -1,5 +1,11 @@ package(default_visibility = ["//visibility:public"]) +filegroup( + name = "package_assets", + srcs = glob(["*"]), + visibility = ["//packages/bazel:__subpackages__"], +) + load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") load("@build_bazel_rules_typescript//:defs.bzl", "ts_library") diff --git a/packages/bazel/src/ngc-wrapped/BUILD.bazel b/packages/bazel/src/ngc-wrapped/BUILD.bazel index 75b98f6d71..cedd7e5c2e 100644 --- a/packages/bazel/src/ngc-wrapped/BUILD.bazel +++ b/packages/bazel/src/ngc-wrapped/BUILD.bazel @@ -1,6 +1,12 @@ load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") load("@build_bazel_rules_typescript//:defs.bzl", "ts_library") +filegroup( + name = "package_assets", + srcs = glob(["*"]), + visibility = ["//packages/bazel:__subpackages__"], +) + ts_library( name = "ngc_lib", srcs = [ From ce649f725f9987cdef31434fb1ad4a0858ad49d9 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Wed, 7 Mar 2018 09:56:07 -0800 Subject: [PATCH 062/604] build: add a ng_package rule for @angular/router (#22628) PR Close #22628 --- packages/bazel/BUILD.bazel | 4 +- packages/bazel/src/ng_package/ng_package.bzl | 43 +++++++++++++++----- packages/router/BUILD.bazel | 17 +++++++- packages/router/testing/BUILD.bazel | 1 - packages/router/upgrade/BUILD.bazel | 13 ++++++ 5 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 packages/router/upgrade/BUILD.bazel diff --git a/packages/bazel/BUILD.bazel b/packages/bazel/BUILD.bazel index 6704aeaef2..dc6d681df4 100644 --- a/packages/bazel/BUILD.bazel +++ b/packages/bazel/BUILD.bazel @@ -9,15 +9,15 @@ genrule( npm_package( name = "npm_package", srcs = [ - "package.json", "index.bzl", + "package.json", "//packages/bazel/src:package_assets", ], - deps = [":workspace"], # Re-host //packages/bazel/ which is just // in the public distro replacements = { "//packages/bazel/": "//", "angular/packages/bazel/": "angular/", }, stamp_data = "//tools:stamp_data", + deps = [":workspace"], ) diff --git a/packages/bazel/src/ng_package/ng_package.bzl b/packages/bazel/src/ng_package/ng_package.bzl index e4dcb5c4c7..808261a528 100644 --- a/packages/bazel/src/ng_package/ng_package.bzl +++ b/packages/bazel/src/ng_package/ng_package.bzl @@ -18,17 +18,38 @@ WELL_KNOWN_GLOBALS = { "@angular/core": "ng.core", "@angular/common": "ng.common", "@angular/platform-browser": "ng.platformBrowser", - "rxjs/Observable": "Rx", - "rxjs/Observer": "Rx", - "rxjs/Subject": "Rx", - "rxjs/Subscription": "Rx", - "rxjs/observable/merge": "Rx.Observable", - "rxjs/observable/of": "Rx.Observable.prototype", - "rxjs/operator/concatMap": "Rx.Observable.prototype", - "rxjs/operator/filter": "Rx.Observable.prototype", - "rxjs/operator/map": "Rx.Observable.prototype", - "rxjs/operator/share": "Rx.Observable.prototype", } +WELL_KNOWN_GLOBALS.update({"rxjs/%s" % s: "Rx" for s in [ + "BehaviorSubject", + "Observable", + "Observer", + "Subject", + "Subscription", + "util/EmptyError", +]}) +WELL_KNOWN_GLOBALS.update({"rxjs/observable/%s" % s: "Rx.Observable" for s in [ + "from", + "fromPromise", + "forkJoin", + "merge", + "of", +]}) +WELL_KNOWN_GLOBALS.update({"rxjs/operator/%s" % s: "Rx.Observable.prototype" for s in [ + "catch", + "concatAll", + "concatMap", + "every", + "first", + "filter", + "last", + "map", + "mergeAll", + "mergeMap", + "reduce", + "share", + "toPromise", +]}) + def _rollup(ctx, rollup_config, entry_point, inputs, js_output, format = "es"): map_output = ctx.actions.declare_file(js_output.basename + ".map", sibling = js_output) @@ -102,7 +123,7 @@ def _ng_package_impl(ctx): esm5 = [] bundles = [] - for entry_point in [''] + ctx.attr.secondary_entry_points: + for entry_point in [""] + ctx.attr.secondary_entry_points: es2015_entry_point = "/".join([p for p in [ ctx.bin_dir.path, ctx.label.package, diff --git a/packages/router/BUILD.bazel b/packages/router/BUILD.bazel index 87295b81ed..b2a37dfd06 100644 --- a/packages/router/BUILD.bazel +++ b/packages/router/BUILD.bazel @@ -1,6 +1,6 @@ package(default_visibility = ["//visibility:public"]) -load("//tools:defaults.bzl", "ng_module") +load("//tools:defaults.bzl", "ng_module", "ng_package") ng_module( name = "router", @@ -19,3 +19,18 @@ ng_module( "@rxjs", ], ) + +ng_package( + name = "npm_package", + srcs = ["package.json"], + entry_point = "packages/router/index.js", + secondary_entry_points = [ + "testing", + "upgrade", + ], + deps = [ + ":router", + "//packages/router/testing", + "//packages/router/upgrade", + ], +) diff --git a/packages/router/testing/BUILD.bazel b/packages/router/testing/BUILD.bazel index 987d1c8199..4ddca662d1 100644 --- a/packages/router/testing/BUILD.bazel +++ b/packages/router/testing/BUILD.bazel @@ -4,7 +4,6 @@ load("//tools:defaults.bzl", "ng_module") ng_module( name = "testing", - testonly = 1, srcs = glob(["**/*.ts"]), module_name = "@angular/router/testing", deps = [ diff --git a/packages/router/upgrade/BUILD.bazel b/packages/router/upgrade/BUILD.bazel new file mode 100644 index 0000000000..cdafe241ce --- /dev/null +++ b/packages/router/upgrade/BUILD.bazel @@ -0,0 +1,13 @@ +package(default_visibility = ["//visibility:public"]) + +load("//tools:defaults.bzl", "ng_module") + +ng_module( + name = "upgrade", + srcs = glob(["**/*.ts"]), + module_name = "@angular/router/upgrade", + deps = [ + "//packages/core", + "//packages/router", + ], +) From ad8fb8484fed3b9681fe368ff5b8cdcc04127e56 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Wed, 7 Mar 2018 10:11:51 -0800 Subject: [PATCH 063/604] test: include router in public_api_guard tests (#22628) Remove duplicate public api testing from the gulp task. PR Close #22628 --- tools/gulp-tasks/public-api.js | 10 +---- tools/public_api_guard/BUILD.bazel | 62 +++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/tools/gulp-tasks/public-api.js b/tools/gulp-tasks/public-api.js index de39638da4..68520276cc 100644 --- a/tools/gulp-tasks/public-api.js +++ b/tools/gulp-tasks/public-api.js @@ -7,20 +7,15 @@ */ // NOTE: This list shold be in sync with aio/tools/transforms/angular-api-package/index.js +// NOTE: Some packages have moved to Bazel; these are tested in tools/public_api_guard/BUILD.bazel const entrypoints = [ 'dist/packages-dist/animations/animations.d.ts', 'dist/packages-dist/animations/browser.d.ts', 'dist/packages-dist/animations/browser/testing.d.ts', - 'dist/packages-dist/common/common.d.ts', - 'dist/packages-dist/common/testing.d.ts', - 'dist/packages-dist/common/http.d.ts', - 'dist/packages-dist/common/http/testing.d.ts', // The API surface of the compiler is currently unstable - all of the important APIs are exposed // via @angular/core, @angular/platform-browser or @angular/platform-browser-dynamic instead. //'dist/packages-dist/compiler/index.d.ts', //'dist/packages-dist/compiler/testing.d.ts', - 'dist/packages-dist/core/core.d.ts', - 'dist/packages-dist/core/testing.d.ts', 'dist/packages-dist/forms/forms.d.ts', 'dist/packages-dist/http/http.d.ts', 'dist/packages-dist/http/testing.d.ts', @@ -33,9 +28,6 @@ const entrypoints = [ 'dist/packages-dist/platform-webworker-dynamic/platform-webworker-dynamic.d.ts', 'dist/packages-dist/platform-server/platform-server.d.ts', 'dist/packages-dist/platform-server/testing.d.ts', - 'dist/packages-dist/router/router.d.ts', - 'dist/packages-dist/router/testing.d.ts', - 'dist/packages-dist/router/upgrade.d.ts', 'dist/packages-dist/service-worker/service-worker.d.ts', 'dist/packages-dist/service-worker/config.d.ts', 'dist/packages-dist/upgrade/upgrade.d.ts', diff --git a/tools/public_api_guard/BUILD.bazel b/tools/public_api_guard/BUILD.bazel index 1e111c4226..22a1f74f90 100644 --- a/tools/public_api_guard/BUILD.bazel +++ b/tools/public_api_guard/BUILD.bazel @@ -2,25 +2,61 @@ load("//tools/ts-api-guardian:index.bzl", "ts_api_guardian_test") [ ts_api_guardian_test( - name = "%s_api" % i.replace("/", "_"), + name = "%s_%s_api" % ( + bundle[0], + bundle[1].replace("/", "_"), + ), actual = "packages/%s/npm_package/%s.d.ts" % ( - i.split("/")[0], - "/".join(i.split("/")[1:]), + bundle[0], + bundle[1], ), data = glob([ - "%s/**/*.d.ts" % i.split("/")[0], + "%s/**/*.d.ts" % bundle[0], ]) + [ - "//packages/%s:npm_package" % i.split("/")[0], + "//packages/%s:npm_package" % bundle[0], ], - golden = "tools/public_api_guard/%s.d.ts" % i, + golden = "tools/public_api_guard/%s/%s.d.ts" % ( + bundle[0], + bundle[1], + ), ) - for i in [ - "core/core", - "core/testing", - "common/http/testing", - "common/common", - "common/http", - "common/testing", + for bundle in [ + [ + "core", + "core", + ], + [ + "core", + "testing", + ], + [ + "common", + "http/testing", + ], + [ + "common", + "common", + ], + [ + "common", + "http", + ], + [ + "common", + "testing", + ], + [ + "router", + "router", + ], + [ + "router", + "testing", + ], + [ + "router", + "upgrade", + ], # TODO(alexeagle): add remaining packages here once they have ng_package's ] ] From 78167915ee117efd33819ae86cf47b5e15fb60e3 Mon Sep 17 00:00:00 2001 From: Olivier Combe Date: Fri, 24 Nov 2017 19:22:39 +0100 Subject: [PATCH 064/604] build(common): export locale data as commonjs instead of es2015 (#20624) PR Close #20624 --- packages/common/locales/tsconfig-build.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/locales/tsconfig-build.json b/packages/common/locales/tsconfig-build.json index aad872e6b8..5a57417b96 100644 --- a/packages/common/locales/tsconfig-build.json +++ b/packages/common/locales/tsconfig-build.json @@ -4,7 +4,7 @@ "declaration": true, "stripInternal": true, "experimentalDecorators": true, - "module": "es2015", + "module": "commonjs", "moduleResolution": "node", "outDir": "../../../dist/packages/common/locales", "paths": { From ffc6ee00861642948e41be835cb9d7a9a17899d5 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Wed, 7 Mar 2018 14:57:04 -0800 Subject: [PATCH 065/604] release: cut the 6.0.0-beta.7 release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7a5f16d1ce..305b714513 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-srcs", - "version": "6.0.0-beta.6", + "version": "6.0.0-beta.7", "private": true, "branchPattern": "2.0.*", "description": "Angular - a web framework for modern web apps", From 8ad4ae0a0709fad0968ca579b479a55483851f6e Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Wed, 7 Mar 2018 14:59:33 -0800 Subject: [PATCH 066/604] docs: add changelog for 6.0.0-beta.7 --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dddf84efc..77a802aab3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ + +# [6.0.0-beta.7](https://github.com/angular/angular/compare/6.0.0-beta.6...6.0.0-beta.7) (2018-03-07) + + +### Bug Fixes + +* **bazel:** fixes for ng_package on Windows ([#22597](https://github.com/angular/angular/issues/22597)) ([4c40812](https://github.com/angular/angular/commit/4c40812)) +* **compiler:** allow tree-shakeable injectables to depend on string tokens ([#22376](https://github.com/angular/angular/issues/22376)) ([dd53447](https://github.com/angular/angular/commit/dd53447)) +* **router:** fix URL serialization so special characters are only encoded where needed ([#22337](https://github.com/angular/angular/issues/22337)) ([fa974c7](https://github.com/angular/angular/commit/fa974c7)), closes [#10280](https://github.com/angular/angular/issues/10280) + + + + +## [5.2.8](https://github.com/angular/angular/compare/5.2.7...5.2.8) (2018-03-07) + + +### Bug Fixes + +* **platform-server:** generate correct stylings for camel case names ([#22263](https://github.com/angular/angular/issues/22263)) ([de02a7a](https://github.com/angular/angular/commit/de02a7a)), closes [#19235](https://github.com/angular/angular/issues/19235) +* **router:** don't mutate route configs ([#22358](https://github.com/angular/angular/issues/22358)) ([8f0a064](https://github.com/angular/angular/commit/8f0a064)), closes [#22203](https://github.com/angular/angular/issues/22203) +* **router:** fix URL serialization so special characters are only encoded where needed ([#22337](https://github.com/angular/angular/issues/22337)) ([789a47e](https://github.com/angular/angular/commit/789a47e)), closes [#10280](https://github.com/angular/angular/issues/10280) +* **upgrade:** correctly destroy nested downgraded component ([#22400](https://github.com/angular/angular/issues/22400)) ([4aef9de](https://github.com/angular/angular/commit/4aef9de)), closes [#22392](https://github.com/angular/angular/issues/22392) +* **upgrade:** correctly handle `=` bindings in `[@angular](https://github.com/angular)/upgrade` ([#22167](https://github.com/angular/angular/issues/22167)) ([6638390](https://github.com/angular/angular/commit/6638390)) +* **upgrade:** fix empty transclusion content with AngularJS@>=1.5.8 ([#22167](https://github.com/angular/angular/issues/22167)) ([a9a0e27](https://github.com/angular/angular/commit/a9a0e27)), closes [#22175](https://github.com/angular/angular/issues/22175) + + # [6.0.0-beta.6](https://github.com/angular/angular/compare/6.0.0-beta.5...6.0.0-beta.6) (2018-02-28) From 065bcc5aad71c873c53538551518c9ae2992d4dd Mon Sep 17 00:00:00 2001 From: Kapunahele Wong Date: Thu, 22 Feb 2018 11:08:07 -0500 Subject: [PATCH 067/604] docs: add HeroService to code tabs and fix headers (#22373) PR Close #22373 --- aio/content/tutorial/toh-pt5.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/aio/content/tutorial/toh-pt5.md b/aio/content/tutorial/toh-pt5.md index d630c251a5..27eb7eab9d 100644 --- a/aio/content/tutorial/toh-pt5.md +++ b/aio/content/tutorial/toh-pt5.md @@ -15,7 +15,7 @@ When you’re done, users will be able to navigate the app like this: -## Add the _AppRoutingModule_ +## Add the `AppRoutingModule` An Angular best practice is to load and configure the router in a separate, top-level module that is dedicated to routing and imported by the root `AppModule`. @@ -138,7 +138,7 @@ You should see the familiar heroes master/detail view. {@a routerlink} -## Add a navigation link (_routerLink_) +## Add a navigation link (`routerLink`) Users shouldn't have to paste a route URL into the address bar. They should be able to click a link to navigate. @@ -283,7 +283,7 @@ The user should be able to get to these details in three ways. In this section, you'll enable navigation to the `HeroDetailsComponent` and liberate it from the `HeroesComponent`. -### Delete _hero details_ from _HeroesComponent_ +### Delete _hero details_ from `HeroesComponent` When the user clicks a hero item in the `HeroesComponent`, the app should navigate to the `HeroDetailComponent`, @@ -325,7 +325,7 @@ At this point, all application routes are in place. title="src/app/app-routing.module.ts (all routes)"> -### _DashboardComponent_ hero links +### `DashboardComponent` hero links The `DashboardComponent` hero links do nothing at the moment. @@ -343,7 +343,7 @@ to insert the current interation's `hero.id` into each [`routerLink`](#routerlink). {@a heroes-component-links} -### _HeroesComponent_ hero links +### `HeroesComponent` hero links The hero items in the `HeroesComponent` are `
    • ` elements whose click events are bound to the component's `onSelect()` method. @@ -446,7 +446,7 @@ The browser refreshes and the app crashes with a compiler error. `HeroService` doesn't have a `getHero()` method. Add it now. -### Add *HeroService.getHero()* +### Add `HeroService.getHero()` Open `HeroService` and add this `getHero()` method @@ -518,7 +518,7 @@ Here are the code files discussed on this page and your app should look like thi {@a approutingmodule} {@a appmodule} -#### _AppRoutingModule_ and _AppModule_ +#### _AppRoutingModule_, _AppModule_, and _HeroService_ + + {@a appcomponent} @@ -565,6 +569,7 @@ Here are the code files discussed on this page and your app should look like thi {@a heroescomponent} #### _HeroesComponent_ + From 538f1d980f62a2f01ddc87c56c4ddb63a5c4298f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mis=CC=8Cko=20Hevery?= Date: Thu, 1 Mar 2018 13:16:13 -0800 Subject: [PATCH 068/604] refactor(core): move sanitization into core (#22540) This is in preparation of having Ivy have sanitization inline. PR Close #22540 --- packages/core/src/core.ts | 2 +- packages/core/src/core_private_export.ts | 3 + .../core/src/core_render3_private_export.ts | 4 ++ .../src/sanitization}/html_sanitizer.ts | 64 +++++++++++-------- .../src/sanitization}/inert_body.ts | 48 +++++++------- packages/core/src/sanitization/readme.md | 10 +++ .../core/src/{ => sanitization}/security.ts | 0 .../src/sanitization}/style_sanitizer.ts | 4 +- .../src/sanitization}/url_sanitizer.ts | 4 +- packages/core/src/view/element.ts | 2 +- packages/core/src/view/services.ts | 2 +- packages/core/src/view/types.ts | 2 +- .../test/sanitization}/html_sanitizer_spec.ts | 11 ++-- .../sanitization}/style_sanitizer_spec.ts | 10 +-- .../test/sanitization}/url_sanitizer_spec.ts | 10 +-- packages/core/testing/src/render3.ts | 7 ++ .../src/security/dom_sanitization_service.ts | 6 +- 17 files changed, 108 insertions(+), 81 deletions(-) rename packages/{platform-browser/src/security => core/src/sanitization}/html_sanitizer.ts (83%) rename packages/{platform-browser/src/security => core/src/sanitization}/inert_body.ts (79%) create mode 100644 packages/core/src/sanitization/readme.md rename packages/core/src/{ => sanitization}/security.ts (100%) rename packages/{platform-browser/src/security => core/src/sanitization}/style_sanitizer.ts (98%) rename packages/{platform-browser/src/security => core/src/sanitization}/url_sanitizer.ts (95%) rename packages/{platform-browser/test/security => core/test/sanitization}/html_sanitizer_spec.ts (94%) rename packages/{platform-browser/test/security => core/test/sanitization}/style_sanitizer_spec.ts (90%) rename packages/{platform-browser/test/security => core/test/sanitization}/url_sanitizer_spec.ts (93%) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 713c99fe06..eae4219f21 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -33,7 +33,7 @@ export {EventEmitter} from './event_emitter'; export {ErrorHandler} from './error_handler'; export * from './core_private_export'; export * from './core_render3_private_export'; -export {Sanitizer, SecurityContext} from './security'; +export {Sanitizer, SecurityContext} from './sanitization/security'; export * from './codegen_private_exports'; export * from './animation/animation_metadata_wrapped'; import {AnimationTriggerMetadata} from './animation/animation_metadata_wrapped'; diff --git a/packages/core/src/core_private_export.ts b/packages/core/src/core_private_export.ts index 26e714858f..14166b5dfb 100644 --- a/packages/core/src/core_private_export.ts +++ b/packages/core/src/core_private_export.ts @@ -18,6 +18,9 @@ export {CodegenComponentFactoryResolver as ɵCodegenComponentFactoryResolver} fr export {ReflectionCapabilities as ɵReflectionCapabilities} from './reflection/reflection_capabilities'; export {GetterFn as ɵGetterFn, MethodFn as ɵMethodFn, SetterFn as ɵSetterFn} from './reflection/types'; export {DirectRenderer as ɵDirectRenderer, RenderDebugInfo as ɵRenderDebugInfo} from './render/api'; +export {sanitizeHtml as ɵsanitizeHtml} from './sanitization/html_sanitizer'; +export {sanitizeStyle as ɵsanitizeStyle} from './sanitization/style_sanitizer'; +export {sanitizeUrl as ɵsanitizeUrl} from './sanitization/url_sanitizer'; export {global as ɵglobal, looseIdentical as ɵlooseIdentical, stringify as ɵstringify} from './util'; export {makeDecorator as ɵmakeDecorator} from './util/decorators'; export {isObservable as ɵisObservable, isPromise as ɵisPromise} from './util/lang'; diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 41a4a91bce..d13d4b212c 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -72,3 +72,7 @@ export { Pp as ɵPp, } from './render3/index'; // clang-format on + +export {htmlSanitizer as ɵhtmlSanitizer} from './sanitization/html_sanitizer'; +export {styleSanitizer as ɵstyleSanitizer} from './sanitization/style_sanitizer'; +export {urlSanitizer as ɵurlSanitizer, resourceUrlSanitizer as ɵresourceUrlSanitizer} from './sanitization/url_sanitizer'; diff --git a/packages/platform-browser/src/security/html_sanitizer.ts b/packages/core/src/sanitization/html_sanitizer.ts similarity index 83% rename from packages/platform-browser/src/security/html_sanitizer.ts rename to packages/core/src/sanitization/html_sanitizer.ts index fd49067b42..1f60b9e01f 100644 --- a/packages/platform-browser/src/security/html_sanitizer.ts +++ b/packages/core/src/sanitization/html_sanitizer.ts @@ -8,8 +8,6 @@ import {isDevMode} from '@angular/core'; -import {DomAdapter, getDOM} from '../dom/dom_adapter'; - import {InertBodyHelper} from './inert_body'; import {sanitizeSrcset, sanitizeUrl} from './url_sanitizer'; @@ -95,58 +93,61 @@ class SanitizingHtmlSerializer { // because characters were re-encoded. public sanitizedSomething = false; private buf: string[] = []; - private DOM = getDOM(); sanitizeChildren(el: Element): string { // This cannot use a TreeWalker, as it has to run on Angular's various DOM adapters. // However this code never accesses properties off of `document` before deleting its contents // again, so it shouldn't be vulnerable to DOM clobbering. - let current: Node = this.DOM.firstChild(el) !; + let current: Node = el.firstChild !; while (current) { - if (this.DOM.isElementNode(current)) { + if (current.nodeType === Node.ELEMENT_NODE) { this.startElement(current as Element); - } else if (this.DOM.isTextNode(current)) { - this.chars(this.DOM.nodeValue(current) !); + } else if (current.nodeType === Node.TEXT_NODE) { + this.chars(current.nodeValue !); } else { // Strip non-element, non-text nodes. this.sanitizedSomething = true; } - if (this.DOM.firstChild(current)) { - current = this.DOM.firstChild(current) !; + if (current.firstChild) { + current = current.firstChild !; continue; } while (current) { // Leaving the element. Walk up and to the right, closing tags as we go. - if (this.DOM.isElementNode(current)) { + if (current.nodeType === Node.ELEMENT_NODE) { this.endElement(current as Element); } - let next = this.checkClobberedElement(current, this.DOM.nextSibling(current) !); + let next = this.checkClobberedElement(current, current.nextSibling !); if (next) { current = next; break; } - current = this.checkClobberedElement(current, this.DOM.parentElement(current) !); + current = this.checkClobberedElement(current, current.parentNode !); } } return this.buf.join(''); } private startElement(element: Element) { - const tagName = this.DOM.nodeName(element).toLowerCase(); + const tagName = element.nodeName.toLowerCase(); if (!VALID_ELEMENTS.hasOwnProperty(tagName)) { this.sanitizedSomething = true; return; } this.buf.push('<'); this.buf.push(tagName); - this.DOM.attributeMap(element).forEach((value: string, attrName: string) => { + const elAttrs = element.attributes; + for (let i = 0; i < elAttrs.length; i++) { + const elAttr = elAttrs.item(i); + const attrName = elAttr.name; + let value = elAttr.value; const lower = attrName.toLowerCase(); if (!VALID_ATTRS.hasOwnProperty(lower)) { this.sanitizedSomething = true; - return; + continue; } // TODO(martinprobst): Special case image URIs for data:image/... if (URI_ATTRS[lower]) value = sanitizeUrl(value); @@ -156,12 +157,12 @@ class SanitizingHtmlSerializer { this.buf.push('="'); this.buf.push(encodeEntities(value)); this.buf.push('"'); - }); + }; this.buf.push('>'); } private endElement(current: Element) { - const tagName = this.DOM.nodeName(current).toLowerCase(); + const tagName = current.nodeName.toLowerCase(); if (VALID_ELEMENTS.hasOwnProperty(tagName) && !VOID_ELEMENTS.hasOwnProperty(tagName)) { this.buf.push('el).content : null; +} +function isTemplateElement(el: Node): boolean { + return el.nodeType === Node.ELEMENT_NODE && el.nodeName === 'TEMPLATE'; +} diff --git a/packages/platform-browser/src/security/inert_body.ts b/packages/core/src/sanitization/inert_body.ts similarity index 79% rename from packages/platform-browser/src/security/inert_body.ts rename to packages/core/src/sanitization/inert_body.ts index d4cb58fb45..85463c7c9d 100644 --- a/packages/platform-browser/src/security/inert_body.ts +++ b/packages/core/src/sanitization/inert_body.ts @@ -6,8 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {DomAdapter, getDOM} from '../dom/dom_adapter'; - /** * This helper class is used to get hold of an inert tree of DOM elements containing dirty HTML * that needs sanitizing. @@ -18,22 +16,22 @@ import {DomAdapter, getDOM} from '../dom/dom_adapter'; */ export class InertBodyHelper { private inertBodyElement: HTMLElement; + private inertDocument: Document; - constructor(private defaultDoc: any, private DOM: DomAdapter) { - const inertDocument = this.DOM.createHtmlDocument(); - this.inertBodyElement = inertDocument.body; + constructor(private defaultDoc: Document) { + this.inertDocument = this.defaultDoc.implementation.createHTMLDocument('sanitization-inert'); + this.inertBodyElement = this.inertDocument.body; if (this.inertBodyElement == null) { // usually there should be only one body element in the document, but IE doesn't have any, so // we need to create one. - const inertHtml = this.DOM.createElement('html', inertDocument); - this.inertBodyElement = this.DOM.createElement('body', inertDocument); - this.DOM.appendChild(inertHtml, this.inertBodyElement); - this.DOM.appendChild(inertDocument, inertHtml); + const inertHtml = this.inertDocument.createElement('html'); + this.inertDocument.appendChild(inertHtml); + this.inertBodyElement = this.inertDocument.createElement('body'); + inertHtml.appendChild(this.inertBodyElement); } - this.DOM.setInnerHTML( - this.inertBodyElement, ''); + this.inertBodyElement.innerHTML = ''; if (this.inertBodyElement.querySelector && !this.inertBodyElement.querySelector('svg')) { // We just hit the Safari 10.1 bug - which allows JS to run inside the SVG G element // so use the XHR strategy. @@ -41,8 +39,8 @@ export class InertBodyHelper { return; } - this.DOM.setInnerHTML( - this.inertBodyElement, '

      '); + this.inertBodyElement.innerHTML = + '

      '; if (this.inertBodyElement.querySelector && this.inertBodyElement.querySelector('svg img')) { // We just hit the Firefox bug - which prevents the inner img JS from being sanitized // so use the DOMParser strategy, if it is available. @@ -118,17 +116,17 @@ export class InertBodyHelper { */ private getInertBodyElement_InertDocument(html: string) { // Prefer using

    • - ObservableEvents API
      ObservableEvents API
      Creation & cancellation @@ -203,10 +204,11 @@ button.removeEventListener(‘click’, handler); An observable produces values over time. An array is created as a static set of values. In a sense, observables are asynchronous where arrays are synchronous. In the following examples, ➞ implies asynchronous value delivery. - - - + + + + + From 2c2b62f45f29e7658028d85be5a26db812c0525d Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Thu, 1 Mar 2018 09:46:39 -0800 Subject: [PATCH 047/604] fix(ivy): preventDefault when listener returns false (#22529) Closes #22495 PR Close #22529 --- packages/core/src/render3/instructions.ts | 31 +++++-- .../compiler_canonical/elements_spec.ts | 38 +++++++++ packages/core/test/render3/listeners_spec.ts | 82 +++++++++++++++++-- packages/core/test/render3/outputs_spec.ts | 28 +++---- 4 files changed, 151 insertions(+), 28 deletions(-) diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 04f545ba80..ef68c99250 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -619,22 +619,24 @@ export function hostElement(rNode: RElement | null, def: ComponentDef): LEl * and saves the subscription for later cleanup. * * @param eventName Name of the event - * @param listener The function to be called when event emits + * @param listenerFn The function to be called when event emits * @param useCapture Whether or not to use capture in event listener. */ -export function listener(eventName: string, listener: EventListener, useCapture = false): void { +export function listener( + eventName: string, listenerFn: (e?: any) => any, useCapture = false): void { ngDevMode && assertPreviousIsParent(); const node = previousOrParentNode; const native = node.native as RElement; - const wrappedListener = wrapListenerWithDirtyLogic(currentView, listener); // In order to match current behavior, native DOM event listeners must be added for all // events (including outputs). const cleanupFns = cleanup || (cleanup = currentView.cleanup = []); if (isProceduralRenderer(renderer)) { + const wrappedListener = wrapListenerWithDirtyLogic(currentView, listenerFn); const cleanupFn = renderer.listen(native, eventName, wrappedListener); cleanupFns.push(cleanupFn, null); } else { + const wrappedListener = wrapListenerWithDirtyAndDefault(currentView, listenerFn); native.addEventListener(eventName, wrappedListener, useCapture); cleanupFns.push(eventName, native, wrappedListener, useCapture); } @@ -649,7 +651,7 @@ export function listener(eventName: string, listener: EventListener, useCapture const outputs = tNode.outputs; let outputData: PropertyAliasValue|undefined; if (outputs && (outputData = outputs[eventName])) { - createOutput(outputData, listener); + createOutput(outputData, listenerFn); } } @@ -1437,10 +1439,27 @@ export function markDirtyIfOnPush(node: LElementNode): void { * Wraps an event listener so its host view and its ancestor views will be marked dirty * whenever the event fires. Necessary to support OnPush components. */ -export function wrapListenerWithDirtyLogic(view: LView, listener: EventListener): EventListener { +export function wrapListenerWithDirtyLogic(view: LView, listenerFn: (e?: any) => any): (e: Event) => + any { + return function(e: any) { + markViewDirty(view); + return listenerFn(e); + }; +} + +/** + * Wraps an event listener so its host view and its ancestor views will be marked dirty + * whenever the event fires. Also wraps with preventDefault behavior. + */ +export function wrapListenerWithDirtyAndDefault( + view: LView, listenerFn: (e?: any) => any): EventListener { return function(e: Event) { markViewDirty(view); - listener(e); + if (listenerFn(e) === false) { + e.preventDefault(); + // Necessary for legacy browsers that don't support preventDefault (e.g. IE) + e.returnValue = false; + } }; } diff --git a/packages/core/test/render3/compiler_canonical/elements_spec.ts b/packages/core/test/render3/compiler_canonical/elements_spec.ts index e626248543..7b13dbd1fa 100644 --- a/packages/core/test/render3/compiler_canonical/elements_spec.ts +++ b/packages/core/test/render3/compiler_canonical/elements_spec.ts @@ -51,4 +51,42 @@ describe('elements', () => { expect(toHtml(renderComponent(MyComponent))) .toEqual('
      Hello World!
      '); }); + + it('should support listeners', () => { + type $ListenerComp$ = ListenerComp; + + @Component({ + selector: 'listener-comp', + template: + `` + }) + class ListenerComp { + onClick() {} + onPress(e: Event) {} + onPress2(e: Event) {} + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: ListenerComp, + tag: 'listener-comp', + factory: function ListenerComp_Factory() { return new ListenerComp(); }, + template: function ListenerComp_Template(ctx: $ListenerComp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'button'); + $r3$.ɵL('click', function ListenerComp_click_Handler() { return ctx.onClick(); }); + $r3$.ɵL('keypress', function ListenerComp_keypress_Handler($event: $any$) { + ctx.onPress($event); + return ctx.onPress2($event); + }); + $r3$.ɵT(1, 'Click'); + $r3$.ɵe(); + } + } + }); + // /NORMATIVE + } + + const listenerComp = renderComponent(ListenerComp); + expect(toHtml(listenerComp)).toEqual(''); + }); }); diff --git a/packages/core/test/render3/listeners_spec.ts b/packages/core/test/render3/listeners_spec.ts index 83e557e311..406a1eb040 100644 --- a/packages/core/test/render3/listeners_spec.ts +++ b/packages/core/test/render3/listeners_spec.ts @@ -9,6 +9,7 @@ import {defineComponent, defineDirective} from '../../src/render3/index'; import {container, containerRefreshEnd, containerRefreshStart, directiveRefresh, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, listener, text} from '../../src/render3/instructions'; +import {getRendererFactory2} from './imported_renderer2'; import {containerEl, renderComponent, renderToHtml} from './render_util'; @@ -29,7 +30,7 @@ describe('event listeners', () => { if (cm) { elementStart(0, 'button'); { - listener('click', function() { ctx.onClick(); }); + listener('click', function() { return ctx.onClick(); }); text(1, 'Click me'); } elementEnd(); @@ -43,6 +44,39 @@ describe('event listeners', () => { }); } + class PreventDefaultComp { + handlerReturnValue: any = true; + event: Event; + + onClick(e: any) { + this.event = e; + + // stub preventDefault() to check whether it's called + Object.defineProperty( + this.event, 'preventDefault', + {value: jasmine.createSpy('preventDefault'), writable: true}); + + return this.handlerReturnValue; + } + + static ngComponentDef = defineComponent({ + type: PreventDefaultComp, + tag: 'prevent-default-comp', + factory: () => new PreventDefaultComp(), + /** */ + template: (ctx: PreventDefaultComp, cm: boolean) => { + if (cm) { + elementStart(0, 'button'); + { + listener('click', function($event: any) { return ctx.onClick($event); }); + text(1, 'Click'); + } + elementEnd(); + } + } + }); + } + beforeEach(() => { comps = []; }); it('should call function on event emit', () => { @@ -55,6 +89,38 @@ describe('event listeners', () => { expect(comp.counter).toEqual(2); }); + it('should retain event handler return values using document', () => { + const preventDefaultComp = renderComponent(PreventDefaultComp); + const button = containerEl.querySelector('button') !; + + button.click(); + expect(preventDefaultComp.event !.preventDefault).not.toHaveBeenCalled(); + + preventDefaultComp.handlerReturnValue = undefined; + button.click(); + expect(preventDefaultComp.event !.preventDefault).not.toHaveBeenCalled(); + + preventDefaultComp.handlerReturnValue = false; + button.click(); + expect(preventDefaultComp.event !.preventDefault).toHaveBeenCalled(); + }); + + it('should retain event handler return values with renderer2', () => { + const preventDefaultComp = renderComponent(PreventDefaultComp, getRendererFactory2(document)); + const button = containerEl.querySelector('button') !; + + button.click(); + expect(preventDefaultComp.event !.preventDefault).not.toHaveBeenCalled(); + + preventDefaultComp.handlerReturnValue = undefined; + button.click(); + expect(preventDefaultComp.event !.preventDefault).not.toHaveBeenCalled(); + + preventDefaultComp.handlerReturnValue = false; + button.click(); + expect(preventDefaultComp.event !.preventDefault).toHaveBeenCalled(); + }); + it('should call function chain on event emit', () => { /** */ function Template(ctx: any, cm: boolean) { @@ -63,7 +129,7 @@ describe('event listeners', () => { { listener('click', function() { ctx.onClick(); - ctx.onClick2(); + return ctx.onClick2(); }); text(1, 'Click me'); } @@ -96,7 +162,7 @@ describe('event listeners', () => { if (cm) { elementStart(0, 'button'); { - listener('click', function() { ctx.showing = !ctx.showing; }); + listener('click', function() { return ctx.showing = !ctx.showing; }); text(1, 'Click me'); } elementEnd(); @@ -131,7 +197,7 @@ describe('event listeners', () => { if (embeddedViewStart(1)) { elementStart(0, 'button'); { - listener('click', function() { ctx.onClick(); }); + listener('click', function() { return ctx.onClick(); }); text(1, 'Click me'); } elementEnd(); @@ -170,7 +236,7 @@ describe('event listeners', () => { type: HostListenerDir, factory: function HostListenerDir_Factory() { const $dir$ = new HostListenerDir(); - listener('click', function() { $dir$.onClick(); }); + listener('click', function() { return $dir$.onClick(); }); return $dir$; }, }); @@ -222,7 +288,7 @@ describe('event listeners', () => { if (embeddedViewStart(0)) { elementStart(0, 'button'); { - listener('click', function() { ctx.onClick(); }); + listener('click', function() { return ctx.onClick(); }); text(1, 'Click'); } elementEnd(); @@ -337,7 +403,7 @@ describe('event listeners', () => { if (embeddedViewStart(0)) { elementStart(0, 'button'); { - listener('click', function() { ctx.counter1++; }); + listener('click', function() { return ctx.counter1++; }); text(1, 'Click'); } elementEnd(); @@ -352,7 +418,7 @@ describe('event listeners', () => { if (embeddedViewStart(0)) { elementStart(0, 'button'); { - listener('click', function() { ctx.counter2++; }); + listener('click', function() { return ctx.counter2++; }); text(1, 'Click'); } elementEnd(); diff --git a/packages/core/test/render3/outputs_spec.ts b/packages/core/test/render3/outputs_spec.ts index 78a9ae675c..96ee81e5c0 100644 --- a/packages/core/test/render3/outputs_spec.ts +++ b/packages/core/test/render3/outputs_spec.ts @@ -47,7 +47,7 @@ describe('outputs', () => { if (cm) { elementStart(0, ButtonToggle); { - listener('change', function() { ctx.onChange(); }); + listener('change', function() { return ctx.onChange(); }); } elementEnd(); } @@ -72,8 +72,8 @@ describe('outputs', () => { if (cm) { elementStart(0, ButtonToggle); { - listener('change', function() { ctx.onChange(); }); - listener('reset', function() { ctx.onReset(); }); + listener('change', function() { return ctx.onChange(); }); + listener('reset', function() { return ctx.onReset(); }); } elementEnd(); } @@ -99,7 +99,7 @@ describe('outputs', () => { if (cm) { elementStart(0, ButtonToggle); { - listener('change', function() { ctx.counter++; }); + listener('change', function() { return ctx.counter++; }); } elementEnd(); } @@ -135,7 +135,7 @@ describe('outputs', () => { if (embeddedViewStart(0)) { elementStart(0, ButtonToggle); { - listener('change', function() { ctx.onChange(); }); + listener('change', function() { return ctx.onChange(); }); } elementEnd(); } @@ -187,7 +187,7 @@ describe('outputs', () => { if (embeddedViewStart(0)) { elementStart(0, ButtonToggle); { - listener('change', function() { ctx.onChange(); }); + listener('change', function() { return ctx.onChange(); }); } elementEnd(); } @@ -249,13 +249,13 @@ describe('outputs', () => { if (embeddedViewStart(0)) { elementStart(0, 'button'); { - listener('click', function() { ctx.onClick(); }); + listener('click', function() { return ctx.onClick(); }); text(1, 'Click me'); } elementEnd(); elementStart(2, ButtonToggle); { - listener('change', function() { ctx.onChange(); }); + listener('change', function() { return ctx.onChange(); }); } elementEnd(); elementStart(4, DestroyComp); @@ -311,7 +311,7 @@ describe('outputs', () => { if (cm) { elementStart(0, 'button', null, [MyButton]); { - listener('click', function() { ctx.onClick(); }); + listener('click', function() { return ctx.onClick(); }); } elementEnd(); } @@ -336,7 +336,7 @@ describe('outputs', () => { if (cm) { elementStart(0, ButtonToggle, null, [OtherDir]); { - listener('change', function() { ctx.onChange(); }); + listener('change', function() { return ctx.onChange(); }); } elementEnd(); } @@ -369,7 +369,7 @@ describe('outputs', () => { if (cm) { elementStart(0, ButtonToggle, null, [OtherDir]); { - listener('change', function() { ctx.onChange(); }); + listener('change', function() { return ctx.onChange(); }); } elementEnd(); } @@ -403,7 +403,7 @@ describe('outputs', () => { if (cm) { elementStart(0, 'button'); { - listener('click', function() { ctx.onClick(); }); + listener('click', function() { return ctx.onClick(); }); text(1, 'Click me'); } elementEnd(); @@ -415,7 +415,7 @@ describe('outputs', () => { if (embeddedViewStart(0)) { elementStart(0, ButtonToggle); { - listener('change', function() { ctx.onChange(); }); + listener('change', function() { return ctx.onChange(); }); } elementEnd(); } @@ -426,7 +426,7 @@ describe('outputs', () => { if (embeddedViewStart(1)) { elementStart(0, 'div', null, [OtherDir]); { - listener('change', function() { ctx.onChange(); }); + listener('change', function() { return ctx.onChange(); }); } elementEnd(); } From 2a1e3d191fb8675b118b18a92e84ddd5d4824c36 Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Sat, 3 Mar 2018 08:04:48 -0800 Subject: [PATCH 048/604] docs: add info about the WebStorm plugin to BAZEL.md (#22572) PR Close #22572 --- docs/BAZEL.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/BAZEL.md b/docs/BAZEL.md index f5e3481a93..ddc0b140f2 100644 --- a/docs/BAZEL.md +++ b/docs/BAZEL.md @@ -173,6 +173,11 @@ Contact Alex Eagle with questions. ## Known issues +### Webstorm + +The autocompletion in WebStorm can be added via a Bazel plugin intended for IntelliJ IDEA, but the plugin needs to be installed in a special way. +See [bazelbuild/intellij#246](https://github.com/bazelbuild/intellij/issues/246) for more info. + ### Xcode If you see the following error: From 53b0fe814480e008df8aa2318f3648a8d8e26654 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Sat, 3 Mar 2018 13:12:08 +0000 Subject: [PATCH 049/604] feat(aio): allow template to position embedded ToC (#22570) Previously the doc-viewer would insert an embedded `` element into the DOM directly after the H1 element. Now it will not do this if there is already a such element in the doc contents. This allows the content-author/template-developer to position the ToC for specific cases. PR Close #22570 --- .../doc-viewer/doc-viewer.component.spec.ts | 52 +++++++++++++------ .../layout/doc-viewer/doc-viewer.component.ts | 11 ++-- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts b/aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts index aa8d6eade0..182a8c5058 100644 --- a/aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts +++ b/aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts @@ -175,6 +175,9 @@ describe('DocViewerComponent', () => { const DOC_WITHOUT_H1 = 'Some content'; const DOC_WITH_H1 = '

      Features

      Some content'; const DOC_WITH_NO_TOC_H1 = '

      Features

      Some content'; + const DOC_WITH_EMBEDDED_TOC = '

      Features

      Some content'; + const DOC_WITH_EMBEDDED_TOC_WITHOUT_H1 = 'Some content'; + const DOC_WITH_EMBEDDED_TOC_WITH_NO_TOC_H1 = 'Some content'; const DOC_WITH_HIDDEN_H1_CONTENT = '

      linkFeatures

      Some content'; let titleService: MockTitle; let tocService: MockTocService; @@ -271,26 +274,45 @@ describe('DocViewerComponent', () => { }); describe('(ToC)', () => { - it('should add an embedded ToC element if there is an `

      ` heading', () => { - doPrepareTitleAndToc(DOC_WITH_H1); - const tocEl = getTocEl()!; + describe('needed', () => { + it('should add an embedded ToC element if there is an `

      ` heading', () => { + doPrepareTitleAndToc(DOC_WITH_H1); + const tocEl = getTocEl()!; - expect(tocEl).toBeTruthy(); - expect(tocEl.classList.contains('embedded')).toBe(true); + expect(tocEl).toBeTruthy(); + expect(tocEl.classList.contains('embedded')).toBe(true); + }); + + it('should not add a second ToC element if there a hard coded one in place', () => { + doPrepareTitleAndToc(DOC_WITH_EMBEDDED_TOC); + expect(targetEl.querySelectorAll('aio-toc').length).toEqual(1); + }); }); - it('should not add a ToC element if there is a `.no-toc` `

      ` heading', () => { - doPrepareTitleAndToc(DOC_WITH_NO_TOC_H1); - expect(getTocEl()).toBeFalsy(); + + describe('not needed', () => { + it('should not add a ToC element if there is a `.no-toc` `

      ` heading', () => { + doPrepareTitleAndToc(DOC_WITH_NO_TOC_H1); + expect(getTocEl()).toBeFalsy(); + }); + + it('should not add a ToC element if there is no `

      ` heading', () => { + doPrepareTitleAndToc(DOC_WITHOUT_H1); + expect(getTocEl()).toBeFalsy(); + + doPrepareTitleAndToc(EMPTY_DOC); + expect(getTocEl()).toBeFalsy(); + }); + + it('should remove ToC a hard coded one', () => { + doPrepareTitleAndToc(DOC_WITH_EMBEDDED_TOC_WITHOUT_H1); + expect(getTocEl()).toBeFalsy(); + + doPrepareTitleAndToc(DOC_WITH_EMBEDDED_TOC_WITH_NO_TOC_H1); + expect(getTocEl()).toBeFalsy(); + }); }); - it('should not add a ToC element if there is no `

      ` heading', () => { - doPrepareTitleAndToc(DOC_WITHOUT_H1); - expect(getTocEl()).toBeFalsy(); - - doPrepareTitleAndToc(EMPTY_DOC); - expect(getTocEl()).toBeFalsy(); - }); it('should generate ToC entries if there is an `

      ` heading', () => { doAddTitleAndToc(DOC_WITH_H1, 'foo'); diff --git a/aio/src/app/layout/doc-viewer/doc-viewer.component.ts b/aio/src/app/layout/doc-viewer/doc-viewer.component.ts index 7ead74d128..dce991eae3 100644 --- a/aio/src/app/layout/doc-viewer/doc-viewer.component.ts +++ b/aio/src/app/layout/doc-viewer/doc-viewer.component.ts @@ -112,10 +112,15 @@ export class DocViewerComponent implements DoCheck, OnDestroy { */ protected prepareTitleAndToc(targetElem: HTMLElement, docId: string): () => void { const titleEl = targetElem.querySelector('h1'); - const hasToc = !!titleEl && !/no-?toc/i.test(titleEl.className); + const needsToc = !!titleEl && !/no-?toc/i.test(titleEl.className); + const embeddedToc = targetElem.querySelector('aio-toc.embedded'); - if (hasToc) { + if (needsToc && !embeddedToc) { + // Add an embedded ToC if it's needed and there isn't one in the content already. titleEl!.insertAdjacentHTML('afterend', ''); + } else if (!needsToc && embeddedToc) { + // Remove the embedded Toc if it's there and not needed. + embeddedToc.remove(); } return () => { @@ -127,7 +132,7 @@ export class DocViewerComponent implements DoCheck, OnDestroy { if (titleEl) { title = (typeof titleEl.innerText === 'string') ? titleEl.innerText : titleEl.textContent; - if (hasToc) { + if (needsToc) { this.tocService.genToc(targetElem, docId); } } From 8ea4c57174d555349cd5f1803bbeca472d498a43 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Sat, 3 Mar 2018 13:13:33 +0000 Subject: [PATCH 050/604] fix(aio): reposition and shrink the API badges (#22570) Closes #22130 PR Close #22570 --- aio/src/styles/2-modules/_api-pages.scss | 10 ++++++++++ aio/src/styles/2-modules/_heading-anchors.scss | 2 +- aio/src/styles/2-modules/_label.scss | 8 ++++++++ aio/tools/transforms/templates/api/base.template.html | 6 +++--- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/aio/src/styles/2-modules/_api-pages.scss b/aio/src/styles/2-modules/_api-pages.scss index f25126d2db..7bfdb19556 100644 --- a/aio/src/styles/2-modules/_api-pages.scss +++ b/aio/src/styles/2-modules/_api-pages.scss @@ -10,6 +10,16 @@ } } +.api-header { + display: flex; + align-items: center; + + @media screen and (max-width: 600px) { + flex-direction: column; + align-items: flex-start; + } +} + .api-body { .class-overview { diff --git a/aio/src/styles/2-modules/_heading-anchors.scss b/aio/src/styles/2-modules/_heading-anchors.scss index e3a169fae4..cbf88cf111 100644 --- a/aio/src/styles/2-modules/_heading-anchors.scss +++ b/aio/src/styles/2-modules/_heading-anchors.scss @@ -3,7 +3,7 @@ .header-link { color: $mediumgray; - margin-left: 8px; + margin: 0 4px; text-decoration: none; user-select: none; visibility: hidden; diff --git a/aio/src/styles/2-modules/_label.scss b/aio/src/styles/2-modules/_label.scss index 6efd06cb64..45d225fa8a 100644 --- a/aio/src/styles/2-modules/_label.scss +++ b/aio/src/styles/2-modules/_label.scss @@ -38,6 +38,14 @@ label.raised, .api-header label { .api-header label { + // The API badges should be a little smaller + padding: 2px 10px; + font-size: 12px; + + @media screen and (max-width: 600px) { + margin: 4px 0; + } + &.api-status-label { background-color: $mediumgray; } diff --git a/aio/tools/transforms/templates/api/base.template.html b/aio/tools/transforms/templates/api/base.template.html index 63f6490cab..69514248fd 100644 --- a/aio/tools/transforms/templates/api/base.template.html +++ b/aio/tools/transforms/templates/api/base.template.html @@ -20,13 +20,13 @@ {% for crumb in doc.breadCrumbs %}{% if not loop.last %} {$ slash() $} {% if crumb.path %}{$ crumb.text $}{% else %}{$ crumb.text $}{% endif %}{% endif %}{% endfor %}
      +

      {$ doc.name $}

      + {% if doc.deprecated !== undefined %}{% endif %} {% if doc.experimental !== undefined %}{% endif %} {% if doc.stable !== undefined %}{% endif %} - -

      {$ doc.name $}

      - {$ version $}
      +
      {% block body %}{% endblock %} From 94bc277b1bc7c91e2db72e013d7b90e99f6043c1 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Sat, 3 Mar 2018 13:14:28 +0000 Subject: [PATCH 051/604] fix(aio): tidy up embedded ToC styling (#22570) The spacing didn't work well when the screen was narrow. PR Close #22570 --- aio/src/styles/2-modules/_toc.scss | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/aio/src/styles/2-modules/_toc.scss b/aio/src/styles/2-modules/_toc.scss index 028f839b67..a03b1bb698 100644 --- a/aio/src/styles/2-modules/_toc.scss +++ b/aio/src/styles/2-modules/_toc.scss @@ -8,10 +8,16 @@ overflow-x: hidden; } -aio-toc { - &.embedded { - @media (min-width: 801px) { - display: none; +aio-toc.embedded { + @media (min-width: 801px) { + display: none; + } + + .toc-inner { + padding: 12px 0 0 0; + + .toc-heading { + margin: 0 0 8px; } } } From f86d8ae0fd95240d5857091981eac2b650d34754 Mon Sep 17 00:00:00 2001 From: Suguru Inatomi Date: Sun, 4 Mar 2018 14:01:36 +0900 Subject: [PATCH 052/604] docs(compiler): fix a line about ivy library (#22579) PR Close #22579 --- packages/compiler/design/separate_compilation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compiler/design/separate_compilation.md b/packages/compiler/design/separate_compilation.md index 29e81694a4..2b51c7e4f1 100644 --- a/packages/compiler/design/separate_compilation.md +++ b/packages/compiler/design/separate_compilation.md @@ -715,7 +715,7 @@ To produce an ivy application the options would look like, ##### example - ivy library -To produce an ivy application the options would look like, +To produce an ivy library the options would look like, ```json { From 2c75acc5b327e43aa297b86a82ad28a6167d9350 Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Wed, 28 Feb 2018 15:00:58 +0100 Subject: [PATCH 053/604] feat(ivy): add support for the ngProjectAs attribute (#22498) PR Close #22498 --- packages/core/src/render3/instructions.ts | 30 ++++- .../core/src/render3/interfaces/projection.ts | 2 + .../core/src/render3/node_selector_matcher.ts | 29 +++- .../content_projection_spec.ts | 7 +- packages/core/test/render3/content_spec.ts | 127 ++++++++++++++++-- .../render3/node_selector_matcher_spec.ts | 26 +++- 6 files changed, 197 insertions(+), 24 deletions(-) diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index ef68c99250..ebe31d590e 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -11,7 +11,7 @@ import './ng_dev_mode'; import {assertEqual, assertLessThan, assertNotEqual, assertNotNull, assertNull, assertSame} from './assert'; import {LContainer, TContainer} from './interfaces/container'; import {LInjector} from './interfaces/injector'; -import {CssSelector, LProjection} from './interfaces/projection'; +import {CssSelector, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; import {LQueries} from './interfaces/query'; import {LView, LViewFlags, LifecycleStage, RootContext, TData, TView} from './interfaces/view'; @@ -560,8 +560,12 @@ function setUpAttributes(native: RElement, attrs: string[]): void { const isProc = isProceduralRenderer(renderer); for (let i = 0; i < attrs.length; i += 2) { - isProc ? (renderer as ProceduralRenderer3).setAttribute(native, attrs[i], attrs[i | 1]) : - native.setAttribute(attrs[i], attrs[i | 1]); + const attrName = attrs[i]; + if (attrName !== NG_PROJECT_AS_ATTR_NAME) { + const attrVal = attrs[i + 1]; + isProc ? (renderer as ProceduralRenderer3).setAttribute(native, attrName, attrVal) : + native.setAttribute(attrName, attrVal); + } } } @@ -1279,9 +1283,23 @@ export function directiveRefresh(directiveIndex: number, elementIndex: number * each projected node belongs (it re-distributes nodes among "buckets" where each "bucket" is * backed by a selector). * - * @param selectors + * This function requires CSS selectors to be provided in 2 forms: parsed (by a compiler) and text, + * un-parsed form. + * + * The parsed form is needed for efficient matching of a node against a given CSS selector. + * The un-parsed, textual form is needed for support of the ngProjectAs attribute. + * + * Having a CSS selector in 2 different formats is not ideal, but alternatives have even more + * drawbacks: + * - having only a textual form would require runtime parsing of CSS selectors; + * - we can't have only a parsed as we can't re-construct textual form from it (as entered by a + * template author). + * + * @param selectors A collection of parsed CSS selectors + * @param rawSelectors A collection of CSS selectors in the raw, un-parsed form */ -export function projectionDef(index: number, selectors?: CssSelector[]): void { +export function projectionDef( + index: number, selectors?: CssSelector[], textSelectors?: string[]): void { const noOfNodeBuckets = selectors ? selectors.length + 1 : 1; const distributedNodes = new Array(noOfNodeBuckets); for (let i = 0; i < noOfNodeBuckets; i++) { @@ -1296,7 +1314,7 @@ export function projectionDef(index: number, selectors?: CssSelector[]): void { // - there are selectors defined // - a node has a tag name / attributes that can be matched if (selectors && componentChild.tNode) { - const matchedIdx = matchingSelectorIndex(componentChild.tNode, selectors !); + const matchedIdx = matchingSelectorIndex(componentChild.tNode, selectors, textSelectors !); distributedNodes[matchedIdx].push(componentChild); } else { distributedNodes[0].push(componentChild); diff --git a/packages/core/src/render3/interfaces/projection.ts b/packages/core/src/render3/interfaces/projection.ts index 87e44c933d..37d5c3ef96 100644 --- a/packages/core/src/render3/interfaces/projection.ts +++ b/packages/core/src/render3/interfaces/projection.ts @@ -45,6 +45,8 @@ export type CssSelectorWithNegations = [SimpleCssSelector | null, SimpleCssSelec */ export type CssSelector = CssSelectorWithNegations[]; +export const NG_PROJECT_AS_ATTR_NAME = 'ngProjectAs'; + // Note: This hack is necessary so we don't erroneously get a circular dependency // failure based on types. export const unusedValueExportToPlacateAjd = 1; diff --git a/packages/core/src/render3/node_selector_matcher.ts b/packages/core/src/render3/node_selector_matcher.ts index b1a508d85f..51b0a7974f 100644 --- a/packages/core/src/render3/node_selector_matcher.ts +++ b/packages/core/src/render3/node_selector_matcher.ts @@ -10,7 +10,7 @@ import './ng_dev_mode'; import {assertNotNull} from './assert'; import {TNode, unusedValueExportToPlacateAjd as unused1} from './interfaces/node'; -import {CssSelector, CssSelectorWithNegations, SimpleCssSelector, unusedValueExportToPlacateAjd as unused2} from './interfaces/projection'; +import {CssSelector, CssSelectorWithNegations, NG_PROJECT_AS_ATTR_NAME, SimpleCssSelector, unusedValueExportToPlacateAjd as unused2} from './interfaces/projection'; const unusedValueToPlacateAjd = unused1 + unused2; @@ -115,13 +115,34 @@ export function isNodeMatchingSelector(tNode: TNode, selector: CssSelector): boo return false; } +export function getProjectAsAttrValue(tNode: TNode): string|null { + const nodeAttrs = tNode.attrs; + if (nodeAttrs != null) { + const ngProjectAsAttrIdx = nodeAttrs.indexOf(NG_PROJECT_AS_ATTR_NAME); + // only check for ngProjectAs in attribute names, don't accidentally match attribute's value + // (attribute names are stored at even indexes) + if ((ngProjectAsAttrIdx & 1) === 0) { + return nodeAttrs[ngProjectAsAttrIdx + 1]; + } + } + return null; +} + /** * Checks a given node against matching selectors and returns - * selector index (or 0 if none matched); + * selector index (or 0 if none matched). + * + * This function takes into account the ngProjectAs attribute: if present its value will be compared + * to the raw (un-parsed) CSS selector instead of using standard selector matching logic. */ -export function matchingSelectorIndex(tNode: TNode, selectors: CssSelector[]): number { +export function matchingSelectorIndex( + tNode: TNode, selectors: CssSelector[], textSelectors: string[]): number { + const ngProjectAsAttrVal = getProjectAsAttrValue(tNode); for (let i = 0; i < selectors.length; i++) { - if (isNodeMatchingSelector(tNode, selectors[i])) { + // if a node has the ngProjectAs attribute match it against unparsed selector + // match a node against a parsed selector only if ngProjectAs attribute is not present + if (ngProjectAsAttrVal === textSelectors[i] || + ngProjectAsAttrVal === null && isNodeMatchingSelector(tNode, selectors[i])) { return i + 1; // first matching selector "captures" a given node } } diff --git a/packages/core/test/render3/compiler_canonical/content_projection_spec.ts b/packages/core/test/render3/compiler_canonical/content_projection_spec.ts index ed64e0bf20..7f663fc039 100644 --- a/packages/core/test/render3/compiler_canonical/content_projection_spec.ts +++ b/packages/core/test/render3/compiler_canonical/content_projection_spec.ts @@ -39,8 +39,9 @@ describe('content projection', () => { } // NORMATIVE - const $pD_0$: $r3$.ɵCssSelector[] = + const $pD_0P$: $r3$.ɵCssSelector[] = [[[['span', 'title', 'toFirst'], null]], [[['span', 'title', 'toSecond'], null]]]; + const $pD_0R$: string[] = ['span[title=toFirst]', 'span[title=toSecond]']; // /NORMATIVE @Component({ @@ -57,7 +58,7 @@ describe('content projection', () => { factory: () => new ComplexComponent(), template: function(ctx: $ComplexComponent$, cm: $boolean$) { if (cm) { - $r3$.ɵpD(0, $pD_0$); + $r3$.ɵpD(0, $pD_0P$, $pD_0R$); $r3$.ɵE(1, 'div', ['id', 'first']); $r3$.ɵP(2, 0, 1); $r3$.ɵe(); @@ -91,4 +92,4 @@ describe('content projection', () => { } }); -}); \ No newline at end of file +}); diff --git a/packages/core/test/render3/content_spec.ts b/packages/core/test/render3/content_spec.ts index 0cc2e61007..0621af9898 100644 --- a/packages/core/test/render3/content_spec.ts +++ b/packages/core/test/render3/content_spec.ts @@ -538,7 +538,8 @@ describe('content projection', () => { const Child = createComponent('child', function(ctx: any, cm: boolean) { if (cm) { projectionDef( - 0, [[[['span', 'title', 'toFirst'], null]], [[['span', 'title', 'toSecond'], null]]]); + 0, [[[['span', 'title', 'toFirst'], null]], [[['span', 'title', 'toSecond'], null]]], + ['span[title=toFirst]', 'span[title=toSecond]']); elementStart(1, 'div', ['id', 'first']); { projection(2, 0, 1); } elementEnd(); @@ -585,7 +586,8 @@ describe('content projection', () => { const Child = createComponent('child', function(ctx: any, cm: boolean) { if (cm) { projectionDef( - 0, [[[['span', 'class', 'toFirst'], null]], [[['span', 'class', 'toSecond'], null]]]); + 0, [[[['span', 'class', 'toFirst'], null]], [[['span', 'class', 'toSecond'], null]]], + ['span.toFirst', 'span.toSecond']); elementStart(1, 'div', ['id', 'first']); { projection(2, 0, 1); } elementEnd(); @@ -632,7 +634,8 @@ describe('content projection', () => { const Child = createComponent('child', function(ctx: any, cm: boolean) { if (cm) { projectionDef( - 0, [[[['span', 'class', 'toFirst'], null]], [[['span', 'class', 'toSecond'], null]]]); + 0, [[[['span', 'class', 'toFirst'], null]], [[['span', 'class', 'toSecond'], null]]], + ['span.toFirst', 'span.toSecond']); elementStart(1, 'div', ['id', 'first']); { projection(2, 0, 1); } elementEnd(); @@ -678,7 +681,9 @@ describe('content projection', () => { */ const Child = createComponent('child', function(ctx: any, cm: boolean) { if (cm) { - projectionDef(0, [[[['span'], null]], [[['span', 'class', 'toSecond'], null]]]); + projectionDef( + 0, [[[['span'], null]], [[['span', 'class', 'toSecond'], null]]], + ['span', 'span.toSecond']); elementStart(1, 'div', ['id', 'first']); { projection(2, 0, 1); } elementEnd(); @@ -724,7 +729,7 @@ describe('content projection', () => { */ const Child = createComponent('child', function(ctx: any, cm: boolean) { if (cm) { - projectionDef(0, [[[['span', 'class', 'toFirst'], null]]]); + projectionDef(0, [[[['span', 'class', 'toFirst'], null]]], ['span.toFirst']); elementStart(1, 'div', ['id', 'first']); { projection(2, 0, 1); } elementEnd(); @@ -771,7 +776,7 @@ describe('content projection', () => { */ const Child = createComponent('child', function(ctx: any, cm: boolean) { if (cm) { - projectionDef(0, [[[['span', 'class', 'toSecond'], null]]]); + projectionDef(0, [[[['span', 'class', 'toSecond'], null]]], ['span.toSecond']); elementStart(1, 'div', ['id', 'first']); { projection(2, 0); } elementEnd(); @@ -825,7 +830,7 @@ describe('content projection', () => { */ const GrandChild = createComponent('grand-child', function(ctx: any, cm: boolean) { if (cm) { - projectionDef(0, [[[['span'], null]]]); + projectionDef(0, [[[['span'], null]]], ['span']); projection(1, 0, 1); elementStart(2, 'hr'); elementEnd(); @@ -891,7 +896,9 @@ describe('content projection', () => { */ const Card = createComponent('card', function(ctx: any, cm: boolean) { if (cm) { - projectionDef(0, [[[['', 'card-title', ''], null]], [[['', 'card-content', ''], null]]]); + projectionDef( + 0, [[[['', 'card-title', ''], null]], [[['', 'card-content', ''], null]]], + ['[card-title]', '[card-content]']); projection(1, 0, 1); elementStart(2, 'hr'); elementEnd(); @@ -942,6 +949,108 @@ describe('content projection', () => { '

      Title


      content
      '); }); + + it('should support ngProjectAs on elements (including )', () => { + + /** + * + *
      + * + */ + const Card = createComponent('card', function(ctx: any, cm: boolean) { + if (cm) { + projectionDef( + 0, [[[['', 'card-title', ''], null]], [[['', 'card-content', ''], null]]], + ['[card-title]', '[card-content]']); + projection(1, 0, 1); + elementStart(2, 'hr'); + elementEnd(); + projection(3, 0, 2); + } + }); + + /** + * + *

      + * + */ + const CardWithTitle = createComponent('card-with-title', function(ctx: any, cm: boolean) { + if (cm) { + projectionDef(0); + elementStart(1, Card); + { + elementStart(3, 'h1', ['ngProjectAs', '[card-title]']); + { text(4, 'Title'); } + elementEnd(); + projection(5, 0, 0, ['ngProjectAs', '[card-content]']); + } + elementEnd(); + Card.ngComponentDef.h(2, 1); + directiveRefresh(2, 1); + } + }); + + /** + * + * content + * + */ + const App = createComponent('app', function(ctx: any, cm: boolean) { + if (cm) { + elementStart(0, CardWithTitle); + { text(2, 'content'); } + elementEnd(); + } + CardWithTitle.ngComponentDef.h(1, 0); + directiveRefresh(1, 0); + }); + + const app = renderComponent(App); + expect(toHtml(app)) + .toEqual('

      Title


      content
      '); + + }); + + it('should not match selectors against node having ngProjectAs attribute', function() { + + /** + * + */ + const Child = createComponent('child', function(ctx: any, cm: boolean) { + if (cm) { + projectionDef(0, [[[['div'], null]]], ['div']); + projection(1, 0, 1); + } + }); + + /** + * + *
      should not project
      + *
      should project
      + *
      + */ + const Parent = createComponent('parent', function(ctx: any, cm: boolean) { + if (cm) { + elementStart(0, Child); + { + elementStart(2, 'div', ['ngProjectAs', 'span']); + { text(3, 'should not project'); } + elementEnd(); + elementStart(4, 'div'); + { text(5, 'should project'); } + elementEnd(); + } + elementEnd(); + } + Child.ngComponentDef.h(1, 0); + directiveRefresh(1, 0); + }); + + const parent = renderComponent(Parent); + expect(toHtml(parent)).toEqual('
      should project
      '); + }); + it('should match selectors against projected containers', () => { /** @@ -951,7 +1060,7 @@ describe('content projection', () => { */ const Child = createComponent('child', function(ctx: any, cm: boolean) { if (cm) { - projectionDef(0, [[[['div'], null]]]); + projectionDef(0, [[[['div'], null]]], ['div']); elementStart(1, 'span'); { projection(2, 0, 1); } elementEnd(); diff --git a/packages/core/test/render3/node_selector_matcher_spec.ts b/packages/core/test/render3/node_selector_matcher_spec.ts index de4570774b..f26d19b4e2 100644 --- a/packages/core/test/render3/node_selector_matcher_spec.ts +++ b/packages/core/test/render3/node_selector_matcher_spec.ts @@ -7,8 +7,8 @@ */ import {TNode} from '../../src/render3/interfaces/node'; -import {CssSelector, CssSelectorWithNegations, SimpleCssSelector} from '../../src/render3/interfaces/projection'; -import {isNodeMatchingSelector, isNodeMatchingSelectorWithNegations, isNodeMatchingSimpleSelector} from '../../src/render3/node_selector_matcher'; +import {CssSelector, CssSelectorWithNegations, NG_PROJECT_AS_ATTR_NAME, SimpleCssSelector} from '../../src/render3/interfaces/projection'; +import {getProjectAsAttrValue, isNodeMatchingSelector, isNodeMatchingSelectorWithNegations, isNodeMatchingSimpleSelector} from '../../src/render3/node_selector_matcher'; function testLStaticData(tagName: string, attrs: string[] | null): TNode { return { @@ -183,4 +183,26 @@ describe('css selector matching', () => { }); }); + describe('reading the ngProjectAs attribute value', function() { + + function testTNode(attrs: string[] | null) { return testLStaticData('tag', attrs); } + + it('should get ngProjectAs value if present', function() { + expect(getProjectAsAttrValue(testTNode([NG_PROJECT_AS_ATTR_NAME, 'tag[foo=bar]']))) + .toBe('tag[foo=bar]'); + }); + + it('should return null if there are no attributes', + function() { expect(getProjectAsAttrValue(testTNode(null))).toBe(null); }); + + it('should return if ngProjectAs is not present', function() { + expect(getProjectAsAttrValue(testTNode(['foo', 'bar']))).toBe(null); + }); + + it('should not accidentally identify ngProjectAs in attribute values', function() { + expect(getProjectAsAttrValue(testTNode(['foo', NG_PROJECT_AS_ATTR_NAME]))).toBe(null); + }); + + }); + }); From fa974c7d4e2df273a0bd4ed17b808ed12757870c Mon Sep 17 00:00:00 2001 From: Jason Aden Date: Tue, 20 Feb 2018 15:08:41 -0800 Subject: [PATCH 054/604] fix(router): fix URL serialization so special characters are only encoded where needed (#22337) This change brings Angular largely in line with how AngularJS previously serialized URLs. This is based on RFC 3986 and resolves issues such as the above #10280 where URLs could be parsed, re-serialized, then parsed again producing a different result on the second parsing. Adjustments to be aware of in this commit: * URI fragments will now serialize the same as query strings * In the URI path or segments (portion prior to query string and/or fragment), the plus sign (`+`) and ampersand (`&`) will appear decoded * In the URL path or segments, parentheses values (`(` and `)`) will now appear percent encoded as `%28` and `%29` respectively * In the URL path or segments, semicolons will be encoded in their percent encoding `%3B` NOTE: Parentheses and semicolons denoting auxillary routes or matrix params will still appear in their decoded form -- only parentheses and semicolons used as values in a segment or key/value pair for matrix params will be encoded. While these changes are not considered breaking because applications should be decoding URLs and key/value pairs, it is possible that some unit tests will break if comparing hard-coded URLs in tests since that hard coded string will represent the old encoding. Therefore we are releasing this fix in the upcoming Angular v6 rather than adding it to a patch for v5. Fixes: #10280 PR Close #22337 --- packages/router/src/url_tree.ts | 62 ++++++--- packages/router/test/url_serializer.spec.ts | 141 ++++++++++++++++++-- 2 files changed, 175 insertions(+), 28 deletions(-) diff --git a/packages/router/src/url_tree.ts b/packages/router/src/url_tree.ts index 63c3a7ea01..13f0ab46e1 100644 --- a/packages/router/src/url_tree.ts +++ b/packages/router/src/url_tree.ts @@ -280,7 +280,7 @@ export class DefaultUrlSerializer implements UrlSerializer { serialize(tree: UrlTree): string { const segment = `/${serializeSegment(tree.root, true)}`; const query = serializeQueryParams(tree.queryParams); - const fragment = typeof tree.fragment === `string` ? `#${encodeURI(tree.fragment !)}` : ''; + const fragment = typeof tree.fragment === `string` ? `#${encodeUriQuery(tree.fragment !)}` : ''; return `${segment}${query}${fragment}`; } @@ -326,9 +326,10 @@ function serializeSegment(segment: UrlSegmentGroup, root: boolean): string { } /** - * This method is intended for encoding *key* or *value* parts of query component. We need a custom - * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be - * encoded per http://tools.ietf.org/html/rfc3986: + * Encodes a URI string with the default encoding. This function will only ever be called from + * `encodeUriQuery` or `encodeUriSegment` as it's the base set of encodings to be used. We need + * a custom encoding because encodeURIComponent is too aggressive and encodes stuff that doesn't + * have to be encoded per http://tools.ietf.org/html/rfc3986: * query = *( pchar / "/" / "?" ) * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" @@ -336,32 +337,61 @@ function serializeSegment(segment: UrlSegmentGroup, root: boolean): string { * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" * / "*" / "+" / "," / ";" / "=" */ -export function encode(s: string): string { +function encodeUriString(s: string): string { return encodeURIComponent(s) .replace(/%40/g, '@') .replace(/%3A/gi, ':') .replace(/%24/g, '$') - .replace(/%2C/gi, ',') - .replace(/%3B/gi, ';'); + .replace(/%2C/gi, ','); +} + +/** + * This function should be used to encode both keys and values in a query string key/value or the + * URL fragment. In the following URL, you need to call encodeUriQuery on "k", "v" and "f": + * + * http://www.site.org/html;mk=mv?k=v#f + */ +export function encodeUriQuery(s: string): string { + return encodeUriString(s).replace(/%3B/gi, ';'); +} + +/** + * This function should be run on any URI segment as well as the key and value in a key/value + * pair for matrix params. In the following URL, you need to call encodeUriSegment on "html", + * "mk", and "mv": + * + * http://www.site.org/html;mk=mv?k=v#f + */ +export function encodeUriSegment(s: string): string { + return encodeUriString(s).replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/%26/gi, '&'); } export function decode(s: string): string { return decodeURIComponent(s); } -export function serializePath(path: UrlSegment): string { - return `${encode(path.path)}${serializeParams(path.parameters)}`; +// Query keys/values should have the "+" replaced first, as "+" in a query string is " ". +// decodeURIComponent function will not decode "+" as a space. +export function decodeQuery(s: string): string { + return decode(s.replace(/\+/g, '%20')); } -function serializeParams(params: {[key: string]: string}): string { - return Object.keys(params).map(key => `;${encode(key)}=${encode(params[key])}`).join(''); +export function serializePath(path: UrlSegment): string { + return `${encodeUriSegment(path.path)}${serializeMatrixParams(path.parameters)}`; +} + +function serializeMatrixParams(params: {[key: string]: string}): string { + return Object.keys(params) + .map(key => `;${encodeUriSegment(key)}=${encodeUriSegment(params[key])}`) + .join(''); } function serializeQueryParams(params: {[key: string]: any}): string { const strParams: string[] = Object.keys(params).map((name) => { const value = params[name]; - return Array.isArray(value) ? value.map(v => `${encode(name)}=${encode(v)}`).join('&') : - `${encode(name)}=${encode(value)}`; + return Array.isArray(value) ? + value.map(v => `${encodeUriQuery(name)}=${encodeUriQuery(v)}`).join('&') : + `${encodeUriQuery(name)}=${encodeUriQuery(value)}`; }); return strParams.length ? `?${strParams.join("&")}` : ''; @@ -414,7 +444,7 @@ class UrlParser { } parseFragment(): string|null { - return this.consumeOptional('#') ? decodeURI(this.remaining) : null; + return this.consumeOptional('#') ? decodeURIComponent(this.remaining) : null; } private parseChildren(): {[outlet: string]: UrlSegmentGroup} { @@ -506,8 +536,8 @@ class UrlParser { } } - const decodedKey = decode(key); - const decodedVal = decode(value); + const decodedKey = decodeQuery(key); + const decodedVal = decodeQuery(value); if (params.hasOwnProperty(decodedKey)) { // Append to existing values diff --git a/packages/router/test/url_serializer.spec.ts b/packages/router/test/url_serializer.spec.ts index a318011b10..f71538cc3a 100644 --- a/packages/router/test/url_serializer.spec.ts +++ b/packages/router/test/url_serializer.spec.ts @@ -7,7 +7,7 @@ */ import {PRIMARY_OUTLET} from '../src/shared'; -import {DefaultUrlSerializer, UrlSegmentGroup, encode, serializePath} from '../src/url_tree'; +import {DefaultUrlSerializer, UrlSegmentGroup, encodeUriQuery, encodeUriSegment, serializePath} from '../src/url_tree'; describe('url serializer', () => { const url = new DefaultUrlSerializer(); @@ -189,7 +189,7 @@ describe('url serializer', () => { describe('encoding/decoding', () => { it('should encode/decode path segments and parameters', () => { const u = - `/${encode("one two")};${encode("p 1")}=${encode("v 1")};${encode("p 2")}=${encode("v 2")}`; + `/${encodeUriSegment("one two")};${encodeUriSegment("p 1")}=${encodeUriSegment("v 1")};${encodeUriSegment("p 2")}=${encodeUriSegment("v 2")}`; const tree = url.parse(u); expect(tree.root.children[PRIMARY_OUTLET].segments[0].path).toEqual('one two'); @@ -199,7 +199,8 @@ describe('url serializer', () => { }); it('should encode/decode "slash" in path segments and parameters', () => { - const u = `/${encode("one/two")};${encode("p/1")}=${encode("v/1")}/three`; + const u = + `/${encodeUriSegment("one/two")};${encodeUriSegment("p/1")}=${encodeUriSegment("v/1")}/three`; const tree = url.parse(u); const segment = tree.root.children[PRIMARY_OUTLET].segments[0]; expect(segment.path).toEqual('one/two'); @@ -210,7 +211,8 @@ describe('url serializer', () => { }); it('should encode/decode query params', () => { - const u = `/one?${encode("p 1")}=${encode("v 1")}&${encode("p 2")}=${encode("v 2")}`; + const u = + `/one?${encodeUriQuery("p 1")}=${encodeUriQuery("v 1")}&${encodeUriQuery("p 2")}=${encodeUriQuery("v 2")}`; const tree = url.parse(u); expect(tree.queryParams).toEqual({'p 1': 'v 1', 'p 2': 'v 2'}); @@ -219,28 +221,143 @@ describe('url serializer', () => { expect(url.serialize(tree)).toEqual(u); }); + it('should decode spaces in query as %20 or +', () => { + const u1 = `/one?foo=bar baz`; + const u2 = `/one?foo=bar+baz`; + const u3 = `/one?foo=bar%20baz`; + + const u1p = url.parse(u1); + const u2p = url.parse(u2); + const u3p = url.parse(u3); + + expect(url.serialize(u1p)).toBe(url.serialize(u2p)); + expect(url.serialize(u2p)).toBe(url.serialize(u3p)); + expect(u1p.queryParamMap.get('foo')).toBe('bar baz'); + expect(u2p.queryParamMap.get('foo')).toBe('bar baz'); + expect(u3p.queryParamMap.get('foo')).toBe('bar baz'); + }); + it('should encode query params leaving sub-delimiters intact', () => { - const percentChars = '/?#[]&+= '; - const percentCharsEncoded = '%2F%3F%23%5B%5D%26%2B%3D%20'; + const percentChars = '/?#&+=[] '; + const percentCharsEncoded = '%2F%3F%23%26%2B%3D%5B%5D%20'; const intactChars = '!$\'()*,;:'; const params = percentChars + intactChars; const paramsEncoded = percentCharsEncoded + intactChars; const mixedCaseString = 'sTrInG'; - expect(percentCharsEncoded).toEqual(encode(percentChars)); - expect(intactChars).toEqual(encode(intactChars)); + expect(percentCharsEncoded).toEqual(encodeUriQuery(percentChars)); + expect(intactChars).toEqual(encodeUriQuery(intactChars)); // Verify it replaces repeated characters correctly - expect(paramsEncoded + paramsEncoded).toEqual(encode(params + params)); + expect(paramsEncoded + paramsEncoded).toEqual(encodeUriQuery(params + params)); // Verify it doesn't change the case of alpha characters - expect(mixedCaseString + paramsEncoded).toEqual(encode(mixedCaseString + params)); + expect(mixedCaseString + paramsEncoded).toEqual(encodeUriQuery(mixedCaseString + params)); }); it('should encode/decode fragment', () => { - const u = `/one#${encodeURI("one two=three four")}`; + const u = `/one#${encodeUriQuery('one two=three four')}`; const tree = url.parse(u); expect(tree.fragment).toEqual('one two=three four'); - expect(url.serialize(tree)).toEqual(u); + expect(url.serialize(tree)).toEqual('/one#one%20two%3Dthree%20four'); + }); + }); + + describe('special character encoding/decoding', () => { + + // Tests specific to https://github.com/angular/angular/issues/10280 + it('should parse encoded parens in matrix params', () => { + const auxRoutesUrl = '/abc;foo=(other:val)'; + const fooValueUrl = '/abc;foo=%28other:val%29'; + + const auxParsed = url.parse(auxRoutesUrl).root; + const fooParsed = url.parse(fooValueUrl).root; + + + // Test base case + expect(auxParsed.children[PRIMARY_OUTLET].segments.length).toBe(1); + expect(auxParsed.children[PRIMARY_OUTLET].segments[0].path).toBe('abc'); + expect(auxParsed.children[PRIMARY_OUTLET].segments[0].parameters).toEqual({foo: ''}); + expect(auxParsed.children['other'].segments.length).toBe(1); + expect(auxParsed.children['other'].segments[0].path).toBe('val'); + + // Confirm matrix params are URL decoded + expect(fooParsed.children[PRIMARY_OUTLET].segments.length).toBe(1); + expect(fooParsed.children[PRIMARY_OUTLET].segments[0].path).toBe('abc'); + expect(fooParsed.children[PRIMARY_OUTLET].segments[0].parameters).toEqual({ + foo: '(other:val)' + }); + }); + + it('should serialize encoded parens in matrix params', () => { + const testUrl = '/abc;foo=%28one%29'; + + const parsed = url.parse(testUrl); + + expect(url.serialize(parsed)).toBe('/abc;foo=%28one%29'); + }); + + it('should not serialize encoded parens in query params', () => { + const testUrl = '/abc?foo=%28one%29'; + + const parsed = url.parse(testUrl); + + expect(parsed.queryParams).toEqual({foo: '(one)'}); + + expect(url.serialize(parsed)).toBe('/abc?foo=(one)'); + }); + + // Test special characters in general + + // From http://www.ietf.org/rfc/rfc3986.txt + const unreserved = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~`; + + it('should encode a minimal set of special characters in queryParams and fragment', () => { + const notEncoded = unreserved + `:@!$'*,();`; + const encode = ` +%&=#[]/?`; + const encoded = `%20%2B%25%26%3D%23%5B%5D%2F%3F`; + + const parsed = url.parse('/foo'); + + parsed.queryParams = {notEncoded, encode}; + + expect(url.serialize(parsed)).toBe(`/foo?notEncoded=${notEncoded}&encode=${encoded}`); + }); + + it('should encode a minimal set of special characters in fragment', () => { + const notEncoded = unreserved + `:@!$'*,();`; + const encode = ` +%&=#[]/?`; + const encoded = `%20%2B%25%26%3D%23%5B%5D%2F%3F`; + + const parsed = url.parse('/foo'); + + parsed.fragment = notEncoded + encode; + + expect(url.serialize(parsed)).toBe(`/foo#${notEncoded}${encoded}`); + }); + + it('should encode minimal special characters plus parens and semi-colon in matrix params', + () => { + const notEncoded = unreserved + `:@!$'*,&`; + const encode = ` /%=#()[];?+`; + const encoded = `%20%2F%25%3D%23%28%29%5B%5D%3B%3F%2B`; + + const parsed = url.parse('/foo'); + + parsed.root.children[PRIMARY_OUTLET].segments[0].parameters = {notEncoded, encode}; + + expect(url.serialize(parsed)).toBe(`/foo;notEncoded=${notEncoded};encode=${encoded}`); + }); + + it('should encode special characters in the path the same as matrix params', () => { + const notEncoded = unreserved + `:@!$'*,&`; + const encode = ` /%=#()[];?+`; + const encoded = `%20%2F%25%3D%23%28%29%5B%5D%3B%3F%2B`; + + const parsed = url.parse('/foo'); + + parsed.root.children[PRIMARY_OUTLET].segments[0].path = notEncoded + encode; + + expect(url.serialize(parsed)).toBe(`/${notEncoded}${encoded}`); }); }); From 4c40812b71cbfc9d51a41913629f9b447623bbee Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Mon, 5 Mar 2018 16:47:43 -0800 Subject: [PATCH 055/604] fix(bazel): fixes for ng_package on Windows (#22597) PR Close #22597 --- WORKSPACE | 6 +++--- integration/bazel/WORKSPACE | 6 ++---- packages/bazel/package.json | 1 + packages/bazel/src/modify_tsconfig.js | 1 + packages/bazel/src/ng_package/ng_package.bzl | 3 +-- packages/bazel/src/ng_package/packager.ts | 9 ++++++++- packages/bazel/src/ngc-wrapped/index.ts | 12 ++++++------ 7 files changed, 22 insertions(+), 16 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 49e6c208ce..088edb56e2 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -17,9 +17,9 @@ node_repositories(package_json = [ http_archive( name = "build_bazel_rules_typescript", - url = "https://github.com/bazelbuild/rules_typescript/archive/0.11.0.zip", - strip_prefix = "rules_typescript-0.11.0", - sha256 = "ce7bac7b5287d5162fcbe4f7c14ff507ae7d506ceb44626ad09f6b7e27d3260b", + url = "https://github.com/bazelbuild/rules_typescript/archive/0.11.1.zip", + strip_prefix = "rules_typescript-0.11.1", + sha256 = "7406bea7954e1c906f075115dfa176551a881119f6820b126ea1eacb09f34a1a", ) load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace") diff --git a/integration/bazel/WORKSPACE b/integration/bazel/WORKSPACE index 06ca4806eb..301a0cb52e 100644 --- a/integration/bazel/WORKSPACE +++ b/integration/bazel/WORKSPACE @@ -10,11 +10,9 @@ http_archive( load("@build_bazel_rules_nodejs//:defs.bzl", "node_repositories") node_repositories(package_json = ["//:package.json"]) -http_archive( +local_repository( name = "build_bazel_rules_typescript", - url = "https://github.com/bazelbuild/rules_typescript/archive/0.11.0.zip", - strip_prefix = "rules_typescript-0.11.0", - sha256 = "ce7bac7b5287d5162fcbe4f7c14ff507ae7d506ceb44626ad09f6b7e27d3260b", + path = "node_modules/@bazel/typescript", ) load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace") diff --git a/packages/bazel/package.json b/packages/bazel/package.json index c92d00df30..cb59349c09 100644 --- a/packages/bazel/package.json +++ b/packages/bazel/package.json @@ -5,6 +5,7 @@ "author": "angular", "license": "MIT", "dependencies": { + "@bazel/typescript": "^0.11.1", "@types/node": "6.0.84", "@types/shelljs": "0.7.7", "shelljs": "0.7.8", diff --git a/packages/bazel/src/modify_tsconfig.js b/packages/bazel/src/modify_tsconfig.js index 37ff67c06f..a4a9e28713 100644 --- a/packages/bazel/src/modify_tsconfig.js +++ b/packages/bazel/src/modify_tsconfig.js @@ -24,6 +24,7 @@ function main(args) { data['compilerOptions']['target'] = 'es5'; data['bazelOptions']['es5Mode'] = true; data['bazelOptions']['tsickle'] = false; + data['bazelOptions']['tsickleExternsPath'] = ''; data['compilerOptions']['outDir'] = path.join(data['compilerOptions']['outDir'], newRoot); if (data['angularCompilerOptions']) { data['angularCompilerOptions']['expectedOut'] = diff --git a/packages/bazel/src/ng_package/ng_package.bzl b/packages/bazel/src/ng_package/ng_package.bzl index 83f6fff158..e4dcb5c4c7 100644 --- a/packages/bazel/src/ng_package/ng_package.bzl +++ b/packages/bazel/src/ng_package/ng_package.bzl @@ -144,9 +144,8 @@ def _ng_package_impl(ctx): for dep in ctx.attr.deps if hasattr(dep, "angular")]) - # TODO: the args look long, maybe need to spill to a params file: - # https://docs.bazel.build/versions/master/skylark/lib/Args.html#use_param_file args = ctx.actions.args() + args.use_param_file("%s", use_always = True) args.add(npm_package_directory.path) args.add(ctx.label.package) args.add([ctx.bin_dir.path, ctx.label.package], join_with="/") diff --git a/packages/bazel/src/ng_package/packager.ts b/packages/bazel/src/ng_package/packager.ts index 4c5a3c89b6..b43e360891 100644 --- a/packages/bazel/src/ng_package/packager.ts +++ b/packages/bazel/src/ng_package/packager.ts @@ -16,6 +16,7 @@ function filter(ext: string): (path: string) => boolean { function main(args: string[]): number { shx.set('-e'); + args = fs.readFileSync(args[0], {encoding: 'utf-8'}).split('\n').map(s => s === '\'\'' ? '' : s); const [out, srcDir, binDir, readmeMd, fesms2015Arg, fesms5Arg, bundlesArg, srcsArg, stampData, licenseFile] = args; @@ -32,7 +33,13 @@ function main(args: string[]): number { function replaceVersionPlaceholders(filePath: string, content: string) { if (stampData) { const version = shx.grep('BUILD_SCM_VERSION', stampData).split(' ')[1].trim(); - return content.replace(/0.0.0-PLACEHOLDER/g, version); + // Split the replacement into separate strings so we don't match it while publishing + return content.replace( + new RegExp( + '0.0.0' + + '-PLACEHOLDER', + 'g'), + version); } return content; } diff --git a/packages/bazel/src/ngc-wrapped/index.ts b/packages/bazel/src/ngc-wrapped/index.ts index 99b3e1ef12..b5a482d8f9 100644 --- a/packages/bazel/src/ngc-wrapped/index.ts +++ b/packages/bazel/src/ngc-wrapped/index.ts @@ -7,7 +7,7 @@ */ import * as ng from '@angular/compiler-cli'; -import {BazelOptions, CachedFileLoader, CompilerHost, FileCache, FileLoader, UncachedFileLoader, constructManifest, debug, fixUmdModuleDeclarations, parseTsconfig, runAsWorker, runWorkerLoop} from '@bazel/typescript'; +import {BazelOptions, CachedFileLoader, CompilerHost, FileCache, FileLoader, UncachedFileLoader, constructManifest, debug, fixUmdModuleDeclarations, parseTsconfig, resolveNormalizedPath, runAsWorker, runWorkerLoop} from '@bazel/typescript'; import * as fs from 'fs'; import * as path from 'path'; import * as tsickle from 'tsickle'; @@ -77,7 +77,7 @@ export function relativeToRootDirs(filePath: string, rootDirs: string[]): string if (!filePath) return filePath; // NB: the rootDirs should have been sorted longest-first for (const dir of rootDirs || []) { - const rel = path.relative(dir, filePath); + const rel = path.posix.relative(dir, filePath); if (rel.indexOf('.') != 0) return rel; } return filePath; @@ -107,7 +107,7 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true, // Resolve the inputs to absolute paths to match TypeScript internals const resolvedInputs: {[path: string]: string} = {}; for (const key of Object.keys(inputs)) { - resolvedInputs[path.resolve(key)] = inputs[key]; + resolvedInputs[resolveNormalizedPath(key)] = inputs[key]; } fileCache.updateCache(resolvedInputs); } else { @@ -133,7 +133,7 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true, tsHost.writeFile = (fileName: string, content: string, writeByteOrderMark: boolean, onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => { - const relative = relativeToRootDirs(fileName, [compilerOpts.rootDir]); + const relative = relativeToRootDirs(fileName.replace(/\\/g, '/'), [compilerOpts.rootDir]); const expectedIdx = writtenExpectedOuts.findIndex(o => o === relative); if (expectedIdx >= 0) { writtenExpectedOuts.splice(expectedIdx, 1); @@ -196,7 +196,7 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true, } return bazelOpts.workspaceName + '/' + result; }; - ngHost.toSummaryFileName = (fileName: string, referringSrcFileName: string) => path.join( + ngHost.toSummaryFileName = (fileName: string, referringSrcFileName: string) => path.posix.join( bazelOpts.workspaceName, relativeToRootDirs(fileName, compilerOpts.rootDirs).replace(EXT, '')); if (allDepsCompiledWithBazel) { @@ -206,7 +206,7 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true, // as that has a different implementation of fromSummaryFileName / toSummaryFileName ngHost.fromSummaryFileName = (fileName: string, referringLibFileName: string) => { const workspaceRelative = fileName.split('/').splice(1).join('/'); - return path.resolve(bazelBin, workspaceRelative) + '.d.ts'; + return resolveNormalizedPath(bazelBin, workspaceRelative) + '.d.ts'; }; } // Patch a property on the ngHost that allows the resourceNameToModuleName function to From d0db9ded90c254bf7ea9021cca120beb2b7e5406 Mon Sep 17 00:00:00 2001 From: Ward Bell Date: Tue, 20 Feb 2018 17:10:46 -0800 Subject: [PATCH 056/604] docs: fix cli-quickstart doc and specs (#22338) * tests were broken * incorrect instructions. * didn't match current CLI template for new project PR Close #22338 --- .../cli-quickstart/src/app/app.component.html | 11 +++++------ .../cli-quickstart/src/app/app.component.spec.ts | 6 ++---- .../examples/cli-quickstart/src/app/app.component.ts | 2 +- aio/content/guide/quickstart.md | 2 +- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/aio/content/examples/cli-quickstart/src/app/app.component.html b/aio/content/examples/cli-quickstart/src/app/app.component.html index 1a1d09dc78..24c56edee4 100644 --- a/aio/content/examples/cli-quickstart/src/app/app.component.html +++ b/aio/content/examples/cli-quickstart/src/app/app.component.html @@ -1,20 +1,19 @@

      - Welcome to {{title}}!! + Welcome to {{ title }}!

      - Angular logo + Angular Logo

      Here are some links to help you start:

      - diff --git a/aio/content/examples/cli-quickstart/src/app/app.component.spec.ts b/aio/content/examples/cli-quickstart/src/app/app.component.spec.ts index 7d2799ceb6..1f5da50d19 100644 --- a/aio/content/examples/cli-quickstart/src/app/app.component.spec.ts +++ b/aio/content/examples/cli-quickstart/src/app/app.component.spec.ts @@ -1,7 +1,5 @@ import { TestBed, async } from '@angular/core/testing'; - import { AppComponent } from './app.component'; - describe('AppComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ @@ -20,13 +18,13 @@ describe('AppComponent', () => { it(`should have as title 'app'`, async(() => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; - expect(app.title).toEqual('app'); + expect(app.title).toMatch(/app/i); })); it('should render title in a h1 tag', async(() => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; - expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!!'); + expect(compiled.querySelector('h1').textContent).toMatch(/app/i); })); }); diff --git a/aio/content/examples/cli-quickstart/src/app/app.component.ts b/aio/content/examples/cli-quickstart/src/app/app.component.ts index 4c1b4a9bf7..d977bbe40a 100644 --- a/aio/content/examples/cli-quickstart/src/app/app.component.ts +++ b/aio/content/examples/cli-quickstart/src/app/app.component.ts @@ -11,6 +11,6 @@ import { Component } from '@angular/core'; // #enddocregion metadata // #docregion title, class export class AppComponent { - title = 'My First Angular App'; + title = 'My First Angular App!'; } // #enddocregion title, class diff --git a/aio/content/guide/quickstart.md b/aio/content/guide/quickstart.md index 6d94197581..6ea0489188 100644 --- a/aio/content/guide/quickstart.md +++ b/aio/content/guide/quickstart.md @@ -126,7 +126,7 @@ This is the _root component_ and it is named `app-root`. You can find it in `./src/app/app.component.ts`. -Open the component file and change the `title` property from _Welcome to app!!_ to _Welcome to My First Angular App!!_: +Open the component file and change the `title` property from `'app'` to `'My First Angular App!'`. From b3d1761825bf56b84a5c22551790f9a5259c9b7f Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Sun, 4 Mar 2018 20:57:42 +0000 Subject: [PATCH 057/604] build(aio): compute and display short descriptions in methods (#22583) Previously only export docs were displaying a short description. Now methods in classes and interfaces also compute and render the short description. Closes #22500 PR Close #22583 --- .../transforms/angular-api-package/index.js | 4 +- .../templates/api/lib/memberHelpers.html | 51 ++++++++++++------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/aio/tools/transforms/angular-api-package/index.js b/aio/tools/transforms/angular-api-package/index.js index 16c81e7c1e..9f769c9afc 100644 --- a/aio/tools/transforms/angular-api-package/index.js +++ b/aio/tools/transforms/angular-api-package/index.js @@ -94,7 +94,7 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage]) .config(function(splitDescription, EXPORT_DOC_TYPES) { // Only split the description on the API docs - splitDescription.docTypes = EXPORT_DOC_TYPES; + splitDescription.docTypes = EXPORT_DOC_TYPES.concat(['member', 'function-overload']); }) .config(function(computePathsProcessor, EXPORT_DOC_TYPES, generateApiListDoc) { @@ -130,6 +130,6 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage]) ]); convertToJsonProcessor.docTypes = convertToJsonProcessor.docTypes.concat(DOCS_TO_CONVERT); postProcessHtml.docTypes = convertToJsonProcessor.docTypes.concat(DOCS_TO_CONVERT); - autoLinkCode.docTypes = DOCS_TO_CONVERT.concat(['member']); + autoLinkCode.docTypes = DOCS_TO_CONVERT.concat(['member', 'function-overload']); autoLinkCode.codeElements = ['code', 'code-example', 'code-pane']; }); diff --git a/aio/tools/transforms/templates/api/lib/memberHelpers.html b/aio/tools/transforms/templates/api/lib/memberHelpers.html index ffeab09194..c278c3c3fc 100644 --- a/aio/tools/transforms/templates/api/lib/memberHelpers.html +++ b/aio/tools/transforms/templates/api/lib/memberHelpers.html @@ -38,24 +38,35 @@ {%- endmacro -%} {%- macro renderOverloadInfo(overload, cssClass, method) -%} - {% if overload.description and (overload.description != method.description) %}{$ overload.description | marked $}{% endif %} - {$ renderMemberSyntax(overload) $} +{$ renderMemberSyntax(overload) $} -

      Parameters

      - {$ params.renderParameters(overload.parameterDocs, cssClass + '-parameters', cssClass + '-parameter') $} +{% if overload.shortDescription and (overload.shortDescription != method.shortDescription) %} +
      + {$ overload.shortDescription | marked $} +
      {% endif %} - {% if overload.type or overload.returns.type %} -

      Returns

      - {% marked %}`{$ (overload.type or overload.returns.type) $}`{% if overload.returns %}: {$ overload.returns.description $}{% endif %}{% endmarked %} - {% endif %} +

      Parameters

      +{$ params.renderParameters(overload.parameterDocs, cssClass + '-parameters', cssClass + '-parameter') $} - {% if overload.throws.length %} -

      Throws

      - {% for error in overload.throws %} - {% marked %}`{$ (error.typeList or 'Error') $}` {$ error.description $}{% endmarked %} - {% endfor %} - {% endif %} +{% if overload.type or overload.returns.type %} +

      Returns

      +{% marked %}`{$ (overload.type or overload.returns.type) $}`{% if overload.returns %}: {$ overload.returns.description $}{% endif %}{% endmarked %} +{% endif %} + + +{% if overload.throws.length %} +

      Throws

      +{% for error in overload.throws %} +{% marked %}`{$ (error.typeList or 'Error') $}` {$ error.description $}{% endmarked %} +{% endfor %} +{% endif %} + +{% if overload.description and (overload.description != method.description) -%} +
      + {$ overload.description | marked $} +
      +{%- endif %} {%- endmacro -%} {%- macro renderMethodDetail(method, cssClass) -%} @@ -68,9 +79,9 @@ {% endif %}

      {% endif %} - {% if method.description %} - + {% endif %} {% if method.overloads.length == 0 %} @@ -102,6 +113,11 @@ {% endif %} + {% if method.description %} + + {% endif %}
      - ObservableArray
      ObservableArray
      Given From b80fd6be58d44271d5ccf841b209842a069e1f98 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 2 Mar 2018 22:53:50 +0000 Subject: [PATCH 042/604] build(aio): render whether API classes and members are abstract (#22563) Closes #22537 PR Close #22563 --- aio/package.json | 2 +- .../templates/api/includes/class-overview.html | 2 +- .../templates/api/lib/memberHelpers.html | 3 ++- aio/yarn.lock | 16 ++++++++-------- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/aio/package.json b/aio/package.json index a9d0d17e50..9be842003c 100644 --- a/aio/package.json +++ b/aio/package.json @@ -107,7 +107,7 @@ "cross-spawn": "^5.1.0", "css-selector-parser": "^1.3.0", "dgeni": "^0.4.7", - "dgeni-packages": "^0.25.0", + "dgeni-packages": "^0.26.0", "entities": "^1.1.1", "eslint": "^3.19.0", "eslint-plugin-jasmine": "^2.2.0", diff --git a/aio/tools/transforms/templates/api/includes/class-overview.html b/aio/tools/transforms/templates/api/includes/class-overview.html index 1bd48c0dca..3d681c90a8 100644 --- a/aio/tools/transforms/templates/api/includes/class-overview.html +++ b/aio/tools/transforms/templates/api/includes/class-overview.html @@ -2,7 +2,7 @@
      -{$ doc.docType $} {$ doc.name $}{$ doc.typeParams | escape $}{$ memberHelper.renderHeritage(doc) $} {{$ memberHelper.renderMembers(doc) $} +{% if doc.isAbstract %}abstract {% endif%}{$ doc.docType $} {$ doc.name $}{$ doc.typeParams | escape $}{$ memberHelper.renderHeritage(doc) $} {{$ memberHelper.renderMembers(doc) $} } {$ descendants.renderDescendants(doc, 'class', 'Subclasses') $} diff --git a/aio/tools/transforms/templates/api/lib/memberHelpers.html b/aio/tools/transforms/templates/api/lib/memberHelpers.html index 6deda7f58e..d3469ceaec 100644 --- a/aio/tools/transforms/templates/api/lib/memberHelpers.html +++ b/aio/tools/transforms/templates/api/lib/memberHelpers.html @@ -28,9 +28,10 @@ {%- macro renderMemberSyntax(member, truncateLines) -%} {%- if member.accessibility !== 'public' %}{$ member.accessibility $} {% endif -%} + {%- if member.isAbstract %}abstract {% endif -%} + {%- if member.isStatic %}static {% endif -%} {%- if (member.isGetAccessor or member.isReadonly) and not member.isSetAccessor %}get {% endif -%} {%- if member.isSetAccessor and not member.isGetAccessor %}set {% endif -%} - {%- if member.isStatic %}static {% endif -%} {$ member.name $}{$ member.typeParameters | escape $}{% if not member.isGetAccessor %}{$ params.paramList(member.parameters, truncateLines) | trim $}{% endif %} {%- if member.isOptional %}?{% endif -%} {$ params.returnType(member.type) | trim | truncateCode(truncateLines) $} diff --git a/aio/yarn.lock b/aio/yarn.lock index 7545c27601..6431fd29d7 100644 --- a/aio/yarn.lock +++ b/aio/yarn.lock @@ -2312,9 +2312,9 @@ devtools-timeline-model@1.1.6: chrome-devtools-frontend "1.0.401423" resolve "1.1.7" -dgeni-packages@^0.25.0: - version "0.25.0" - resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.25.0.tgz#380f0b569ae36d82959252604b729e85e0cd7d4a" +dgeni-packages@^0.26.0: + version "0.26.0" + resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.26.0.tgz#4311a0631f8459703001a5e65e390a29321a0562" dependencies: canonical-path "0.0.2" catharsis "^0.8.1" @@ -2336,7 +2336,7 @@ dgeni-packages@^0.25.0: source-map-support "^0.4.15" spdx-license-list "^2.1.0" stringmap "^0.2.2" - typescript "2.4" + typescript "~2.7.1" urlencode "^1.1.0" dgeni@^0.4.7, dgeni@^0.4.9: @@ -8457,10 +8457,6 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@2.4: - version "2.4.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.2.tgz#f8395f85d459276067c988aa41837a8f82870844" - typescript@^2.4.1, typescript@~2.6.1: version "2.6.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" @@ -8469,6 +8465,10 @@ typescript@^2.5.3: version "2.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.3.tgz#df3dcdc38f3beb800d4bc322646b04a3f6ca7f0d" +typescript@~2.7.1: + version "2.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.2.tgz#2d615a1ef4aee4f574425cdff7026edf81919836" + uglify-es@^3.3.4: version "3.3.5" resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.5.tgz#cf7e695da81999f85196b15e2978862f13212f88" From 41064fcb36ca74f8a88cba85dc65da00153a273a Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 2 Mar 2018 23:32:12 +0000 Subject: [PATCH 043/604] fix(aio): reposition and simplify github links (#22565) Closes #22379 PR Close #22565 --- aio/src/styles/1-layouts/_content-layout.scss | 13 ++++++++----- aio/tests/e2e/api.e2e-spec.ts | 10 ++++++++++ aio/tests/e2e/app.po.ts | 5 ++--- .../transforms/templates/api/base.template.html | 10 +++++----- .../transforms/templates/api/lib/githubLinks.html | 9 +++++---- .../templates/overview-dump.template.html | 6 +++--- 6 files changed, 33 insertions(+), 20 deletions(-) diff --git a/aio/src/styles/1-layouts/_content-layout.scss b/aio/src/styles/1-layouts/_content-layout.scss index 6933ca7ba8..7870fa3fec 100644 --- a/aio/src/styles/1-layouts/_content-layout.scss +++ b/aio/src/styles/1-layouts/_content-layout.scss @@ -39,9 +39,12 @@ aio-shell.page-docs { } .page-actions { - display: flex; - flex-direction: column; - position: absolute; - top: 80px; - right: 24px; + float: right; + .material-icons { + border-radius: 4px; + padding: 4px; + font-size: 20px; + background-color: $mist; + color: $mediumgray; + } } \ No newline at end of file diff --git a/aio/tests/e2e/api.e2e-spec.ts b/aio/tests/e2e/api.e2e-spec.ts index d85010d1c8..b09ab70e44 100644 --- a/aio/tests/e2e/api.e2e-spec.ts +++ b/aio/tests/e2e/api.e2e-spec.ts @@ -50,4 +50,14 @@ describe('Api pages', function() { const page = new ApiPage('api/forms/FormControl'); expect(page.getSection('instance-properties').isPresent()).toBe(false); }); + + it('should show links to github', () => { + const page = new ApiPage('api/core/EventEmitter'); + /* tslint:disable:max-line-length */ + expect(page.ghLinks.get(0).getAttribute('href')) + .toMatch(/https:\/\/github\.com\/angular\/angular\/edit\/master\/packages\/core\/src\/event_emitter\.ts\?message=docs\(core\)%3A%20describe%20your%20change\.\.\.#L\d+-L\d+/); + expect(page.ghLinks.get(1).getAttribute('href')) + .toMatch(/https:\/\/github\.com\/angular\/angular\/tree\/[^/]+\/packages\/core\/src\/event_emitter\.ts#L\d+-L\d+/); + /* tslint:enable:max-line-length */ + }); }); diff --git a/aio/tests/e2e/app.po.ts b/aio/tests/e2e/app.po.ts index 61690131a4..1194620da2 100644 --- a/aio/tests/e2e/app.po.ts +++ b/aio/tests/e2e/app.po.ts @@ -10,10 +10,9 @@ export class SitePage { sidenav = element(by.css('mat-sidenav')); docViewer = element(by.css('aio-doc-viewer')); codeExample = element.all(by.css('aio-doc-viewer pre > code')); - ghLink = this.docViewer + ghLinks = this.docViewer .all(by.css('a')) - .filter((a: ElementFinder) => a.getAttribute('href').then(href => githubRegex.test(href))) - .first(); + .filter((a: ElementFinder) => a.getAttribute('href').then(href => githubRegex.test(href))); static setWindowWidth(newWidth: number) { const win = browser.driver.manage().window(); diff --git a/aio/tools/transforms/templates/api/base.template.html b/aio/tools/transforms/templates/api/base.template.html index 14f2edfaf7..a5cdb7bb75 100644 --- a/aio/tools/transforms/templates/api/base.template.html +++ b/aio/tools/transforms/templates/api/base.template.html @@ -2,7 +2,11 @@ {% set comma = joiner(',') %} {% set slash = joiner('/') %}
      -
      {$ property.name $} + {%- if (property.isGetAccessor or property.isReadonly) and not property.isSetAccessor %}Read-only.{% endif %} {$ (property.description or property.constructorParamDoc.description) | marked $} {% if property.constructorParamDoc %} Declared in constructor.{% endif %}
      - {$ method.description | marked $} + {% if method.shortDescription %}
      + {$ method.shortDescription | marked $}
      + {$ method.description | marked $} +
      {% endmacro -%} @@ -135,6 +151,7 @@
      {%- if (property.isGetAccessor or property.isReadonly) and not property.isSetAccessor %}Read-only.{% endif %} + {% if property.shortDescription %}{$ property.shortDescription | marked $}{% endif %} {$ (property.description or property.constructorParamDoc.description) | marked $} {% if property.constructorParamDoc %} Declared in constructor.{% endif %}