Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
19273a89b6 | |||
957192db6d | |||
4546f09b0e | |||
c7e4b86bf1 | |||
3a67339945 | |||
80fef43334 | |||
80efa4b26b | |||
92103606e1 | |||
487970f6f2 | |||
89a493934f | |||
6f54ed01a8 | |||
ef99670343 | |||
808e4cf1b6 | |||
9cb71fc7c9 | |||
6f082f93f5 | |||
0d7124e7cb | |||
52896591e6 | |||
07c44a76f9 | |||
3b5fc56cc1 | |||
2c30365496 | |||
8c6b376c07 | |||
089d4533b3 | |||
8113a03c21 | |||
93854e00e0 | |||
fddf30cfaf | |||
0fd859ca0b | |||
f274eeb9fc | |||
675e3d4244 | |||
767841b434 | |||
7bce0153bb |
46
CHANGELOG.md
46
CHANGELOG.md
@ -1,10 +1,54 @@
|
||||
<a name="2.1.2"></a>
|
||||
## [2.1.2](https://github.com/angular/angular/compare/2.1.1...2.1.2) (2016-10-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** don't access view local variables nor pipes in host expressions ([#12396](https://github.com/angular/angular/issues/12396)) ([867494a](https://github.com/angular/angular/commit/867494a)), closes [#12004](https://github.com/angular/angular/issues/12004) [#12071](https://github.com/angular/angular/issues/12071)
|
||||
* **compiler:** walk third party modules ([#12453](https://github.com/angular/angular/issues/12453)) ([a838aba](https://github.com/angular/angular/commit/a838aba)), closes [#11889](https://github.com/angular/angular/issues/11889) [#12428](https://github.com/angular/angular/issues/12428)
|
||||
* **compiler:** remove double exports of template_ast ([7742ec0](https://github.com/angular/angular/commit/7742ec0))
|
||||
* **compiler:** use Maps instead of objects in selector implementation ([d321b0e](https://github.com/angular/angular/commit/d321b0e))
|
||||
* **compiler-cli:** fix types ([ef15364](https://github.com/angular/angular/commit/ef15364))
|
||||
* **compiler-cli:** assert that all pipes and directives are declared by a module ([7221632](https://github.com/angular/angular/commit/7221632))
|
||||
* **http:** overwrite already set xsrf header ([b4265e0](https://github.com/angular/angular/commit/b4265e0))
|
||||
* **router:** add a test to make sure canDeactivate guards are called for aux routes ([fc60fa7](https://github.com/angular/angular/commit/fc60fa7)), closes [#11345](https://github.com/angular/angular/issues/11345)
|
||||
* **router:** canDeactivate guards are not triggered for componentless routes ([b741853](https://github.com/angular/angular/commit/b741853)), closes [#12375](https://github.com/angular/angular/issues/12375)
|
||||
* **router:** change router not to deactivate aux routes when navigating from a componentless routes ([52a853e](https://github.com/angular/angular/commit/52a853e))
|
||||
* **router:** disallow component routes with named outlets ([8f2fa0f](https://github.com/angular/angular/commit/8f2fa0f)), closes [#11208](https://github.com/angular/angular/issues/11208) [#11082](https://github.com/angular/angular/issues/11082)
|
||||
* **router:** preserve resolve data ([6ccbfd4](https://github.com/angular/angular/commit/6ccbfd4)), closes [#12306](https://github.com/angular/angular/issues/12306)
|
||||
|
||||
|
||||
|
||||
<a name="2.2.0-beta.1"></a>
|
||||
# [2.2.0-beta.1](https://github.com/angular/angular/compare/2.2.0-beta.0...2.2.0-beta.1) (2016-10-27)
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **upgrade:** re-export the new static upgrade APIs on new entry ([a26dd28](https://github.com/angular/angular/commit/a26dd28))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **router:** export routerLinkActive w/ isActive property ([c9f58cf](https://github.com/angular/angular/commit/c9f58cf))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* upgrade: Four newly added APIs in 2.2.0-beta:
|
||||
downgradeComponent, downgradeInjectable, UpgradeComponent, and UpgradeModule are no longer exported by @angular/upgrade.
|
||||
Import these from @angular/upgrade/static instead.
|
||||
|
||||
|
||||
Note: The 2.2.0-beta.1 release also contains all the changes present in the 2.1.2 release.
|
||||
|
||||
|
||||
|
||||
# [2.1.1](https://github.com/angular/angular/compare/2.1.0...2.1.1) (2016-10-20)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** generate aot code for animation trigger output events ([#12291](https://github.com/angular/angular/issues/12291)) ([6e5f8b5](https://github.com/angular/angular/commit/6e5f8b5)), closes [#11707](https://github.com/angular/angular/issues/11707)
|
||||
* **compiler:** don't redeclare a var in the same scope ([#12386](https://github.com/angular/angular/issues/12386)) ([cca4a5c](https://github.com/angular/angular/commit/cca4a5c))
|
||||
* **core:** fix decorator defalut values ([bd1dcb5](https://github.com/angular/angular/commit/bd1dcb5))
|
||||
* **core:** fix decorator default values ([bd1dcb5](https://github.com/angular/angular/commit/bd1dcb5))
|
||||
* **core:** fix property decorators ([3993279](https://github.com/angular/angular/commit/3993279)), closes [#12224](https://github.com/angular/angular/issues/12224)
|
||||
* **http:** make normalizeMethodName optimizer-compatible. ([#12370](https://github.com/angular/angular/issues/12370)) ([8409b65](https://github.com/angular/angular/commit/8409b65))
|
||||
* **router:** correctly export filter operator in es5 ([#12286](https://github.com/angular/angular/issues/12286)) ([27d7677](https://github.com/angular/angular/commit/27d7677))
|
||||
|
@ -8,13 +8,10 @@
|
||||
|
||||
import {Inject, Injectable, OpaqueToken} from '@angular/core';
|
||||
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {MeasureValues} from '../measure_values';
|
||||
import {Statistic} from '../statistic';
|
||||
import {Validator} from '../validator';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A validator that checks the regression slope of a specific metric.
|
||||
* Waits for the regression slope to be >=0.
|
||||
@ -40,17 +37,17 @@ export class RegressionSlopeValidator extends Validator {
|
||||
|
||||
validate(completeSample: MeasureValues[]): MeasureValues[] {
|
||||
if (completeSample.length >= this._sampleSize) {
|
||||
var latestSample = ListWrapper.slice(
|
||||
completeSample, completeSample.length - this._sampleSize, completeSample.length);
|
||||
var xValues: number[] = [];
|
||||
var yValues: number[] = [];
|
||||
for (var i = 0; i < latestSample.length; i++) {
|
||||
const latestSample =
|
||||
completeSample.slice(completeSample.length - this._sampleSize, completeSample.length);
|
||||
const xValues: number[] = [];
|
||||
const yValues: number[] = [];
|
||||
for (let i = 0; i < latestSample.length; i++) {
|
||||
// For now, we only use the array index as x value.
|
||||
// TODO(tbosch): think about whether we should use time here instead
|
||||
xValues.push(i);
|
||||
yValues.push(latestSample[i].values[this._metric]);
|
||||
}
|
||||
var regressionSlope = Statistic.calculateRegressionSlope(
|
||||
const regressionSlope = Statistic.calculateRegressionSlope(
|
||||
xValues, Statistic.calculateMean(xValues), yValues, Statistic.calculateMean(yValues));
|
||||
return regressionSlope >= 0 ? latestSample : null;
|
||||
} else {
|
||||
|
@ -8,12 +8,9 @@
|
||||
|
||||
import {Inject, Injectable, OpaqueToken} from '@angular/core';
|
||||
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {MeasureValues} from '../measure_values';
|
||||
import {Validator} from '../validator';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A validator that waits for the sample to have a certain size.
|
||||
*/
|
||||
@ -28,8 +25,7 @@ export class SizeValidator extends Validator {
|
||||
|
||||
validate(completeSample: MeasureValues[]): MeasureValues[] {
|
||||
if (completeSample.length >= this._sampleSize) {
|
||||
return ListWrapper.slice(
|
||||
completeSample, completeSample.length - this._sampleSize, completeSample.length);
|
||||
return completeSample.slice(completeSample.length - this._sampleSize, completeSample.length);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ export class IOsDriverExtension extends WebDriverExtension {
|
||||
var startTime = record['startTime'];
|
||||
var endTime = record['endTime'];
|
||||
|
||||
if (type === 'FunctionCall' && (isBlank(data) || data['scriptName'] !== 'InjectedScript')) {
|
||||
if (type === 'FunctionCall' && (data == null || data['scriptName'] !== 'InjectedScript')) {
|
||||
events.push(createStartEvent('script', startTime));
|
||||
endEvent = createEndEvent('script', endTime);
|
||||
} else if (type === 'Time') {
|
||||
|
@ -28,7 +28,7 @@ export function main() {
|
||||
if (!descriptions) {
|
||||
descriptions = [];
|
||||
}
|
||||
if (isBlank(sampleId)) {
|
||||
if (sampleId == null) {
|
||||
sampleId = 'null';
|
||||
}
|
||||
var providers: Provider[] = [
|
||||
|
@ -9,7 +9,6 @@
|
||||
import {describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {MeasureValues, ReflectiveInjector, RegressionSlopeValidator} from '../../index';
|
||||
import {ListWrapper} from '../../src/facade/collection';
|
||||
|
||||
export function main() {
|
||||
describe('regression slope validator', () => {
|
||||
@ -44,17 +43,15 @@ export function main() {
|
||||
it('should return the last sampleSize runs when the regression slope is ==0', () => {
|
||||
createValidator({size: 2, metric: 'script'});
|
||||
var sample = [mv(0, 0, {'script': 1}), mv(1, 1, {'script': 1}), mv(2, 2, {'script': 1})];
|
||||
expect(validator.validate(ListWrapper.slice(sample, 0, 2)))
|
||||
.toEqual(ListWrapper.slice(sample, 0, 2));
|
||||
expect(validator.validate(sample)).toEqual(ListWrapper.slice(sample, 1, 3));
|
||||
expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2));
|
||||
expect(validator.validate(sample)).toEqual(sample.slice(1, 3));
|
||||
});
|
||||
|
||||
it('should return the last sampleSize runs when the regression slope is >0', () => {
|
||||
createValidator({size: 2, metric: 'script'});
|
||||
var sample = [mv(0, 0, {'script': 1}), mv(1, 1, {'script': 2}), mv(2, 2, {'script': 3})];
|
||||
expect(validator.validate(ListWrapper.slice(sample, 0, 2)))
|
||||
.toEqual(ListWrapper.slice(sample, 0, 2));
|
||||
expect(validator.validate(sample)).toEqual(ListWrapper.slice(sample, 1, 3));
|
||||
expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2));
|
||||
expect(validator.validate(sample)).toEqual(sample.slice(1, 3));
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -9,7 +9,6 @@
|
||||
import {describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {MeasureValues, ReflectiveInjector, SizeValidator} from '../../index';
|
||||
import {ListWrapper} from '../../src/facade/collection';
|
||||
|
||||
export function main() {
|
||||
describe('size validator', () => {
|
||||
@ -37,9 +36,8 @@ export function main() {
|
||||
it('should return the last sampleSize runs when it has at least the given size', () => {
|
||||
createValidator(2);
|
||||
var sample = [mv(0, 0, {'a': 1}), mv(1, 1, {'b': 2}), mv(2, 2, {'c': 3})];
|
||||
expect(validator.validate(ListWrapper.slice(sample, 0, 2)))
|
||||
.toEqual(ListWrapper.slice(sample, 0, 2));
|
||||
expect(validator.validate(sample)).toEqual(ListWrapper.slice(sample, 1, 3));
|
||||
expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2));
|
||||
expect(validator.validate(sample)).toEqual(sample.slice(1, 3));
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -11,8 +11,6 @@ import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableD
|
||||
import {isListLikeIterable} from '../facade/collection';
|
||||
import {isPresent} from '../facade/lang';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
*
|
||||
@ -31,11 +29,11 @@ import {isPresent} from '../facade/lang';
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* The CSS classes are updated as follow depending on the type of the expression evaluation:
|
||||
* - `string` - the CSS classes listed in a string (space delimited) are added,
|
||||
* - `Array` - the CSS classes (Array elements) are added,
|
||||
* - `Object` - keys are CSS class names that get added when the expression given in the value
|
||||
* evaluates to a truthy value, otherwise class are removed.
|
||||
* The CSS classes are updated as follows, depending on the type of the expression evaluation:
|
||||
* - `string` - the CSS classes listed in the string (space delimited) are added,
|
||||
* - `Array` - the CSS classes declared as Array elements are added,
|
||||
* - `Object` - keys are CSS classes that get added when the expression given in the value
|
||||
* evaluates to a truthy value, otherwise they are removed.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@ -50,7 +48,6 @@ export class NgClass implements DoCheck {
|
||||
private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
|
||||
private _ngEl: ElementRef, private _renderer: Renderer) {}
|
||||
|
||||
|
||||
@Input('class')
|
||||
set klass(v: string) {
|
||||
this._applyInitialClasses(true);
|
||||
|
@ -27,7 +27,7 @@ Then you can add an import statement in the `bootstrap` allowing you to bootstra
|
||||
generated code:
|
||||
|
||||
```typescript
|
||||
main_module.ts
|
||||
main.module.ts
|
||||
-------------
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {Component, NgModule, ApplicationRef} from '@angular/core';
|
||||
@ -49,7 +49,7 @@ export class MainModule {
|
||||
bootstrap.ts
|
||||
-------------
|
||||
|
||||
import {MainModuleNgFactory} from './main_module.ngfactory';
|
||||
import {MainModuleNgFactory} from './main.module.ngfactory';
|
||||
import {platformBrowser} from '@angular/platform-browser';
|
||||
|
||||
platformBrowser().bootstrapModuleFactory(MainModuleNgFactory);
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
export {CodeGenerator} from './src/codegen';
|
||||
export {Extractor} from './src/extractor';
|
||||
export {NodeReflectorHostContext, ReflectorHost, ReflectorHostContext} from './src/reflector_host';
|
||||
export {StaticReflector, StaticReflectorHost, StaticSymbol} from './src/static_reflector';
|
||||
|
||||
|
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @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} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'use-third-party',
|
||||
template: '<third-party-comp [thirdParty]="title"></third-party-comp>' +
|
||||
'<another-third-party-comp></another-third-party-comp>',
|
||||
})
|
||||
export class ComponentUsingThirdParty {
|
||||
title: string = 'from 3rd party';
|
||||
}
|
@ -12,6 +12,10 @@
|
||||
<source>Welcome</source>
|
||||
<target>tervetuloa</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="63a85808f03b8181e36a952e0fa38202c2304862" datatype="html">
|
||||
<source>other-3rdP-component</source>
|
||||
<target>other-3rdP-component</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
@ -9,4 +9,5 @@
|
||||
<translationbundle>
|
||||
<translation id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4">käännä teksti</translation>
|
||||
<translation id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">tervetuloa</translation>
|
||||
<translation id="63a85808f03b8181e36a952e0fa38202c2304862">other-3rdP-component</translation>
|
||||
</translationbundle>
|
||||
|
@ -11,9 +11,12 @@ import {FormsModule} from '@angular/forms';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {MdButtonModule} from '@angular2-material/button';
|
||||
|
||||
import {ThirdpartyModule} from '../third_party_src/module';
|
||||
|
||||
import {MultipleComponentsMyComp, NextComp} from './a/multiple_components';
|
||||
import {AnimateCmp} from './animate';
|
||||
import {BasicComp} from './basic';
|
||||
import {ComponentUsingThirdParty} from './comp_using_3rdp';
|
||||
import {CompWithAnalyzeEntryComponentsProvider, CompWithEntryComponents} from './entry_components';
|
||||
import {CompConsumingEvents, CompUsingPipes, CompWithProviders, CompWithReferences, DirPublishingEvents, ModuleUsingCustomElements} from './features';
|
||||
import {CompUsingRootModuleDirectiveAndPipe, SomeDirectiveInRootModule, SomePipeInRootModule, SomeService, someLibModuleWithProviders} from './module_fixtures';
|
||||
@ -22,35 +25,47 @@ import {CompForChildQuery, CompWithChildQuery, CompWithDirectiveChild, Directive
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
SomeDirectiveInRootModule,
|
||||
SomePipeInRootModule,
|
||||
AnimateCmp,
|
||||
BasicComp,
|
||||
CompConsumingEvents,
|
||||
CompForChildQuery,
|
||||
CompWithEntryComponents,
|
||||
CompUsingPipes,
|
||||
CompUsingRootModuleDirectiveAndPipe,
|
||||
CompWithAnalyzeEntryComponentsProvider,
|
||||
ProjectingComp,
|
||||
CompWithChildQuery,
|
||||
CompWithDirectiveChild,
|
||||
CompWithEntryComponents,
|
||||
CompWithNgContent,
|
||||
CompUsingRootModuleDirectiveAndPipe,
|
||||
CompWithProviders,
|
||||
CompWithReferences,
|
||||
CompUsingPipes,
|
||||
CompConsumingEvents,
|
||||
DirectiveForQuery,
|
||||
DirPublishingEvents,
|
||||
MultipleComponentsMyComp,
|
||||
DirectiveForQuery,
|
||||
NextComp,
|
||||
ProjectingComp,
|
||||
SomeDirectiveInRootModule,
|
||||
SomePipeInRootModule,
|
||||
ComponentUsingThirdParty,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule, FormsModule, someLibModuleWithProviders(), ModuleUsingCustomElements,
|
||||
MdButtonModule
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
MdButtonModule,
|
||||
ModuleUsingCustomElements,
|
||||
someLibModuleWithProviders(),
|
||||
ThirdpartyModule,
|
||||
],
|
||||
providers: [SomeService],
|
||||
entryComponents: [
|
||||
AnimateCmp, BasicComp, CompWithEntryComponents, CompWithAnalyzeEntryComponentsProvider,
|
||||
ProjectingComp, CompWithChildQuery, CompUsingRootModuleDirectiveAndPipe, CompWithReferences
|
||||
AnimateCmp,
|
||||
BasicComp,
|
||||
CompUsingRootModuleDirectiveAndPipe,
|
||||
CompWithAnalyzeEntryComponentsProvider,
|
||||
CompWithChildQuery,
|
||||
CompWithEntryComponents,
|
||||
CompWithReferences,
|
||||
ProjectingComp,
|
||||
ComponentUsingThirdParty,
|
||||
]
|
||||
})
|
||||
export class MainModule {
|
||||
|
@ -43,13 +43,13 @@ describe('template codegen output', () => {
|
||||
});
|
||||
|
||||
it('should be able to create the basic component', () => {
|
||||
var compFixture = createComponent(BasicComp);
|
||||
const compFixture = createComponent(BasicComp);
|
||||
expect(compFixture.componentInstance).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should support ngIf', () => {
|
||||
var compFixture = createComponent(BasicComp);
|
||||
var debugElement = compFixture.debugElement;
|
||||
const compFixture = createComponent(BasicComp);
|
||||
const debugElement = compFixture.debugElement;
|
||||
expect(debugElement.children.length).toBe(3);
|
||||
|
||||
compFixture.componentInstance.ctxBool = true;
|
||||
@ -59,8 +59,8 @@ describe('template codegen output', () => {
|
||||
});
|
||||
|
||||
it('should support ngFor', () => {
|
||||
var compFixture = createComponent(BasicComp);
|
||||
var debugElement = compFixture.debugElement;
|
||||
const compFixture = createComponent(BasicComp);
|
||||
const debugElement = compFixture.debugElement;
|
||||
expect(debugElement.children.length).toBe(3);
|
||||
|
||||
// test NgFor
|
||||
@ -83,11 +83,9 @@ describe('template codegen output', () => {
|
||||
});
|
||||
|
||||
it('should support i18n for content tags', () => {
|
||||
const compFixture = createComponent(BasicComp);
|
||||
const debugElement = compFixture.debugElement;
|
||||
const containerElement = <any>debugElement.nativeElement;
|
||||
const pElement = <any>containerElement.children.find((c: any) => c.name == 'p');
|
||||
const pText = <string>pElement.children.map((c: any) => c.data).join('').trim();
|
||||
const containerElement = createComponent(BasicComp).nativeElement;
|
||||
const pElement = containerElement.children.find((c: any) => c.name == 'p');
|
||||
const pText = pElement.children.map((c: any) => c.data).join('').trim();
|
||||
expect(pText).toBe('tervetuloa');
|
||||
});
|
||||
});
|
||||
|
@ -34,6 +34,7 @@ const EXPECTED_XMB = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!ELEMENT ex (#PCDATA)>
|
||||
]>
|
||||
<messagebundle>
|
||||
<msg id="63a85808f03b8181e36a952e0fa38202c2304862">other-3rdP-component</msg>
|
||||
<msg id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" desc="desc" meaning="meaning">translate me</msg>
|
||||
<msg id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">Welcome</msg>
|
||||
</messagebundle>
|
||||
@ -43,6 +44,10 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<file source-language="en" datatype="plaintext" original="ng2.template">
|
||||
<body>
|
||||
<trans-unit id="63a85808f03b8181e36a952e0fa38202c2304862" datatype="html">
|
||||
<source>other-3rdP-component</source>
|
||||
<target/>
|
||||
</trans-unit>
|
||||
<trans-unit id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" datatype="html">
|
||||
<source>translate me</source>
|
||||
<target/>
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
import './init';
|
||||
|
||||
import {ComponentUsingThirdParty} from '../src/comp_using_3rdp';
|
||||
import {MainModule} from '../src/module';
|
||||
import {CompUsingLibModuleDirectiveAndPipe, CompUsingRootModuleDirectiveAndPipe, SOME_TOKEN, ServiceUsingLibModule, SomeLibModule, SomeService} from '../src/module_fixtures';
|
||||
|
||||
@ -15,9 +16,9 @@ import {createComponent, createModule} from './util';
|
||||
describe('NgModule', () => {
|
||||
it('should support providers', () => {
|
||||
const moduleRef = createModule();
|
||||
expect(moduleRef.instance instanceof MainModule).toBe(true);
|
||||
expect(moduleRef.injector.get(MainModule) instanceof MainModule).toBe(true);
|
||||
expect(moduleRef.injector.get(SomeService) instanceof SomeService).toBe(true);
|
||||
expect(moduleRef.instance instanceof MainModule).toEqual(true);
|
||||
expect(moduleRef.injector.get(MainModule) instanceof MainModule).toEqual(true);
|
||||
expect(moduleRef.injector.get(SomeService) instanceof SomeService).toEqual(true);
|
||||
});
|
||||
|
||||
it('should support entryComponents components', () => {
|
||||
@ -26,7 +27,7 @@ describe('NgModule', () => {
|
||||
CompUsingRootModuleDirectiveAndPipe);
|
||||
expect(cf.componentType).toBe(CompUsingRootModuleDirectiveAndPipe);
|
||||
const compRef = cf.create(moduleRef.injector);
|
||||
expect(compRef.instance instanceof CompUsingRootModuleDirectiveAndPipe).toBe(true);
|
||||
expect(compRef.instance instanceof CompUsingRootModuleDirectiveAndPipe).toEqual(true);
|
||||
});
|
||||
|
||||
it('should support entryComponents via the ANALYZE_FOR_ENTRY_COMPONENTS provider and function providers in components',
|
||||
@ -42,12 +43,30 @@ describe('NgModule', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
describe('third-party modules', () => {
|
||||
// https://github.com/angular/angular/issues/11889
|
||||
it('should support third party entryComponents components', () => {
|
||||
const fixture = createComponent(ComponentUsingThirdParty);
|
||||
const thirdPComps = fixture.nativeElement.children;
|
||||
expect(thirdPComps[0].children[0].children[0].data).toEqual('3rdP-component');
|
||||
expect(thirdPComps[1].children[0].children[0].data).toEqual('other-3rdP-component');
|
||||
});
|
||||
|
||||
// https://github.com/angular/angular/issues/12428
|
||||
it('should support third party directives', () => {
|
||||
const fixture = createComponent(ComponentUsingThirdParty);
|
||||
const debugElement = fixture.debugElement;
|
||||
fixture.detectChanges();
|
||||
expect(debugElement.children[0].properties['title']).toEqual('from 3rd party');
|
||||
});
|
||||
});
|
||||
|
||||
it('should support module directives and pipes', () => {
|
||||
const compFixture = createComponent(CompUsingRootModuleDirectiveAndPipe);
|
||||
compFixture.detectChanges();
|
||||
|
||||
const debugElement = compFixture.debugElement;
|
||||
expect(debugElement.children[0].properties['title']).toBe('transformed someValue');
|
||||
expect(debugElement.children[0].properties['title']).toEqual('transformed someValue');
|
||||
});
|
||||
|
||||
it('should support module directives and pipes on lib modules', () => {
|
||||
@ -55,10 +74,10 @@ describe('NgModule', () => {
|
||||
compFixture.detectChanges();
|
||||
|
||||
const debugElement = compFixture.debugElement;
|
||||
expect(debugElement.children[0].properties['title']).toBe('transformed someValue');
|
||||
expect(debugElement.children[0].properties['title']).toEqual('transformed someValue');
|
||||
|
||||
expect(debugElement.injector.get(SomeLibModule) instanceof SomeLibModule).toBe(true);
|
||||
expect(debugElement.injector.get(SomeLibModule) instanceof SomeLibModule).toEqual(true);
|
||||
expect(debugElement.injector.get(ServiceUsingLibModule) instanceof ServiceUsingLibModule)
|
||||
.toBe(true);
|
||||
.toEqual(true);
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,8 @@
|
||||
This folder emulates consuming precompiled modules and components.
|
||||
It is compiled separately from the other sources under `src`
|
||||
to only generate `*.js` / `*.d.ts` / `*.metadata.json` files,
|
||||
but no `*.ngfactory.ts` files.
|
||||
|
||||
** WARNING **
|
||||
Do not import components/directives from here directly as we want to test that ngc still compiles
|
||||
them when they are not imported.
|
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @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} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'third-party-comp',
|
||||
template: '<div>3rdP-component</div>',
|
||||
})
|
||||
export class ThirdPartyComponent {
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @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 {Directive, Input} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[thirdParty]',
|
||||
host: {'[title]': 'thirdParty'},
|
||||
})
|
||||
export class ThirdPartyDirective {
|
||||
@Input() thirdParty: string;
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @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 {ThirdPartyComponent} from './comp';
|
||||
import {ThirdPartyDirective} from './directive';
|
||||
import {AnotherThirdPartyModule} from './other_module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
ThirdPartyComponent,
|
||||
ThirdPartyDirective,
|
||||
],
|
||||
exports: [
|
||||
AnotherThirdPartyModule,
|
||||
ThirdPartyComponent,
|
||||
ThirdPartyDirective,
|
||||
],
|
||||
imports: [AnotherThirdPartyModule]
|
||||
})
|
||||
export class ThirdpartyModule {
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @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} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'another-third-party-comp',
|
||||
template: '<div i18n>other-3rdP-component</div>',
|
||||
})
|
||||
export class AnotherThirdpartyComponent {
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @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 {AnotherThirdpartyComponent} from './other_comp';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AnotherThirdpartyComponent],
|
||||
exports: [AnotherThirdpartyComponent],
|
||||
})
|
||||
export class AnotherThirdPartyModule {
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
"angularCompilerOptions": {
|
||||
// For TypeScript 1.8, we have to lay out generated files
|
||||
// in the same source directory with your code.
|
||||
"genDir": ".",
|
||||
"debug": true
|
||||
},
|
||||
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"experimentalDecorators": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "",
|
||||
"declaration": true,
|
||||
"lib": ["es6", "dom"],
|
||||
"baseUrl": ".",
|
||||
"outDir": "../node_modules/third_party"
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
{
|
||||
"angularCompilerOptions": {
|
||||
// For TypeScript 1.8, we have to lay out generated files
|
||||
// in the same source directory with your code.
|
||||
"genDir": ".",
|
||||
"debug": true
|
||||
},
|
||||
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"experimentalDecorators": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "",
|
||||
"declaration": true,
|
||||
"lib": ["es6", "dom"],
|
||||
"baseUrl": "."
|
||||
},
|
||||
|
||||
"files": [
|
||||
"src/module",
|
||||
"src/bootstrap",
|
||||
"test/all_spec",
|
||||
"benchmarks/src/tree/ng2/index_aot.ts",
|
||||
"benchmarks/src/tree/ng2_switch/index_aot.ts",
|
||||
"benchmarks/src/largetable/ng2/index_aot.ts",
|
||||
"benchmarks/src/largetable/ng2_switch/index_aot.ts"
|
||||
]
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
{
|
||||
"angularCompilerOptions": {
|
||||
// For TypeScript 1.8, we have to lay out generated files
|
||||
// in the same source directory with your code.
|
||||
"genDir": ".",
|
||||
"debug": true
|
||||
},
|
||||
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"experimentalDecorators": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "",
|
||||
"declaration": true,
|
||||
"lib": ["es6", "dom"],
|
||||
"baseUrl": "."
|
||||
},
|
||||
|
||||
"files": [
|
||||
"src/module",
|
||||
"src/bootstrap",
|
||||
"test/all_spec",
|
||||
"benchmarks/src/tree/ng2/index_aot.ts",
|
||||
"benchmarks/src/tree/ng2_switch/index_aot.ts",
|
||||
"benchmarks/src/largetable/ng2/index_aot.ts",
|
||||
"benchmarks/src/largetable/ng2_switch/index_aot.ts"
|
||||
]
|
||||
}
|
@ -11,7 +11,7 @@
|
||||
* Intended to be used in a build step.
|
||||
*/
|
||||
import * as compiler from '@angular/compiler';
|
||||
import {Directive, NgModule, ViewEncapsulation} from '@angular/core';
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
@ -35,73 +35,16 @@ const PREAMBLE = `/**
|
||||
|
||||
`;
|
||||
|
||||
export class CodeGeneratorModuleCollector {
|
||||
constructor(
|
||||
private staticReflector: StaticReflector, private reflectorHost: StaticReflectorHost,
|
||||
private program: ts.Program, private options: AngularCompilerOptions) {}
|
||||
|
||||
getModuleSymbols(program: ts.Program): {fileMetas: FileMetadata[], ngModules: StaticSymbol[]} {
|
||||
// Compare with false since the default should be true
|
||||
const skipFileNames = (this.options.generateCodeForLibraries === false) ?
|
||||
GENERATED_OR_DTS_FILES :
|
||||
GENERATED_FILES;
|
||||
let filePaths = this.program.getSourceFiles()
|
||||
.filter(sf => !skipFileNames.test(sf.fileName))
|
||||
.map(sf => this.reflectorHost.getCanonicalFileName(sf.fileName));
|
||||
const fileMetas = filePaths.map((filePath) => this.readFileMetadata(filePath));
|
||||
const ngModules = fileMetas.reduce((ngModules, fileMeta) => {
|
||||
ngModules.push(...fileMeta.ngModules);
|
||||
return ngModules;
|
||||
}, <StaticSymbol[]>[]);
|
||||
return {fileMetas, ngModules};
|
||||
}
|
||||
|
||||
private readFileMetadata(absSourcePath: string): FileMetadata {
|
||||
const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath);
|
||||
const result: FileMetadata = {directives: [], ngModules: [], fileUrl: absSourcePath};
|
||||
if (!moduleMetadata) {
|
||||
console.log(`WARNING: no metadata found for ${absSourcePath}`);
|
||||
return result;
|
||||
}
|
||||
const metadata = moduleMetadata['metadata'];
|
||||
const symbols = metadata && Object.keys(metadata);
|
||||
if (!symbols || !symbols.length) {
|
||||
return result;
|
||||
}
|
||||
for (const symbol of symbols) {
|
||||
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
|
||||
// Ignore symbols that are only included to record error information.
|
||||
continue;
|
||||
}
|
||||
const staticType = this.reflectorHost.findDeclaration(absSourcePath, symbol, absSourcePath);
|
||||
const annotations = this.staticReflector.annotations(staticType);
|
||||
annotations.forEach((annotation) => {
|
||||
if (annotation instanceof NgModule) {
|
||||
result.ngModules.push(staticType);
|
||||
} else if (annotation instanceof Directive) {
|
||||
result.directives.push(staticType);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export class CodeGenerator {
|
||||
private moduleCollector: CodeGeneratorModuleCollector;
|
||||
|
||||
constructor(
|
||||
private options: AngularCompilerOptions, private program: ts.Program,
|
||||
public host: ts.CompilerHost, private staticReflector: StaticReflector,
|
||||
private compiler: compiler.OfflineCompiler, private reflectorHost: StaticReflectorHost) {
|
||||
this.moduleCollector =
|
||||
new CodeGeneratorModuleCollector(staticReflector, reflectorHost, program, options);
|
||||
}
|
||||
private compiler: compiler.OfflineCompiler, private reflectorHost: StaticReflectorHost) {}
|
||||
|
||||
// Write codegen in a directory structure matching the sources.
|
||||
private calculateEmitPath(filePath: string): string {
|
||||
let root = this.options.basePath;
|
||||
for (let eachRootDir of this.options.rootDirs || []) {
|
||||
for (const eachRootDir of this.options.rootDirs || []) {
|
||||
if (this.options.trace) {
|
||||
console.log(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
||||
}
|
||||
@ -111,31 +54,28 @@ export class CodeGenerator {
|
||||
}
|
||||
|
||||
// transplant the codegen path to be inside the `genDir`
|
||||
var relativePath: string = path.relative(root, filePath);
|
||||
let relativePath: string = path.relative(root, filePath);
|
||||
while (relativePath.startsWith('..' + path.sep)) {
|
||||
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
|
||||
// into `genDir`.
|
||||
relativePath = relativePath.substr(3);
|
||||
}
|
||||
|
||||
return path.join(this.options.genDir, relativePath);
|
||||
}
|
||||
|
||||
codegen(): Promise<any> {
|
||||
const {fileMetas, ngModules} = this.moduleCollector.getModuleSymbols(this.program);
|
||||
const analyzedNgModules = this.compiler.analyzeModules(ngModules);
|
||||
return Promise.all(fileMetas.map(
|
||||
(fileMeta) =>
|
||||
this.compiler
|
||||
.compile(
|
||||
fileMeta.fileUrl, analyzedNgModules, fileMeta.directives, fileMeta.ngModules)
|
||||
.then((generatedModules) => {
|
||||
generatedModules.forEach((generatedModule) => {
|
||||
const sourceFile = this.program.getSourceFile(fileMeta.fileUrl);
|
||||
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
|
||||
this.host.writeFile(
|
||||
emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]);
|
||||
});
|
||||
})));
|
||||
codegen(options: {transitiveModules: boolean}): Promise<any> {
|
||||
const staticSymbols =
|
||||
extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options);
|
||||
|
||||
return this.compiler.compileModules(staticSymbols, options).then(generatedModules => {
|
||||
generatedModules.forEach(generatedModule => {
|
||||
const sourceFile = this.program.getSourceFile(generatedModule.fileUrl);
|
||||
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
|
||||
this.host.writeFile(
|
||||
emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static create(
|
||||
@ -193,7 +133,9 @@ export class CodeGenerator {
|
||||
// TODO(vicb): do not pass cliOptions.i18nFormat here
|
||||
const offlineCompiler = new compiler.OfflineCompiler(
|
||||
resolver, normalizer, tmplParser, new compiler.StyleCompiler(urlResolver),
|
||||
new compiler.ViewCompiler(config), new compiler.DirectiveWrapperCompiler(config),
|
||||
new compiler.ViewCompiler(config, elementSchemaRegistry),
|
||||
new compiler.DirectiveWrapperCompiler(
|
||||
config, expressionParser, elementSchemaRegistry, console),
|
||||
new compiler.NgModuleCompiler(), new compiler.TypeScriptEmitter(reflectorHost),
|
||||
cliOptions.locale, cliOptions.i18nFormat);
|
||||
|
||||
@ -202,8 +144,40 @@ export class CodeGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
export interface FileMetadata {
|
||||
fileUrl: string;
|
||||
directives: StaticSymbol[];
|
||||
ngModules: StaticSymbol[];
|
||||
}
|
||||
export function extractProgramSymbols(
|
||||
program: ts.Program, staticReflector: StaticReflector, reflectorHost: StaticReflectorHost,
|
||||
options: AngularCompilerOptions): StaticSymbol[] {
|
||||
// Compare with false since the default should be true
|
||||
const skipFileNames =
|
||||
options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
|
||||
|
||||
const staticSymbols: StaticSymbol[] = [];
|
||||
|
||||
program.getSourceFiles()
|
||||
.filter(sourceFile => !skipFileNames.test(sourceFile.fileName))
|
||||
.forEach(sourceFile => {
|
||||
const absSrcPath = reflectorHost.getCanonicalFileName(sourceFile.fileName);
|
||||
|
||||
const moduleMetadata = staticReflector.getModuleMetadata(absSrcPath);
|
||||
if (!moduleMetadata) {
|
||||
console.log(`WARNING: no metadata found for ${absSrcPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const metadata = moduleMetadata['metadata'];
|
||||
|
||||
if (!metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const symbol of Object.keys(metadata)) {
|
||||
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
|
||||
// Ignore symbols that are only included to record error information.
|
||||
continue;
|
||||
}
|
||||
staticSymbols.push(reflectorHost.findDeclaration(absSrcPath, symbol, absSrcPath));
|
||||
}
|
||||
});
|
||||
|
||||
return staticSymbols;
|
||||
}
|
@ -10,27 +10,32 @@
|
||||
|
||||
/**
|
||||
* Extract i18n messages from source code
|
||||
*
|
||||
* TODO(vicb): factorize code with the CodeGenerator
|
||||
*/
|
||||
// Must be imported first, because angular2 decorators throws on load.
|
||||
import 'reflect-metadata';
|
||||
|
||||
import * as compiler from '@angular/compiler';
|
||||
import {Component, NgModule, ViewEncapsulation} from '@angular/core';
|
||||
import * as tsc from '@angular/tsc-wrapped';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
import * as tsc from '@angular/tsc-wrapped';
|
||||
import {Console} from './private_import_core';
|
||||
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
|
||||
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
||||
import {StaticReflector, StaticSymbol} from './static_reflector';
|
||||
|
||||
import {Extractor} from './extractor';
|
||||
|
||||
function extract(
|
||||
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions,
|
||||
program: ts.Program, host: ts.CompilerHost) {
|
||||
const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser());
|
||||
const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host, htmlParser);
|
||||
const resourceLoader: compiler.ResourceLoader = {
|
||||
get: (s: string) => {
|
||||
if (!host.fileExists(s)) {
|
||||
// TODO: We should really have a test for error cases like this!
|
||||
throw new Error(`Compilation failed. Resource file not found: ${s}`);
|
||||
}
|
||||
return Promise.resolve(host.readFile(s));
|
||||
}
|
||||
};
|
||||
const extractor =
|
||||
Extractor.create(ngOptions, cliOptions.i18nFormat, program, host, resourceLoader);
|
||||
|
||||
const bundlePromise: Promise<compiler.MessageBundle> = extractor.extract();
|
||||
|
||||
return (bundlePromise).then(messageBundle => {
|
||||
@ -46,6 +51,7 @@ function extract(
|
||||
case 'xliff':
|
||||
case 'xlf':
|
||||
default:
|
||||
const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser());
|
||||
ext = 'xlf';
|
||||
serializer = new compiler.Xliff(htmlParser, compiler.DEFAULT_INTERPOLATION_CONFIG);
|
||||
break;
|
||||
@ -56,139 +62,6 @@ function extract(
|
||||
});
|
||||
}
|
||||
|
||||
const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
|
||||
|
||||
export class Extractor {
|
||||
constructor(
|
||||
private program: ts.Program, public host: ts.CompilerHost,
|
||||
private staticReflector: StaticReflector, private messageBundle: compiler.MessageBundle,
|
||||
private reflectorHost: ReflectorHost,
|
||||
private metadataResolver: compiler.CompileMetadataResolver,
|
||||
private directiveNormalizer: compiler.DirectiveNormalizer) {}
|
||||
|
||||
private readFileMetadata(absSourcePath: string): FileMetadata {
|
||||
const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath);
|
||||
const result: FileMetadata = {components: [], ngModules: [], fileUrl: absSourcePath};
|
||||
if (!moduleMetadata) {
|
||||
console.log(`WARNING: no metadata found for ${absSourcePath}`);
|
||||
return result;
|
||||
}
|
||||
const metadata = moduleMetadata['metadata'];
|
||||
const symbols = metadata && Object.keys(metadata);
|
||||
if (!symbols || !symbols.length) {
|
||||
return result;
|
||||
}
|
||||
for (const symbol of symbols) {
|
||||
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
|
||||
// Ignore symbols that are only included to record error information.
|
||||
continue;
|
||||
}
|
||||
const staticType = this.reflectorHost.findDeclaration(absSourcePath, symbol, absSourcePath);
|
||||
const annotations = this.staticReflector.annotations(staticType);
|
||||
annotations.forEach((annotation) => {
|
||||
if (annotation instanceof NgModule) {
|
||||
result.ngModules.push(staticType);
|
||||
} else if (annotation instanceof Component) {
|
||||
result.components.push(staticType);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
extract(): Promise<compiler.MessageBundle> {
|
||||
const filePaths =
|
||||
this.program.getSourceFiles().map(sf => sf.fileName).filter(f => !GENERATED_FILES.test(f));
|
||||
const fileMetas = filePaths.map((filePath) => this.readFileMetadata(filePath));
|
||||
const ngModules = fileMetas.reduce((ngModules, fileMeta) => {
|
||||
ngModules.push(...fileMeta.ngModules);
|
||||
return ngModules;
|
||||
}, <StaticSymbol[]>[]);
|
||||
const analyzedNgModules = compiler.analyzeModules(ngModules, this.metadataResolver);
|
||||
const errors: compiler.ParseError[] = [];
|
||||
|
||||
let bundlePromise =
|
||||
Promise
|
||||
.all(fileMetas.map((fileMeta) => {
|
||||
const url = fileMeta.fileUrl;
|
||||
return Promise.all(fileMeta.components.map(compType => {
|
||||
const compMeta = this.metadataResolver.getDirectiveMetadata(<any>compType);
|
||||
const ngModule = analyzedNgModules.ngModuleByDirective.get(compType);
|
||||
if (!ngModule) {
|
||||
throw new Error(
|
||||
`Cannot determine the module for component ${compMeta.type.name}!`);
|
||||
}
|
||||
return Promise
|
||||
.all([compMeta, ...ngModule.transitiveModule.directives].map(
|
||||
dirMeta =>
|
||||
this.directiveNormalizer.normalizeDirective(dirMeta).asyncResult))
|
||||
.then((normalizedCompWithDirectives) => {
|
||||
const compMeta = normalizedCompWithDirectives[0];
|
||||
const html = compMeta.template.template;
|
||||
const interpolationConfig =
|
||||
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
|
||||
errors.push(
|
||||
...this.messageBundle.updateFromTemplate(html, url, interpolationConfig));
|
||||
});
|
||||
}));
|
||||
}))
|
||||
.then(_ => this.messageBundle);
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(errors.map(e => e.toString()).join('\n'));
|
||||
}
|
||||
|
||||
return bundlePromise;
|
||||
}
|
||||
|
||||
static create(
|
||||
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program,
|
||||
compilerHost: ts.CompilerHost, htmlParser: compiler.I18NHtmlParser,
|
||||
reflectorHostContext?: ReflectorHostContext): Extractor {
|
||||
const resourceLoader: compiler.ResourceLoader = {
|
||||
get: (s: string) => {
|
||||
if (!compilerHost.fileExists(s)) {
|
||||
// TODO: We should really have a test for error cases like this!
|
||||
throw new Error(`Compilation failed. Resource file not found: ${s}`);
|
||||
}
|
||||
return Promise.resolve(compilerHost.readFile(s));
|
||||
}
|
||||
};
|
||||
|
||||
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
|
||||
const reflectorHost = new ReflectorHost(program, compilerHost, options, reflectorHostContext);
|
||||
const staticReflector = new StaticReflector(reflectorHost);
|
||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||
|
||||
const config = new compiler.CompilerConfig({
|
||||
genDebugInfo: options.debug === true,
|
||||
defaultEncapsulation: ViewEncapsulation.Emulated,
|
||||
logBindingUpdate: false,
|
||||
useJit: false
|
||||
});
|
||||
|
||||
const normalizer =
|
||||
new compiler.DirectiveNormalizer(resourceLoader, urlResolver, htmlParser, config);
|
||||
const elementSchemaRegistry = new compiler.DomElementSchemaRegistry();
|
||||
const resolver = new compiler.CompileMetadataResolver(
|
||||
new compiler.NgModuleResolver(staticReflector),
|
||||
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
||||
elementSchemaRegistry, staticReflector);
|
||||
|
||||
// TODO(vicb): implicit tags & attributes
|
||||
let messageBundle = new compiler.MessageBundle(htmlParser, [], {});
|
||||
|
||||
return new Extractor(
|
||||
program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver, normalizer);
|
||||
}
|
||||
}
|
||||
|
||||
interface FileMetadata {
|
||||
fileUrl: string;
|
||||
components: StaticSymbol[];
|
||||
ngModules: StaticSymbol[];
|
||||
}
|
||||
|
||||
// Entry point
|
||||
if (require.main === module) {
|
||||
const args = require('minimist')(process.argv.slice(2));
|
||||
|
110
modules/@angular/compiler-cli/src/extractor.ts
Normal file
110
modules/@angular/compiler-cli/src/extractor.ts
Normal file
@ -0,0 +1,110 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Extract i18n messages from source code
|
||||
*/
|
||||
// Must be imported first, because angular2 decorators throws on load.
|
||||
import 'reflect-metadata';
|
||||
|
||||
import * as compiler from '@angular/compiler';
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
import * as tsc from '@angular/tsc-wrapped';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {extractProgramSymbols} from './codegen';
|
||||
import {ReflectorHost} from './reflector_host';
|
||||
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
||||
import {StaticReflector, StaticSymbol} from './static_reflector';
|
||||
|
||||
export class Extractor {
|
||||
constructor(
|
||||
private options: tsc.AngularCompilerOptions, private program: ts.Program,
|
||||
public host: ts.CompilerHost, private staticReflector: StaticReflector,
|
||||
private messageBundle: compiler.MessageBundle, private reflectorHost: ReflectorHost,
|
||||
private metadataResolver: compiler.CompileMetadataResolver,
|
||||
private directiveNormalizer: compiler.DirectiveNormalizer) {}
|
||||
|
||||
extract(): Promise<compiler.MessageBundle> {
|
||||
const programSymbols: StaticSymbol[] =
|
||||
extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options);
|
||||
|
||||
const files =
|
||||
compiler.analyzeNgModules(programSymbols, {transitiveModules: true}, this.metadataResolver)
|
||||
.files;
|
||||
const errors: compiler.ParseError[] = [];
|
||||
const filePromises: Promise<any>[] = [];
|
||||
|
||||
files.forEach(file => {
|
||||
const cmpPromises: Promise<compiler.CompileDirectiveMetadata>[] = [];
|
||||
file.directives.forEach(directiveType => {
|
||||
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
|
||||
if (dirMeta.isComponent) {
|
||||
cmpPromises.push(this.directiveNormalizer.normalizeDirective(dirMeta).asyncResult);
|
||||
}
|
||||
});
|
||||
|
||||
if (cmpPromises.length) {
|
||||
const done =
|
||||
Promise.all(cmpPromises).then((compMetas: compiler.CompileDirectiveMetadata[]) => {
|
||||
compMetas.forEach(compMeta => {
|
||||
const html = compMeta.template.template;
|
||||
const interpolationConfig =
|
||||
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
|
||||
errors.push(...this.messageBundle.updateFromTemplate(
|
||||
html, file.srcUrl, interpolationConfig));
|
||||
});
|
||||
});
|
||||
|
||||
filePromises.push(done);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(errors.map(e => e.toString()).join('\n'));
|
||||
}
|
||||
|
||||
return Promise.all(filePromises).then(_ => this.messageBundle);
|
||||
}
|
||||
|
||||
static create(
|
||||
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program,
|
||||
compilerHost: ts.CompilerHost, resourceLoader: compiler.ResourceLoader,
|
||||
reflectorHost?: ReflectorHost): Extractor {
|
||||
const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser());
|
||||
|
||||
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
|
||||
if (!reflectorHost) reflectorHost = new ReflectorHost(program, compilerHost, options);
|
||||
const staticReflector = new StaticReflector(reflectorHost);
|
||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||
|
||||
const config = new compiler.CompilerConfig({
|
||||
genDebugInfo: options.debug === true,
|
||||
defaultEncapsulation: ViewEncapsulation.Emulated,
|
||||
logBindingUpdate: false,
|
||||
useJit: false
|
||||
});
|
||||
|
||||
const normalizer =
|
||||
new compiler.DirectiveNormalizer(resourceLoader, urlResolver, htmlParser, config);
|
||||
const elementSchemaRegistry = new compiler.DomElementSchemaRegistry();
|
||||
const resolver = new compiler.CompileMetadataResolver(
|
||||
new compiler.NgModuleResolver(staticReflector),
|
||||
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
||||
elementSchemaRegistry, staticReflector);
|
||||
|
||||
// TODO(vicb): implicit tags & attributes
|
||||
let messageBundle = new compiler.MessageBundle(htmlParser, [], {});
|
||||
|
||||
return new Extractor(
|
||||
options, program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver,
|
||||
normalizer);
|
||||
}
|
||||
}
|
@ -19,7 +19,9 @@ import {CodeGenerator} from './codegen';
|
||||
function codegen(
|
||||
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program,
|
||||
host: ts.CompilerHost) {
|
||||
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen();
|
||||
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen({
|
||||
transitiveModules: true
|
||||
});
|
||||
}
|
||||
|
||||
// CLI entry point
|
||||
|
@ -250,6 +250,12 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
|
||||
} else {
|
||||
const sf = this.program.getSourceFile(filePath);
|
||||
if (!sf) {
|
||||
if (this.context.fileExists(filePath)) {
|
||||
const sourceText = this.context.readFile(filePath);
|
||||
return this.metadataCollector.getMetadata(
|
||||
ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true));
|
||||
}
|
||||
|
||||
throw new Error(`Source file ${filePath} not present in program.`);
|
||||
}
|
||||
return this.metadataCollector.getMetadata(sf);
|
||||
|
@ -25,7 +25,7 @@ export interface StaticReflectorHost {
|
||||
* @param modulePath is a string identifier for a module as an absolute path.
|
||||
* @returns the metadata for the given module.
|
||||
*/
|
||||
getMetadataFor(modulePath: string): {[key: string]: any};
|
||||
getMetadataFor(modulePath: string): {[key: string]: any}|{[key: string]: any}[];
|
||||
|
||||
/**
|
||||
* Resolve a symbol from an import statement form, to the file where it is declared.
|
||||
@ -72,13 +72,12 @@ export class StaticReflector implements ReflectorReader {
|
||||
constructor(private host: StaticReflectorHost) { this.initializeConversionMap(); }
|
||||
|
||||
importUri(typeOrFunc: StaticSymbol): string {
|
||||
var staticSymbol = this.host.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, '');
|
||||
const staticSymbol = this.host.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, '');
|
||||
return staticSymbol ? staticSymbol.filePath : null;
|
||||
}
|
||||
|
||||
resolveIdentifier(name: string, moduleUrl: string, runtime: any): any {
|
||||
const result = this.host.findDeclaration(moduleUrl, name, '');
|
||||
return result;
|
||||
return this.host.findDeclaration(moduleUrl, name, '');
|
||||
}
|
||||
|
||||
resolveEnum(enumIdentifier: any, name: string): any {
|
||||
@ -238,9 +237,9 @@ export class StaticReflector implements ReflectorReader {
|
||||
|
||||
/** @internal */
|
||||
public simplify(context: StaticSymbol, value: any): any {
|
||||
let _this = this;
|
||||
const _this = this;
|
||||
let scope = BindingScope.empty;
|
||||
let calling = new Map<StaticSymbol, boolean>();
|
||||
const calling = new Map<StaticSymbol, boolean>();
|
||||
|
||||
function simplifyInContext(context: StaticSymbol, value: any, depth: number): any {
|
||||
function resolveReference(context: StaticSymbol, expression: any): StaticSymbol {
|
||||
@ -255,16 +254,15 @@ export class StaticReflector implements ReflectorReader {
|
||||
}
|
||||
|
||||
function resolveReferenceValue(staticSymbol: StaticSymbol): any {
|
||||
let result: any = staticSymbol;
|
||||
let moduleMetadata = _this.getModuleMetadata(staticSymbol.filePath);
|
||||
let declarationValue =
|
||||
const moduleMetadata = _this.getModuleMetadata(staticSymbol.filePath);
|
||||
const declarationValue =
|
||||
moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null;
|
||||
return declarationValue;
|
||||
}
|
||||
|
||||
function isOpaqueToken(context: StaticSymbol, value: any): boolean {
|
||||
if (value && value.__symbolic === 'new' && value.expression) {
|
||||
let target = value.expression;
|
||||
const target = value.expression;
|
||||
if (target.__symbolic == 'reference') {
|
||||
return sameSymbol(resolveReference(context, target), _this.opaqueToken);
|
||||
}
|
||||
@ -535,7 +533,7 @@ export class StaticReflector implements ReflectorReader {
|
||||
}
|
||||
}
|
||||
|
||||
let result = simplifyInContext(context, value, 0);
|
||||
const result = simplifyInContext(context, value, 0);
|
||||
if (shouldIgnore(result)) {
|
||||
return undefined;
|
||||
}
|
||||
@ -550,8 +548,7 @@ export class StaticReflector implements ReflectorReader {
|
||||
if (!moduleMetadata) {
|
||||
moduleMetadata = this.host.getMetadataFor(module);
|
||||
if (Array.isArray(moduleMetadata)) {
|
||||
moduleMetadata = (<Array<any>>moduleMetadata)
|
||||
.find(element => element.version === SUPPORTED_SCHEMA_VERSION) ||
|
||||
moduleMetadata = moduleMetadata.find(md => md['version'] === SUPPORTED_SCHEMA_VERSION) ||
|
||||
moduleMetadata[0];
|
||||
}
|
||||
if (!moduleMetadata) {
|
||||
@ -568,12 +565,8 @@ export class StaticReflector implements ReflectorReader {
|
||||
}
|
||||
|
||||
private getTypeMetadata(type: StaticSymbol): {[key: string]: any} {
|
||||
let moduleMetadata = this.getModuleMetadata(type.filePath);
|
||||
let result = moduleMetadata['metadata'][type.name];
|
||||
if (!result) {
|
||||
result = {__symbolic: 'class'};
|
||||
}
|
||||
return result;
|
||||
const moduleMetadata = this.getModuleMetadata(type.filePath);
|
||||
return moduleMetadata['metadata'][type.name] || {__symbolic: 'class'};
|
||||
}
|
||||
}
|
||||
|
||||
@ -613,7 +606,7 @@ function produceErrorMessage(error: any): string {
|
||||
function mapStringMap(input: {[key: string]: any}, transform: (value: any, key: string) => any):
|
||||
{[key: string]: any} {
|
||||
if (!input) return {};
|
||||
var result: {[key: string]: any} = {};
|
||||
const result: {[key: string]: any} = {};
|
||||
Object.keys(input).forEach((key) => {
|
||||
let value = transform(input[key], key);
|
||||
if (!shouldIgnore(value)) {
|
||||
@ -638,8 +631,7 @@ abstract class BindingScope {
|
||||
public static empty: BindingScope = {resolve: name => BindingScope.missing};
|
||||
|
||||
public static build(): BindingScopeBuilder {
|
||||
let current = new Map<string, any>();
|
||||
let parent: BindingScope = undefined;
|
||||
const current = new Map<string, any>();
|
||||
return {
|
||||
define: function(name, value) {
|
||||
current.set(name, value);
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler-cli/src/static_reflector';
|
||||
import {HostListener, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
|
||||
import {ListWrapper} from '@angular/facade/src/collection';
|
||||
import {MetadataCollector} from '@angular/tsc-wrapped';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
@ -474,7 +473,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
|
||||
function resolvePath(pathParts: string[]): string {
|
||||
let result: string[] = [];
|
||||
ListWrapper.forEachWithIndex(pathParts, (part, index) => {
|
||||
pathParts.forEach((part, index) => {
|
||||
switch (part) {
|
||||
case '':
|
||||
case '.':
|
||||
|
@ -34,9 +34,8 @@ export {DirectiveResolver} from './src/directive_resolver';
|
||||
export {PipeResolver} from './src/pipe_resolver';
|
||||
export {NgModuleResolver} from './src/ng_module_resolver';
|
||||
export {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './src/ml_parser/interpolation_config';
|
||||
export {ElementSchemaRegistry} from './src/schema/element_schema_registry';
|
||||
export * from './src/schema/element_schema_registry';
|
||||
export * from './src/i18n/index';
|
||||
export * from './src/template_parser/template_ast';
|
||||
export * from './src/directive_normalizer';
|
||||
export * from './src/expression_parser/lexer';
|
||||
export * from './src/expression_parser/parser';
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata} from '../compile_metadata';
|
||||
import {ListWrapper, StringMapWrapper} from '../facade/collection';
|
||||
import {StringMapWrapper} from '../facade/collection';
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
import {ParseError} from '../parse_util';
|
||||
import {ANY_STATE, FILL_STYLE_FLAG} from '../private_import_core';
|
||||
@ -180,8 +180,7 @@ function _normalizeStyleMetadata(
|
||||
var normalizedStyles: {[key: string]: string | number}[] = [];
|
||||
entry.styles.forEach(styleEntry => {
|
||||
if (typeof styleEntry === 'string') {
|
||||
ListWrapper.addAll(
|
||||
normalizedStyles, _resolveStylesFromState(<string>styleEntry, stateStyles, errors));
|
||||
normalizedStyles.push(..._resolveStylesFromState(<string>styleEntry, stateStyles, errors));
|
||||
} else {
|
||||
normalizedStyles.push(<{[key: string]: string | number}>styleEntry);
|
||||
}
|
||||
@ -346,12 +345,12 @@ function _parseAnimationKeyframes(
|
||||
});
|
||||
|
||||
if (doSortKeyframes) {
|
||||
ListWrapper.sort(rawKeyframes, (a, b) => a[0] <= b[0] ? -1 : 1);
|
||||
rawKeyframes.sort((a, b) => a[0] <= b[0] ? -1 : 1);
|
||||
}
|
||||
|
||||
var firstKeyframe = rawKeyframes[0];
|
||||
if (firstKeyframe[0] != _INITIAL_KEYFRAME) {
|
||||
ListWrapper.insert(rawKeyframes, 0, firstKeyframe = [_INITIAL_KEYFRAME, {}]);
|
||||
rawKeyframes.splice(0, 0, firstKeyframe = [_INITIAL_KEYFRAME, {}]);
|
||||
}
|
||||
|
||||
var firstKeyframeStyles = firstKeyframe[1];
|
||||
@ -421,7 +420,7 @@ function _parseTransitionAnimation(
|
||||
steps.push(new AnimationStepAst(startingStyles, [], 0, 0, ''));
|
||||
} else {
|
||||
var innerStep = <AnimationStepAst>innerAst;
|
||||
ListWrapper.addAll(innerStep.startingStyles.styles, previousStyles);
|
||||
innerStep.startingStyles.styles.push(...previousStyles);
|
||||
}
|
||||
previousStyles = null;
|
||||
}
|
||||
|
@ -6,7 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {isPresent} from '../facade/lang';
|
||||
|
||||
export class StylesCollectionEntry {
|
||||
@ -37,7 +36,7 @@ export class StylesCollection {
|
||||
}
|
||||
}
|
||||
|
||||
ListWrapper.insert(entries, insertionIndex, tuple);
|
||||
entries.splice(insertionIndex, 0, tuple);
|
||||
}
|
||||
|
||||
getByIndex(property: string, index: number): StylesCollectionEntry {
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {ListWrapper, MapWrapper} from './facade/collection';
|
||||
import {isPresent, normalizeBlank, normalizeBool} from './facade/lang';
|
||||
import {isPresent} from './facade/lang';
|
||||
import {LifecycleHooks} from './private_import_core';
|
||||
import {CssSelector} from './selector';
|
||||
import {sanitizeIdentifier, splitAtColon} from './util';
|
||||
@ -23,7 +23,6 @@ function unimplemented(): any {
|
||||
// group 2: "event" from "(event)"
|
||||
// group 3: "@trigger" from "@trigger"
|
||||
const HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/;
|
||||
const UNDEFINED = new Object();
|
||||
|
||||
export abstract class CompileMetadataWithIdentifier {
|
||||
get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); }
|
||||
@ -125,12 +124,12 @@ export class CompileDiDependencyMetadata {
|
||||
token?: CompileTokenMetadata,
|
||||
value?: any
|
||||
} = {}) {
|
||||
this.isAttribute = normalizeBool(isAttribute);
|
||||
this.isSelf = normalizeBool(isSelf);
|
||||
this.isHost = normalizeBool(isHost);
|
||||
this.isSkipSelf = normalizeBool(isSkipSelf);
|
||||
this.isOptional = normalizeBool(isOptional);
|
||||
this.isValue = normalizeBool(isValue);
|
||||
this.isAttribute = !!isAttribute;
|
||||
this.isSelf = !!isSelf;
|
||||
this.isHost = !!isHost;
|
||||
this.isSkipSelf = !!isSkipSelf;
|
||||
this.isOptional = !!isOptional;
|
||||
this.isValue = !!isValue;
|
||||
this.query = query;
|
||||
this.viewQuery = viewQuery;
|
||||
this.token = token;
|
||||
@ -161,8 +160,8 @@ export class CompileProviderMetadata {
|
||||
this.useValue = useValue;
|
||||
this.useExisting = useExisting;
|
||||
this.useFactory = useFactory;
|
||||
this.deps = normalizeBlank(deps);
|
||||
this.multi = normalizeBool(multi);
|
||||
this.deps = deps || null;
|
||||
this.multi = !!multi;
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,7 +191,7 @@ export class CompileTokenMetadata implements CompileMetadataWithIdentifier {
|
||||
{value?: any, identifier?: CompileIdentifierMetadata, identifierIsInstance?: boolean}) {
|
||||
this.value = value;
|
||||
this.identifier = identifier;
|
||||
this.identifierIsInstance = normalizeBool(identifierIsInstance);
|
||||
this.identifierIsInstance = !!identifierIsInstance;
|
||||
}
|
||||
|
||||
get reference(): any {
|
||||
@ -227,7 +226,7 @@ export class CompileTypeMetadata extends CompileIdentifierMetadata {
|
||||
lifecycleHooks?: LifecycleHooks[];
|
||||
} = {}) {
|
||||
super({reference: reference, name: name, moduleUrl: moduleUrl, prefix: prefix, value: value});
|
||||
this.isHost = normalizeBool(isHost);
|
||||
this.isHost = !!isHost;
|
||||
this.diDeps = _normalizeArray(diDeps);
|
||||
this.lifecycleHooks = _normalizeArray(lifecycleHooks);
|
||||
}
|
||||
@ -248,8 +247,8 @@ export class CompileQueryMetadata {
|
||||
read?: CompileTokenMetadata
|
||||
} = {}) {
|
||||
this.selectors = selectors;
|
||||
this.descendants = normalizeBool(descendants);
|
||||
this.first = normalizeBool(first);
|
||||
this.descendants = !!descendants;
|
||||
this.first = !!first;
|
||||
this.propertyName = propertyName;
|
||||
this.read = read;
|
||||
}
|
||||
@ -303,9 +302,9 @@ export class CompileTemplateMetadata {
|
||||
this.styles = _normalizeArray(styles);
|
||||
this.styleUrls = _normalizeArray(styleUrls);
|
||||
this.externalStylesheets = _normalizeArray(externalStylesheets);
|
||||
this.animations = isPresent(animations) ? ListWrapper.flatten(animations) : [];
|
||||
this.animations = animations ? ListWrapper.flatten(animations) : [];
|
||||
this.ngContentSelectors = ngContentSelectors || [];
|
||||
if (isPresent(interpolation) && interpolation.length != 2) {
|
||||
if (interpolation && interpolation.length != 2) {
|
||||
throw new Error(`'interpolation' should have a start and an end symbol.`);
|
||||
}
|
||||
this.interpolation = interpolation;
|
||||
@ -375,7 +374,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
||||
|
||||
return new CompileDirectiveMetadata({
|
||||
type,
|
||||
isComponent: normalizeBool(isComponent), selector, exportAs, changeDetection,
|
||||
isComponent: !!isComponent, selector, exportAs, changeDetection,
|
||||
inputs: inputsMap,
|
||||
outputs: outputsMap,
|
||||
hostListeners,
|
||||
@ -503,13 +502,13 @@ export class CompilePipeMetadata implements CompileMetadataWithIdentifier {
|
||||
} = {}) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.pure = normalizeBool(pure);
|
||||
this.pure = !!pure;
|
||||
}
|
||||
get identifier(): CompileIdentifierMetadata { return this.type; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata regarding compilation of a directive.
|
||||
* Metadata regarding compilation of a module.
|
||||
*/
|
||||
export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
|
||||
type: CompileTypeMetadata;
|
||||
@ -569,6 +568,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
|
||||
export class TransitiveCompileNgModuleMetadata {
|
||||
directivesSet = new Set<Type<any>>();
|
||||
pipesSet = new Set<Type<any>>();
|
||||
|
||||
constructor(
|
||||
public modules: CompileNgModuleMetadata[], public providers: CompileProviderMetadata[],
|
||||
public entryComponents: CompileTypeMetadata[], public directives: CompileDirectiveMetadata[],
|
||||
@ -581,11 +581,13 @@ export class TransitiveCompileNgModuleMetadata {
|
||||
export function removeIdentifierDuplicates<T extends CompileMetadataWithIdentifier>(items: T[]):
|
||||
T[] {
|
||||
const map = new Map<any, T>();
|
||||
|
||||
items.forEach((item) => {
|
||||
if (!map.get(item.identifier.reference)) {
|
||||
map.set(item.identifier.reference, item);
|
||||
}
|
||||
});
|
||||
|
||||
return MapWrapper.values(map);
|
||||
}
|
||||
|
||||
|
48
modules/@angular/compiler/src/compiler_util/binding_util.ts
Normal file
48
modules/@angular/compiler/src/compiler_util/binding_util.ts
Normal file
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @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 {Identifiers, resolveIdentifier} from '../identifiers';
|
||||
import {ClassBuilder} from '../output/class_builder';
|
||||
import * as o from '../output/output_ast';
|
||||
|
||||
import {ConvertPropertyBindingResult} from './expression_converter';
|
||||
|
||||
export class CheckBindingField {
|
||||
constructor(public expression: o.ReadPropExpr, public bindingId: string) {}
|
||||
}
|
||||
|
||||
export function createCheckBindingField(builder: ClassBuilder): CheckBindingField {
|
||||
const bindingId = `${builder.fields.length}`;
|
||||
const fieldExpr = createBindFieldExpr(bindingId);
|
||||
// private is fine here as no child view will reference the cached value...
|
||||
builder.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private]));
|
||||
builder.ctorStmts.push(o.THIS_EXPR.prop(fieldExpr.name)
|
||||
.set(o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED)))
|
||||
.toStmt());
|
||||
return new CheckBindingField(fieldExpr, bindingId);
|
||||
}
|
||||
|
||||
export function createCheckBindingStmt(
|
||||
evalResult: ConvertPropertyBindingResult, fieldExpr: o.ReadPropExpr,
|
||||
throwOnChangeVar: o.Expression, actions: o.Statement[]): o.Statement[] {
|
||||
var condition: o.Expression = o.importExpr(resolveIdentifier(Identifiers.checkBinding)).callFn([
|
||||
throwOnChangeVar, fieldExpr, evalResult.currValExpr
|
||||
]);
|
||||
if (evalResult.forceUpdate) {
|
||||
condition = evalResult.forceUpdate.or(condition);
|
||||
}
|
||||
return [
|
||||
...evalResult.stmts, new o.IfStmt(condition, actions.concat([
|
||||
<o.Statement>o.THIS_EXPR.prop(fieldExpr.name).set(evalResult.currValExpr).toStmt()
|
||||
]))
|
||||
];
|
||||
}
|
||||
|
||||
function createBindFieldExpr(bindingId: string): o.ReadPropExpr {
|
||||
return o.THIS_EXPR.prop(`_expr_${bindingId}`);
|
||||
}
|
@ -10,52 +10,130 @@
|
||||
import * as cdAst from '../expression_parser/ast';
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
import {Identifiers, resolveIdentifier} from '../identifiers';
|
||||
import {ClassBuilder} from '../output/class_builder';
|
||||
import * as o from '../output/output_ast';
|
||||
|
||||
import {createPureProxy} from './identifier_util';
|
||||
|
||||
const VAL_UNWRAPPER_VAR = o.variable(`valUnwrapper`);
|
||||
|
||||
export interface NameResolver {
|
||||
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression;
|
||||
getLocal(name: string): o.Expression;
|
||||
createLiteralArray(values: o.Expression[]): o.Expression;
|
||||
createLiteralMap(values: Array<Array<string|o.Expression>>): o.Expression;
|
||||
}
|
||||
|
||||
export class ExpressionWithWrappedValueInfo {
|
||||
export class EventHandlerVars { static event = o.variable('$event'); }
|
||||
|
||||
export class ConvertPropertyBindingResult {
|
||||
constructor(
|
||||
public expression: o.Expression, public needsValueUnwrapper: boolean,
|
||||
public temporaryCount: number) {}
|
||||
public stmts: o.Statement[], public currValExpr: o.Expression,
|
||||
public forceUpdate: o.Expression) {}
|
||||
}
|
||||
|
||||
export function convertCdExpressionToIr(
|
||||
nameResolver: NameResolver, implicitReceiver: o.Expression, expression: cdAst.AST,
|
||||
valueUnwrapper: o.ReadVarExpr, bindingIndex: number): ExpressionWithWrappedValueInfo {
|
||||
const visitor = new _AstToIrVisitor(nameResolver, implicitReceiver, valueUnwrapper, bindingIndex);
|
||||
const irAst: o.Expression = expression.visit(visitor, _Mode.Expression);
|
||||
return new ExpressionWithWrappedValueInfo(
|
||||
irAst, visitor.needsValueUnwrapper, visitor.temporaryCount);
|
||||
/**
|
||||
* Converts the given expression AST into an executable output AST, assuming the expression is
|
||||
* used in a property binding.
|
||||
*/
|
||||
export function convertPropertyBinding(
|
||||
builder: ClassBuilder, nameResolver: NameResolver, implicitReceiver: o.Expression,
|
||||
expression: cdAst.AST, bindingId: string): ConvertPropertyBindingResult {
|
||||
const currValExpr = createCurrValueExpr(bindingId);
|
||||
const stmts: o.Statement[] = [];
|
||||
if (!nameResolver) {
|
||||
nameResolver = new DefaultNameResolver();
|
||||
}
|
||||
const visitor = new _AstToIrVisitor(
|
||||
builder, nameResolver, implicitReceiver, VAL_UNWRAPPER_VAR, bindingId, false);
|
||||
const outputExpr: o.Expression = expression.visit(visitor, _Mode.Expression);
|
||||
|
||||
if (!outputExpr) {
|
||||
// e.g. an empty expression was given
|
||||
return null;
|
||||
}
|
||||
|
||||
if (visitor.temporaryCount) {
|
||||
for (let i = 0; i < visitor.temporaryCount; i++) {
|
||||
stmts.push(temporaryDeclaration(bindingId, i));
|
||||
}
|
||||
}
|
||||
|
||||
if (visitor.needsValueUnwrapper) {
|
||||
var initValueUnwrapperStmt = VAL_UNWRAPPER_VAR.callMethod('reset', []).toStmt();
|
||||
stmts.push(initValueUnwrapperStmt);
|
||||
}
|
||||
stmts.push(currValExpr.set(outputExpr).toDeclStmt(null, [o.StmtModifier.Final]));
|
||||
if (visitor.needsValueUnwrapper) {
|
||||
return new ConvertPropertyBindingResult(
|
||||
stmts, currValExpr, VAL_UNWRAPPER_VAR.prop('hasWrappedValue'));
|
||||
} else {
|
||||
return new ConvertPropertyBindingResult(stmts, currValExpr, null);
|
||||
}
|
||||
}
|
||||
|
||||
export function convertCdStatementToIr(
|
||||
nameResolver: NameResolver, implicitReceiver: o.Expression, stmt: cdAst.AST,
|
||||
bindingIndex: number): o.Statement[] {
|
||||
const visitor = new _AstToIrVisitor(nameResolver, implicitReceiver, null, bindingIndex);
|
||||
let statements: o.Statement[] = [];
|
||||
flattenStatements(stmt.visit(visitor, _Mode.Statement), statements);
|
||||
prependTemporaryDecls(visitor.temporaryCount, bindingIndex, statements);
|
||||
return statements;
|
||||
export class ConvertActionBindingResult {
|
||||
constructor(public stmts: o.Statement[], public preventDefault: o.Expression) {}
|
||||
}
|
||||
|
||||
function temporaryName(bindingIndex: number, temporaryNumber: number): string {
|
||||
return `tmp_${bindingIndex}_${temporaryNumber}`;
|
||||
/**
|
||||
* Converts the given expression AST into an executable output AST, assuming the expression is
|
||||
* used in an action binding (e.g. an event handler).
|
||||
*/
|
||||
export function convertActionBinding(
|
||||
builder: ClassBuilder, nameResolver: NameResolver, implicitReceiver: o.Expression,
|
||||
action: cdAst.AST, bindingId: string): ConvertActionBindingResult {
|
||||
if (!nameResolver) {
|
||||
nameResolver = new DefaultNameResolver();
|
||||
}
|
||||
const visitor =
|
||||
new _AstToIrVisitor(builder, nameResolver, implicitReceiver, null, bindingId, true);
|
||||
let actionStmts: o.Statement[] = [];
|
||||
flattenStatements(action.visit(visitor, _Mode.Statement), actionStmts);
|
||||
prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts);
|
||||
var lastIndex = actionStmts.length - 1;
|
||||
var preventDefaultVar: o.ReadVarExpr = null;
|
||||
if (lastIndex >= 0) {
|
||||
var lastStatement = actionStmts[lastIndex];
|
||||
var returnExpr = convertStmtIntoExpression(lastStatement);
|
||||
if (returnExpr) {
|
||||
// Note: We need to cast the result of the method call to dynamic,
|
||||
// as it might be a void method!
|
||||
preventDefaultVar = createPreventDefaultVar(bindingId);
|
||||
actionStmts[lastIndex] =
|
||||
preventDefaultVar.set(returnExpr.cast(o.DYNAMIC_TYPE).notIdentical(o.literal(false)))
|
||||
.toDeclStmt(null, [o.StmtModifier.Final]);
|
||||
}
|
||||
}
|
||||
return new ConvertActionBindingResult(actionStmts, preventDefaultVar);
|
||||
}
|
||||
|
||||
export function temporaryDeclaration(bindingIndex: number, temporaryNumber: number): o.Statement {
|
||||
return new o.DeclareVarStmt(temporaryName(bindingIndex, temporaryNumber), o.NULL_EXPR);
|
||||
/**
|
||||
* Creates variables that are shared by multiple calls to `convertActionBinding` /
|
||||
* `convertPropertyBinding`
|
||||
*/
|
||||
export function createSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.Statement[] {
|
||||
const unwrapperStmts: o.Statement[] = [];
|
||||
var readVars = o.findReadVarNames(stmts);
|
||||
if (readVars.has(VAL_UNWRAPPER_VAR.name)) {
|
||||
unwrapperStmts.push(
|
||||
VAL_UNWRAPPER_VAR
|
||||
.set(o.importExpr(resolveIdentifier(Identifiers.ValueUnwrapper)).instantiate([]))
|
||||
.toDeclStmt(null, [o.StmtModifier.Final]));
|
||||
}
|
||||
return unwrapperStmts;
|
||||
}
|
||||
|
||||
function temporaryName(bindingId: string, temporaryNumber: number): string {
|
||||
return `tmp_${bindingId}_${temporaryNumber}`;
|
||||
}
|
||||
|
||||
export function temporaryDeclaration(bindingId: string, temporaryNumber: number): o.Statement {
|
||||
return new o.DeclareVarStmt(temporaryName(bindingId, temporaryNumber), o.NULL_EXPR);
|
||||
}
|
||||
|
||||
function prependTemporaryDecls(
|
||||
temporaryCount: number, bindingIndex: number, statements: o.Statement[]) {
|
||||
temporaryCount: number, bindingId: string, statements: o.Statement[]) {
|
||||
for (let i = temporaryCount - 1; i >= 0; i--) {
|
||||
statements.unshift(temporaryDeclaration(bindingIndex, i));
|
||||
statements.unshift(temporaryDeclaration(bindingId, i));
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,8 +170,9 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
public temporaryCount: number = 0;
|
||||
|
||||
constructor(
|
||||
private _nameResolver: NameResolver, private _implicitReceiver: o.Expression,
|
||||
private _valueUnwrapper: o.ReadVarExpr, private bindingIndex: number) {}
|
||||
private _builder: ClassBuilder, private _nameResolver: NameResolver,
|
||||
private _implicitReceiver: o.Expression, private _valueUnwrapper: o.ReadVarExpr,
|
||||
private bindingId: string, private isAction: boolean) {}
|
||||
|
||||
visitBinary(ast: cdAst.Binary, mode: _Mode): any {
|
||||
var op: o.BinaryOperator;
|
||||
@ -170,6 +249,9 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
const input = this.visit(ast.exp, _Mode.Expression);
|
||||
const args = this.visitAll(ast.args, _Mode.Expression);
|
||||
const value = this._nameResolver.callPipe(ast.name, input, args);
|
||||
if (!value) {
|
||||
throw new Error(`Illegal state: Pipe ${ast.name} is not allowed here!`);
|
||||
}
|
||||
this.needsValueUnwrapper = true;
|
||||
return convertToStatementIfNeeded(mode, this._valueUnwrapper.callMethod('unwrap', [value]));
|
||||
}
|
||||
@ -209,8 +291,10 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
}
|
||||
|
||||
visitLiteralArray(ast: cdAst.LiteralArray, mode: _Mode): any {
|
||||
return convertToStatementIfNeeded(
|
||||
mode, this._nameResolver.createLiteralArray(this.visitAll(ast.expressions, mode)));
|
||||
const parts = this.visitAll(ast.expressions, mode);
|
||||
const literalArr =
|
||||
this.isAction ? o.literalArr(parts) : createCachedLiteralArray(this._builder, parts);
|
||||
return convertToStatementIfNeeded(mode, literalArr);
|
||||
}
|
||||
|
||||
visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any {
|
||||
@ -218,13 +302,22 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
for (let i = 0; i < ast.keys.length; i++) {
|
||||
parts.push([ast.keys[i], this.visit(ast.values[i], _Mode.Expression)]);
|
||||
}
|
||||
return convertToStatementIfNeeded(mode, this._nameResolver.createLiteralMap(parts));
|
||||
const literalMap =
|
||||
this.isAction ? o.literalMap(parts) : createCachedLiteralMap(this._builder, parts);
|
||||
return convertToStatementIfNeeded(mode, literalMap);
|
||||
}
|
||||
|
||||
visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any {
|
||||
return convertToStatementIfNeeded(mode, o.literal(ast.value));
|
||||
}
|
||||
|
||||
private _getLocal(name: string): o.Expression {
|
||||
if (this.isAction && name == EventHandlerVars.event.name) {
|
||||
return EventHandlerVars.event;
|
||||
}
|
||||
return this._nameResolver.getLocal(name);
|
||||
}
|
||||
|
||||
visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any {
|
||||
const leftMostSafe = this.leftMostSafeNode(ast);
|
||||
if (leftMostSafe) {
|
||||
@ -234,7 +327,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
let result: any = null;
|
||||
let receiver = this.visit(ast.receiver, _Mode.Expression);
|
||||
if (receiver === this._implicitReceiver) {
|
||||
var varExpr = this._nameResolver.getLocal(ast.name);
|
||||
var varExpr = this._getLocal(ast.name);
|
||||
if (isPresent(varExpr)) {
|
||||
result = varExpr.callFn(args);
|
||||
}
|
||||
@ -258,7 +351,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
let result: any = null;
|
||||
var receiver = this.visit(ast.receiver, _Mode.Expression);
|
||||
if (receiver === this._implicitReceiver) {
|
||||
result = this._nameResolver.getLocal(ast.name);
|
||||
result = this._getLocal(ast.name);
|
||||
}
|
||||
if (isBlank(result)) {
|
||||
result = receiver.prop(ast.name);
|
||||
@ -270,7 +363,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
visitPropertyWrite(ast: cdAst.PropertyWrite, mode: _Mode): any {
|
||||
let receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression);
|
||||
if (receiver === this._implicitReceiver) {
|
||||
var varExpr = this._nameResolver.getLocal(ast.name);
|
||||
var varExpr = this._getLocal(ast.name);
|
||||
if (isPresent(varExpr)) {
|
||||
throw new Error('Cannot assign to a reference or variable!');
|
||||
}
|
||||
@ -459,12 +552,12 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
private allocateTemporary(): o.ReadVarExpr {
|
||||
const tempNumber = this._currentTemporary++;
|
||||
this.temporaryCount = Math.max(this._currentTemporary, this.temporaryCount);
|
||||
return new o.ReadVarExpr(temporaryName(this.bindingIndex, tempNumber));
|
||||
return new o.ReadVarExpr(temporaryName(this.bindingId, tempNumber));
|
||||
}
|
||||
|
||||
private releaseTemporary(temporary: o.ReadVarExpr) {
|
||||
this._currentTemporary--;
|
||||
if (temporary.name != temporaryName(this.bindingIndex, this._currentTemporary)) {
|
||||
if (temporary.name != temporaryName(this.bindingId, this._currentTemporary)) {
|
||||
throw new Error(`Temporary ${temporary.name} released out of order`);
|
||||
}
|
||||
}
|
||||
@ -477,3 +570,69 @@ function flattenStatements(arg: any, output: o.Statement[]) {
|
||||
output.push(arg);
|
||||
}
|
||||
}
|
||||
|
||||
function createCachedLiteralArray(builder: ClassBuilder, values: o.Expression[]): o.Expression {
|
||||
if (values.length === 0) {
|
||||
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_ARRAY));
|
||||
}
|
||||
var proxyExpr = o.THIS_EXPR.prop(`_arr_${builder.fields.length}`);
|
||||
var proxyParams: o.FnParam[] = [];
|
||||
var proxyReturnEntries: o.Expression[] = [];
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var paramName = `p${i}`;
|
||||
proxyParams.push(new o.FnParam(paramName));
|
||||
proxyReturnEntries.push(o.variable(paramName));
|
||||
}
|
||||
createPureProxy(
|
||||
o.fn(
|
||||
proxyParams, [new o.ReturnStatement(o.literalArr(proxyReturnEntries))],
|
||||
new o.ArrayType(o.DYNAMIC_TYPE)),
|
||||
values.length, proxyExpr, builder);
|
||||
return proxyExpr.callFn(values);
|
||||
}
|
||||
|
||||
function createCachedLiteralMap(
|
||||
builder: ClassBuilder, entries: [string, o.Expression][]): o.Expression {
|
||||
if (entries.length === 0) {
|
||||
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_MAP));
|
||||
}
|
||||
const proxyExpr = o.THIS_EXPR.prop(`_map_${builder.fields.length}`);
|
||||
const proxyParams: o.FnParam[] = [];
|
||||
const proxyReturnEntries: [string, o.Expression][] = [];
|
||||
const values: o.Expression[] = [];
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
const paramName = `p${i}`;
|
||||
proxyParams.push(new o.FnParam(paramName));
|
||||
proxyReturnEntries.push([entries[i][0], o.variable(paramName)]);
|
||||
values.push(<o.Expression>entries[i][1]);
|
||||
}
|
||||
createPureProxy(
|
||||
o.fn(
|
||||
proxyParams, [new o.ReturnStatement(o.literalMap(proxyReturnEntries))],
|
||||
new o.MapType(o.DYNAMIC_TYPE)),
|
||||
entries.length, proxyExpr, builder);
|
||||
return proxyExpr.callFn(values);
|
||||
}
|
||||
|
||||
|
||||
class DefaultNameResolver implements NameResolver {
|
||||
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression { return null; }
|
||||
getLocal(name: string): o.Expression { return null; }
|
||||
}
|
||||
|
||||
function createCurrValueExpr(bindingId: string): o.ReadVarExpr {
|
||||
return o.variable(`currVal_${bindingId}`); // fix syntax highlighting: `
|
||||
}
|
||||
|
||||
function createPreventDefaultVar(bindingId: string): o.ReadVarExpr {
|
||||
return o.variable(`pd_${bindingId}`);
|
||||
}
|
||||
|
||||
function convertStmtIntoExpression(stmt: o.Statement): o.Expression {
|
||||
if (stmt instanceof o.ExpressionStatement) {
|
||||
return stmt.expr;
|
||||
} else if (stmt instanceof o.ReturnStatement) {
|
||||
return stmt.value;
|
||||
}
|
||||
return null;
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @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 {CompileTokenMetadata} from '../compile_metadata';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {IdentifierSpec, Identifiers, resolveEnumIdentifier, resolveIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
|
||||
export function createDiTokenExpression(token: CompileTokenMetadata): o.Expression {
|
||||
if (isPresent(token.value)) {
|
||||
return o.literal(token.value);
|
||||
} else if (token.identifierIsInstance) {
|
||||
return o.importExpr(token.identifier)
|
||||
.instantiate([], o.importType(token.identifier, [], [o.TypeModifier.Const]));
|
||||
} else {
|
||||
return o.importExpr(token.identifier);
|
||||
}
|
||||
}
|
||||
|
||||
export function createInlineArray(values: o.Expression[]): o.Expression {
|
||||
if (values.length === 0) {
|
||||
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_INLINE_ARRAY));
|
||||
}
|
||||
const log2 = Math.log(values.length) / Math.log(2);
|
||||
const index = Math.ceil(log2);
|
||||
const identifierSpec = index < Identifiers.inlineArrays.length ? Identifiers.inlineArrays[index] :
|
||||
Identifiers.InlineArrayDynamic;
|
||||
const identifier = resolveIdentifier(identifierSpec);
|
||||
return o.importExpr(identifier).instantiate([
|
||||
<o.Expression>o.literal(values.length)
|
||||
].concat(values));
|
||||
}
|
||||
|
||||
export function createPureProxy(
|
||||
fn: o.Expression, argCount: number, pureProxyProp: o.ReadPropExpr,
|
||||
builder: {fields: o.ClassField[], ctorStmts: {push: (stmt: o.Statement) => void}}) {
|
||||
builder.fields.push(new o.ClassField(pureProxyProp.name, null));
|
||||
var pureProxyId =
|
||||
argCount < Identifiers.pureProxies.length ? Identifiers.pureProxies[argCount] : null;
|
||||
if (!pureProxyId) {
|
||||
throw new Error(`Unsupported number of argument for pure functions: ${argCount}`);
|
||||
}
|
||||
builder.ctorStmts.push(o.THIS_EXPR.prop(pureProxyProp.name)
|
||||
.set(o.importExpr(resolveIdentifier(pureProxyId)).callFn([fn]))
|
||||
.toStmt());
|
||||
}
|
||||
|
||||
export function createEnumExpression(enumType: IdentifierSpec, enumValue: any): o.Expression {
|
||||
const enumName =
|
||||
Object.keys(enumType.runtime).find((propName) => enumType.runtime[propName] === enumValue);
|
||||
if (!enumName) {
|
||||
throw new Error(`Unknown enum value ${enumValue} in ${enumType.name}`);
|
||||
}
|
||||
return o.importExpr(resolveEnumIdentifier(resolveIdentifier(enumType), enumName));
|
||||
}
|
89
modules/@angular/compiler/src/compiler_util/render_util.ts
Normal file
89
modules/@angular/compiler/src/compiler_util/render_util.ts
Normal file
@ -0,0 +1,89 @@
|
||||
/**
|
||||
* @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 {SecurityContext} from '@angular/core';
|
||||
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, resolveIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {BoundElementPropertyAst, PropertyBindingType} from '../template_parser/template_ast';
|
||||
|
||||
import {createEnumExpression} from './identifier_util';
|
||||
|
||||
export function writeToRenderer(
|
||||
view: o.Expression, boundProp: BoundElementPropertyAst, renderElement: o.Expression,
|
||||
renderValue: o.Expression, logBindingUpdate: boolean,
|
||||
securityContextExpression?: o.Expression): o.Statement[] {
|
||||
const updateStmts: o.Statement[] = [];
|
||||
const renderer = view.prop('renderer');
|
||||
renderValue = sanitizedValue(view, boundProp, renderValue, securityContextExpression);
|
||||
switch (boundProp.type) {
|
||||
case PropertyBindingType.Property:
|
||||
if (logBindingUpdate) {
|
||||
updateStmts.push(
|
||||
o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfo))
|
||||
.callFn([renderer, renderElement, o.literal(boundProp.name), renderValue])
|
||||
.toStmt());
|
||||
}
|
||||
updateStmts.push(
|
||||
renderer
|
||||
.callMethod(
|
||||
'setElementProperty', [renderElement, o.literal(boundProp.name), renderValue])
|
||||
.toStmt());
|
||||
break;
|
||||
case PropertyBindingType.Attribute:
|
||||
renderValue =
|
||||
renderValue.isBlank().conditional(o.NULL_EXPR, renderValue.callMethod('toString', []));
|
||||
updateStmts.push(
|
||||
renderer
|
||||
.callMethod(
|
||||
'setElementAttribute', [renderElement, o.literal(boundProp.name), renderValue])
|
||||
.toStmt());
|
||||
break;
|
||||
case PropertyBindingType.Class:
|
||||
updateStmts.push(
|
||||
renderer
|
||||
.callMethod(
|
||||
'setElementClass', [renderElement, o.literal(boundProp.name), renderValue])
|
||||
.toStmt());
|
||||
break;
|
||||
case PropertyBindingType.Style:
|
||||
var strValue: o.Expression = renderValue.callMethod('toString', []);
|
||||
if (isPresent(boundProp.unit)) {
|
||||
strValue = strValue.plus(o.literal(boundProp.unit));
|
||||
}
|
||||
|
||||
renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, strValue);
|
||||
updateStmts.push(
|
||||
renderer
|
||||
.callMethod(
|
||||
'setElementStyle', [renderElement, o.literal(boundProp.name), renderValue])
|
||||
.toStmt());
|
||||
break;
|
||||
case PropertyBindingType.Animation:
|
||||
throw new Error('Illegal state: Should not come here!');
|
||||
}
|
||||
return updateStmts;
|
||||
}
|
||||
|
||||
function sanitizedValue(
|
||||
view: o.Expression, boundProp: BoundElementPropertyAst, renderValue: o.Expression,
|
||||
securityContextExpression?: o.Expression): o.Expression {
|
||||
if (boundProp.securityContext === SecurityContext.NONE) {
|
||||
return renderValue; // No sanitization needed.
|
||||
}
|
||||
if (!boundProp.needsRuntimeSecurityContext) {
|
||||
securityContextExpression =
|
||||
createEnumExpression(Identifiers.SecurityContext, boundProp.securityContext);
|
||||
}
|
||||
if (!securityContextExpression) {
|
||||
throw new Error(`internal error, no SecurityContext given ${boundProp.name}`);
|
||||
}
|
||||
let ctx = view.prop('viewUtils').prop('sanitizer');
|
||||
let args = [securityContextExpression, renderValue];
|
||||
return ctx.callMethod('sanitize', args);
|
||||
}
|
@ -9,10 +9,20 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata} from './compile_metadata';
|
||||
import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util';
|
||||
import {convertPropertyBinding} from './compiler_util/expression_converter';
|
||||
import {writeToRenderer} from './compiler_util/render_util';
|
||||
import {CompilerConfig} from './config';
|
||||
import {Parser} from './expression_parser/parser';
|
||||
import {Identifiers, resolveIdentifier} from './identifiers';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG} from './ml_parser/interpolation_config';
|
||||
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
||||
import * as o from './output/output_ast';
|
||||
import {LifecycleHooks, isDefaultChangeDetectionStrategy} from './private_import_core';
|
||||
import {ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
|
||||
import {Console, LifecycleHooks, isDefaultChangeDetectionStrategy} from './private_import_core';
|
||||
import {ElementSchemaRegistry} from './schema/element_schema_registry';
|
||||
import {BindingParser} from './template_parser/binding_parser';
|
||||
import {BoundElementPropertyAst, BoundEventAst} from './template_parser/template_ast';
|
||||
|
||||
export class DirectiveWrapperCompileResult {
|
||||
constructor(public statements: o.Statement[], public dirWrapperClassVar: string) {}
|
||||
@ -42,62 +52,85 @@ const RESET_CHANGES_STMT = o.THIS_EXPR.prop(CHANGES_FIELD_NAME).set(o.literalMap
|
||||
export class DirectiveWrapperCompiler {
|
||||
static dirWrapperClassName(id: CompileIdentifierMetadata) { return `Wrapper_${id.name}`; }
|
||||
|
||||
constructor(private compilerConfig: CompilerConfig) {}
|
||||
constructor(
|
||||
private compilerConfig: CompilerConfig, private _exprParser: Parser,
|
||||
private _schemaRegistry: ElementSchemaRegistry, private _console: Console) {}
|
||||
|
||||
compile(dirMeta: CompileDirectiveMetadata): DirectiveWrapperCompileResult {
|
||||
const builder = new DirectiveWrapperBuilder(this.compilerConfig, dirMeta);
|
||||
Object.keys(dirMeta.inputs).forEach((inputFieldName) => {
|
||||
addCheckInputMethod(inputFieldName, builder);
|
||||
});
|
||||
addDetectChangesInInputPropsMethod(builder);
|
||||
|
||||
const hostParseResult = parseHostBindings(dirMeta, this._exprParser, this._schemaRegistry);
|
||||
reportParseErrors(hostParseResult.errors, this._console);
|
||||
// host properties are change detected by the DirectiveWrappers,
|
||||
// except for the animation properties as they need close integration with animation events
|
||||
// and DirectiveWrappers don't support
|
||||
// event listeners right now.
|
||||
addDetectChangesInHostPropsMethod(
|
||||
hostParseResult.hostProps.filter(hostProp => !hostProp.isAnimation), builder);
|
||||
|
||||
// TODO(tbosch): implement hostListeners via DirectiveWrapper as well!
|
||||
|
||||
const classStmt = builder.build();
|
||||
return new DirectiveWrapperCompileResult([classStmt], classStmt.name);
|
||||
}
|
||||
}
|
||||
|
||||
class DirectiveWrapperBuilder implements ClassBuilder {
|
||||
fields: o.ClassField[] = [];
|
||||
getters: o.ClassGetter[] = [];
|
||||
methods: o.ClassMethod[] = [];
|
||||
ctorStmts: o.Statement[] = [];
|
||||
|
||||
genChanges: boolean;
|
||||
ngOnChanges: boolean;
|
||||
ngOnInit: boolean;
|
||||
ngDoCheck: boolean;
|
||||
|
||||
constructor(public compilerConfig: CompilerConfig, public dirMeta: CompileDirectiveMetadata) {
|
||||
const dirLifecycleHooks = dirMeta.type.lifecycleHooks;
|
||||
this.genChanges = dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1 ||
|
||||
this.compilerConfig.logBindingUpdate;
|
||||
this.ngOnChanges = dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1;
|
||||
this.ngOnInit = dirLifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -1;
|
||||
this.ngDoCheck = dirLifecycleHooks.indexOf(LifecycleHooks.DoCheck) !== -1;
|
||||
}
|
||||
|
||||
build(): o.ClassStmt {
|
||||
const dirDepParamNames: string[] = [];
|
||||
for (let i = 0; i < dirMeta.type.diDeps.length; i++) {
|
||||
for (let i = 0; i < this.dirMeta.type.diDeps.length; i++) {
|
||||
dirDepParamNames.push(`p${i}`);
|
||||
}
|
||||
const dirLifecycleHooks = dirMeta.type.lifecycleHooks;
|
||||
let lifecycleHooks: GenConfig = {
|
||||
genChanges: dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1 ||
|
||||
this.compilerConfig.logBindingUpdate,
|
||||
ngOnChanges: dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1,
|
||||
ngOnInit: dirLifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -1,
|
||||
ngDoCheck: dirLifecycleHooks.indexOf(LifecycleHooks.DoCheck) !== -1
|
||||
};
|
||||
|
||||
const fields: o.ClassField[] = [
|
||||
new o.ClassField(CONTEXT_FIELD_NAME, o.importType(dirMeta.type)),
|
||||
new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)),
|
||||
new o.ClassField(CHANGED_FIELD_NAME, o.BOOL_TYPE),
|
||||
];
|
||||
const ctorStmts: o.Statement[] =
|
||||
[o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(false)).toStmt()];
|
||||
if (lifecycleHooks.genChanges) {
|
||||
if (this.genChanges) {
|
||||
fields.push(new o.ClassField(CHANGES_FIELD_NAME, new o.MapType(o.DYNAMIC_TYPE)));
|
||||
ctorStmts.push(RESET_CHANGES_STMT);
|
||||
}
|
||||
|
||||
const methods: o.ClassMethod[] = [];
|
||||
Object.keys(dirMeta.inputs).forEach((inputFieldName, idx) => {
|
||||
const fieldName = `_${inputFieldName}`;
|
||||
// private is fine here as no child view will reference the cached value...
|
||||
fields.push(new o.ClassField(fieldName, null, [o.StmtModifier.Private]));
|
||||
ctorStmts.push(o.THIS_EXPR.prop(fieldName)
|
||||
.set(o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED)))
|
||||
.toStmt());
|
||||
methods.push(checkInputMethod(inputFieldName, o.THIS_EXPR.prop(fieldName), lifecycleHooks));
|
||||
});
|
||||
methods.push(detectChangesInternalMethod(lifecycleHooks, this.compilerConfig.genDebugInfo));
|
||||
|
||||
ctorStmts.push(
|
||||
o.THIS_EXPR.prop(CONTEXT_FIELD_NAME)
|
||||
.set(o.importExpr(dirMeta.type)
|
||||
.set(o.importExpr(this.dirMeta.type)
|
||||
.instantiate(dirDepParamNames.map((paramName) => o.variable(paramName))))
|
||||
.toStmt());
|
||||
const ctor = new o.ClassMethod(
|
||||
null, dirDepParamNames.map((paramName) => new o.FnParam(paramName, o.DYNAMIC_TYPE)),
|
||||
ctorStmts);
|
||||
|
||||
const wrapperClassName = DirectiveWrapperCompiler.dirWrapperClassName(dirMeta.type);
|
||||
const classStmt = new o.ClassStmt(wrapperClassName, null, fields, [], ctor, methods);
|
||||
return new DirectiveWrapperCompileResult([classStmt], wrapperClassName);
|
||||
return createClassStmt({
|
||||
name: DirectiveWrapperCompiler.dirWrapperClassName(this.dirMeta.type),
|
||||
ctorParams: dirDepParamNames.map((paramName) => new o.FnParam(paramName, o.DYNAMIC_TYPE)),
|
||||
builders: [{fields, ctorStmts}, this]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function detectChangesInternalMethod(
|
||||
lifecycleHooks: GenConfig, logBindingUpdate: boolean): o.ClassMethod {
|
||||
function addDetectChangesInInputPropsMethod(builder: DirectiveWrapperBuilder) {
|
||||
const changedVar = o.variable('changed');
|
||||
const stmts: o.Statement[] = [
|
||||
changedVar.set(o.THIS_EXPR.prop(CHANGED_FIELD_NAME)).toDeclStmt(),
|
||||
@ -105,14 +138,14 @@ function detectChangesInternalMethod(
|
||||
];
|
||||
const lifecycleStmts: o.Statement[] = [];
|
||||
|
||||
if (lifecycleHooks.genChanges) {
|
||||
if (builder.genChanges) {
|
||||
const onChangesStmts: o.Statement[] = [];
|
||||
if (lifecycleHooks.ngOnChanges) {
|
||||
if (builder.ngOnChanges) {
|
||||
onChangesStmts.push(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME)
|
||||
.callMethod('ngOnChanges', [o.THIS_EXPR.prop(CHANGES_FIELD_NAME)])
|
||||
.toStmt());
|
||||
}
|
||||
if (logBindingUpdate) {
|
||||
if (builder.compilerConfig.logBindingUpdate) {
|
||||
onChangesStmts.push(
|
||||
o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfoForChanges))
|
||||
.callFn(
|
||||
@ -123,12 +156,12 @@ function detectChangesInternalMethod(
|
||||
lifecycleStmts.push(new o.IfStmt(changedVar, onChangesStmts));
|
||||
}
|
||||
|
||||
if (lifecycleHooks.ngOnInit) {
|
||||
if (builder.ngOnInit) {
|
||||
lifecycleStmts.push(new o.IfStmt(
|
||||
VIEW_VAR.prop('numberOfChecks').identical(new o.LiteralExpr(0)),
|
||||
[o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngOnInit', []).toStmt()]));
|
||||
}
|
||||
if (lifecycleHooks.ngDoCheck) {
|
||||
if (builder.ngDoCheck) {
|
||||
lifecycleStmts.push(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngDoCheck', []).toStmt());
|
||||
}
|
||||
if (lifecycleStmts.length > 0) {
|
||||
@ -136,51 +169,109 @@ function detectChangesInternalMethod(
|
||||
}
|
||||
stmts.push(new o.ReturnStatement(changedVar));
|
||||
|
||||
return new o.ClassMethod(
|
||||
'detectChangesInternal',
|
||||
builder.methods.push(new o.ClassMethod(
|
||||
'detectChangesInInputProps',
|
||||
[
|
||||
new o.FnParam(
|
||||
VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
||||
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
|
||||
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
|
||||
],
|
||||
stmts, o.BOOL_TYPE);
|
||||
stmts, o.BOOL_TYPE));
|
||||
}
|
||||
|
||||
function checkInputMethod(
|
||||
input: string, fieldExpr: o.ReadPropExpr, lifecycleHooks: GenConfig): o.ClassMethod {
|
||||
function addCheckInputMethod(input: string, builder: DirectiveWrapperBuilder) {
|
||||
const field = createCheckBindingField(builder);
|
||||
var onChangeStatements: o.Statement[] = [
|
||||
o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(true)).toStmt(),
|
||||
o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).prop(input).set(CURR_VALUE_VAR).toStmt(),
|
||||
];
|
||||
if (lifecycleHooks.genChanges) {
|
||||
if (builder.genChanges) {
|
||||
onChangeStatements.push(o.THIS_EXPR.prop(CHANGES_FIELD_NAME)
|
||||
.key(o.literal(input))
|
||||
.set(o.importExpr(resolveIdentifier(Identifiers.SimpleChange))
|
||||
.instantiate([fieldExpr, CURR_VALUE_VAR]))
|
||||
.instantiate([field.expression, CURR_VALUE_VAR]))
|
||||
.toStmt());
|
||||
}
|
||||
onChangeStatements.push(fieldExpr.set(CURR_VALUE_VAR).toStmt());
|
||||
|
||||
var methodBody: o.Statement[] = [
|
||||
new o.IfStmt(
|
||||
FORCE_UPDATE_VAR.or(o.importExpr(resolveIdentifier(Identifiers.checkBinding))
|
||||
.callFn([THROW_ON_CHANGE_VAR, fieldExpr, CURR_VALUE_VAR])),
|
||||
onChangeStatements),
|
||||
];
|
||||
return new o.ClassMethod(
|
||||
var methodBody: o.Statement[] = createCheckBindingStmt(
|
||||
{currValExpr: CURR_VALUE_VAR, forceUpdate: FORCE_UPDATE_VAR, stmts: []}, field.expression,
|
||||
THROW_ON_CHANGE_VAR, onChangeStatements);
|
||||
builder.methods.push(new o.ClassMethod(
|
||||
`check_${input}`,
|
||||
[
|
||||
new o.FnParam(CURR_VALUE_VAR.name, o.DYNAMIC_TYPE),
|
||||
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
|
||||
new o.FnParam(FORCE_UPDATE_VAR.name, o.BOOL_TYPE),
|
||||
],
|
||||
methodBody);
|
||||
methodBody));
|
||||
}
|
||||
|
||||
interface GenConfig {
|
||||
genChanges: boolean;
|
||||
ngOnChanges: boolean;
|
||||
ngOnInit: boolean;
|
||||
ngDoCheck: boolean;
|
||||
function addDetectChangesInHostPropsMethod(
|
||||
hostProps: BoundElementPropertyAst[], builder: DirectiveWrapperBuilder) {
|
||||
const stmts: o.Statement[] = [];
|
||||
const methodParams: o.FnParam[] = [
|
||||
new o.FnParam(
|
||||
VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
||||
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
|
||||
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
|
||||
];
|
||||
hostProps.forEach((hostProp) => {
|
||||
const field = createCheckBindingField(builder);
|
||||
const evalResult = convertPropertyBinding(
|
||||
builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostProp.value, field.bindingId);
|
||||
if (!evalResult) {
|
||||
return;
|
||||
}
|
||||
let securityContextExpr: o.ReadVarExpr;
|
||||
if (hostProp.needsRuntimeSecurityContext) {
|
||||
securityContextExpr = o.variable(`secCtx_${methodParams.length}`);
|
||||
methodParams.push(new o.FnParam(
|
||||
securityContextExpr.name, o.importType(resolveIdentifier(Identifiers.SecurityContext))));
|
||||
}
|
||||
stmts.push(...createCheckBindingStmt(
|
||||
evalResult, field.expression, THROW_ON_CHANGE_VAR,
|
||||
writeToRenderer(
|
||||
VIEW_VAR, hostProp, RENDER_EL_VAR, evalResult.currValExpr,
|
||||
builder.compilerConfig.logBindingUpdate, securityContextExpr)));
|
||||
});
|
||||
builder.methods.push(new o.ClassMethod('detectChangesInHostProps', methodParams, stmts));
|
||||
}
|
||||
|
||||
class ParseResult {
|
||||
constructor(
|
||||
public hostProps: BoundElementPropertyAst[], public hostListeners: BoundEventAst[],
|
||||
public errors: ParseError[]) {}
|
||||
}
|
||||
|
||||
function parseHostBindings(
|
||||
dirMeta: CompileDirectiveMetadata, exprParser: Parser,
|
||||
schemaRegistry: ElementSchemaRegistry): ParseResult {
|
||||
const errors: ParseError[] = [];
|
||||
const parser =
|
||||
new BindingParser(exprParser, DEFAULT_INTERPOLATION_CONFIG, schemaRegistry, [], errors);
|
||||
const sourceFileName = dirMeta.type.moduleUrl ?
|
||||
`in Directive ${dirMeta.type.name} in ${dirMeta.type.moduleUrl}` :
|
||||
`in Directive ${dirMeta.type.name}`;
|
||||
const sourceFile = new ParseSourceFile('', sourceFileName);
|
||||
const sourceSpan = new ParseSourceSpan(
|
||||
new ParseLocation(sourceFile, null, null, null),
|
||||
new ParseLocation(sourceFile, null, null, null));
|
||||
const parsedHostProps = parser.createDirectiveHostPropertyAsts(dirMeta, sourceSpan);
|
||||
const parsedHostListeners = parser.createDirectiveHostEventAsts(dirMeta, sourceSpan);
|
||||
|
||||
return new ParseResult(parsedHostProps, parsedHostListeners, errors);
|
||||
}
|
||||
|
||||
function reportParseErrors(parseErrors: ParseError[], console: Console) {
|
||||
const warnings = parseErrors.filter(error => error.level === ParseErrorLevel.WARNING);
|
||||
const errors = parseErrors.filter(error => error.level === ParseErrorLevel.FATAL);
|
||||
|
||||
if (warnings.length > 0) {
|
||||
this._console.warn(`Directive parse warnings:\n${warnings.join('\n')}`);
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new Error(`Directive parse errors:\n${errors.join('\n')}`);
|
||||
}
|
||||
}
|
@ -60,10 +60,11 @@ export class Parser {
|
||||
parseSimpleBinding(
|
||||
input: string, location: string,
|
||||
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
|
||||
var ast = this._parseBindingAst(input, location, interpolationConfig);
|
||||
if (!SimpleExpressionChecker.check(ast)) {
|
||||
const ast = this._parseBindingAst(input, location, interpolationConfig);
|
||||
const errors = SimpleExpressionChecker.check(ast);
|
||||
if (errors.length > 0) {
|
||||
this._reportError(
|
||||
'Host binding expression can only contain field access and constants', input, location);
|
||||
`Host binding expression cannot contain ${errors.join(' ')}`, input, location);
|
||||
}
|
||||
return new ASTWithSource(ast, input, location, this.errors);
|
||||
}
|
||||
@ -751,51 +752,51 @@ export class _ParseAST {
|
||||
}
|
||||
|
||||
class SimpleExpressionChecker implements AstVisitor {
|
||||
static check(ast: AST): boolean {
|
||||
static check(ast: AST): string[] {
|
||||
var s = new SimpleExpressionChecker();
|
||||
ast.visit(s);
|
||||
return s.simple;
|
||||
return s.errors;
|
||||
}
|
||||
|
||||
simple = true;
|
||||
errors: string[] = [];
|
||||
|
||||
visitImplicitReceiver(ast: ImplicitReceiver, context: any) {}
|
||||
|
||||
visitInterpolation(ast: Interpolation, context: any) { this.simple = false; }
|
||||
visitInterpolation(ast: Interpolation, context: any) {}
|
||||
|
||||
visitLiteralPrimitive(ast: LiteralPrimitive, context: any) {}
|
||||
|
||||
visitPropertyRead(ast: PropertyRead, context: any) {}
|
||||
|
||||
visitPropertyWrite(ast: PropertyWrite, context: any) { this.simple = false; }
|
||||
visitPropertyWrite(ast: PropertyWrite, context: any) {}
|
||||
|
||||
visitSafePropertyRead(ast: SafePropertyRead, context: any) { this.simple = false; }
|
||||
visitSafePropertyRead(ast: SafePropertyRead, context: any) {}
|
||||
|
||||
visitMethodCall(ast: MethodCall, context: any) { this.simple = false; }
|
||||
visitMethodCall(ast: MethodCall, context: any) {}
|
||||
|
||||
visitSafeMethodCall(ast: SafeMethodCall, context: any) { this.simple = false; }
|
||||
visitSafeMethodCall(ast: SafeMethodCall, context: any) {}
|
||||
|
||||
visitFunctionCall(ast: FunctionCall, context: any) { this.simple = false; }
|
||||
visitFunctionCall(ast: FunctionCall, context: any) {}
|
||||
|
||||
visitLiteralArray(ast: LiteralArray, context: any) { this.visitAll(ast.expressions); }
|
||||
|
||||
visitLiteralMap(ast: LiteralMap, context: any) { this.visitAll(ast.values); }
|
||||
|
||||
visitBinary(ast: Binary, context: any) { this.simple = false; }
|
||||
visitBinary(ast: Binary, context: any) {}
|
||||
|
||||
visitPrefixNot(ast: PrefixNot, context: any) { this.simple = false; }
|
||||
visitPrefixNot(ast: PrefixNot, context: any) {}
|
||||
|
||||
visitConditional(ast: Conditional, context: any) { this.simple = false; }
|
||||
visitConditional(ast: Conditional, context: any) {}
|
||||
|
||||
visitPipe(ast: BindingPipe, context: any) { this.simple = false; }
|
||||
visitPipe(ast: BindingPipe, context: any) { this.errors.push('pipes'); }
|
||||
|
||||
visitKeyedRead(ast: KeyedRead, context: any) { this.simple = false; }
|
||||
visitKeyedRead(ast: KeyedRead, context: any) {}
|
||||
|
||||
visitKeyedWrite(ast: KeyedWrite, context: any) { this.simple = false; }
|
||||
visitKeyedWrite(ast: KeyedWrite, context: any) {}
|
||||
|
||||
visitAll(asts: any[]): any[] { return asts.map(node => node.visit(this)); }
|
||||
|
||||
visitChain(ast: Chain, context: any) { this.simple = false; }
|
||||
visitChain(ast: Chain, context: any) {}
|
||||
|
||||
visitQuote(ast: Quote, context: any) { this.simple = false; }
|
||||
visitQuote(ast: Quote, context: any) {}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import {ANALYZE_FOR_ENTRY_COMPONENTS, AnimationTransitionEvent, ChangeDetectionS
|
||||
|
||||
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
|
||||
import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AnimationTransition, AppElement, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewType, balanceAnimationKeyframes, clearStyles, collectAndResolveStyles, devModeEqual, prepareFinalAnimationStyles, reflector, registerModuleFactory, renderStyles, view_utils} from './private_import_core';
|
||||
import {assetUrl} from './util';
|
||||
|
||||
var APP_VIEW_MODULE_URL = assetUrl('core', 'linker/view');
|
||||
var VIEW_UTILS_MODULE_URL = assetUrl('core', 'linker/view_utils');
|
||||
@ -190,8 +189,17 @@ export class Identifiers {
|
||||
moduleUrl: VIEW_UTILS_MODULE_URL,
|
||||
runtime: view_utils.EMPTY_MAP
|
||||
};
|
||||
|
||||
static pureProxies = [
|
||||
static createRenderElement: IdentifierSpec = {
|
||||
name: 'createRenderElement',
|
||||
moduleUrl: VIEW_UTILS_MODULE_URL,
|
||||
runtime: view_utils.createRenderElement
|
||||
};
|
||||
static selectOrCreateRenderHostElement: IdentifierSpec = {
|
||||
name: 'selectOrCreateRenderHostElement',
|
||||
moduleUrl: VIEW_UTILS_MODULE_URL,
|
||||
runtime: view_utils.selectOrCreateRenderHostElement
|
||||
};
|
||||
static pureProxies: IdentifierSpec[] = [
|
||||
null,
|
||||
{name: 'pureProxy1', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy1},
|
||||
{name: 'pureProxy2', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy2},
|
||||
@ -284,6 +292,35 @@ export class Identifiers {
|
||||
moduleUrl: assetUrl('core', 'animation/animation_transition'),
|
||||
runtime: AnimationTransition
|
||||
};
|
||||
|
||||
// This is just the interface!
|
||||
static InlineArray:
|
||||
IdentifierSpec = {name: 'InlineArray', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: null};
|
||||
static inlineArrays: IdentifierSpec[] = [
|
||||
{name: 'InlineArray2', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.InlineArray2},
|
||||
{name: 'InlineArray2', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.InlineArray2},
|
||||
{name: 'InlineArray4', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.InlineArray4},
|
||||
{name: 'InlineArray8', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.InlineArray8},
|
||||
{name: 'InlineArray16', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.InlineArray16},
|
||||
];
|
||||
static EMPTY_INLINE_ARRAY: IdentifierSpec = {
|
||||
name: 'EMPTY_INLINE_ARRAY',
|
||||
moduleUrl: VIEW_UTILS_MODULE_URL,
|
||||
runtime: view_utils.EMPTY_INLINE_ARRAY
|
||||
};
|
||||
static InlineArrayDynamic: IdentifierSpec = {
|
||||
name: 'InlineArrayDynamic',
|
||||
moduleUrl: VIEW_UTILS_MODULE_URL,
|
||||
runtime: view_utils.InlineArrayDynamic
|
||||
};
|
||||
}
|
||||
|
||||
export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string {
|
||||
if (path == null) {
|
||||
return `asset:@angular/lib/${pkg}/index`;
|
||||
} else {
|
||||
return `asset:@angular/lib/${pkg}/src/${path}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveIdentifier(identifier: IdentifierSpec) {
|
||||
|
@ -378,15 +378,15 @@ export class CompileMetadataResolver {
|
||||
}
|
||||
|
||||
private _getTypeDescriptor(type: Type<any>): string {
|
||||
if (this._directiveResolver.resolve(type, false) !== null) {
|
||||
if (this._directiveResolver.resolve(type, false)) {
|
||||
return 'directive';
|
||||
}
|
||||
|
||||
if (this._pipeResolver.resolve(type, false) !== null) {
|
||||
if (this._pipeResolver.resolve(type, false)) {
|
||||
return 'pipe';
|
||||
}
|
||||
|
||||
if (this._ngModuleResolver.resolve(type, false) !== null) {
|
||||
if (this._ngModuleResolver.resolve(type, false)) {
|
||||
return 'module';
|
||||
}
|
||||
|
||||
@ -508,7 +508,7 @@ export class CompileMetadataResolver {
|
||||
let isOptional = false;
|
||||
let query: Query = null;
|
||||
let viewQuery: Query = null;
|
||||
var token: any = null;
|
||||
let token: any = null;
|
||||
if (Array.isArray(param)) {
|
||||
param.forEach((paramEntry) => {
|
||||
if (paramEntry instanceof Host) {
|
||||
@ -605,19 +605,20 @@ export class CompileMetadataResolver {
|
||||
} else if (isValidType(provider)) {
|
||||
compileProvider = this.getTypeMetadata(provider, staticTypeModuleUrl(provider));
|
||||
} else {
|
||||
let providersInfo = (<string[]>providers.reduce(
|
||||
(soFar: string[], seenProvider: any, seenProviderIdx: number) => {
|
||||
if (seenProviderIdx < providerIdx) {
|
||||
soFar.push(`${stringify(seenProvider)}`);
|
||||
} else if (seenProviderIdx == providerIdx) {
|
||||
soFar.push(`?${stringify(seenProvider)}?`);
|
||||
} else if (seenProviderIdx == providerIdx + 1) {
|
||||
soFar.push('...');
|
||||
}
|
||||
return soFar;
|
||||
},
|
||||
[]))
|
||||
.join(', ');
|
||||
const providersInfo =
|
||||
(<string[]>providers.reduce(
|
||||
(soFar: string[], seenProvider: any, seenProviderIdx: number) => {
|
||||
if (seenProviderIdx < providerIdx) {
|
||||
soFar.push(`${stringify(seenProvider)}`);
|
||||
} else if (seenProviderIdx == providerIdx) {
|
||||
soFar.push(`?${stringify(seenProvider)}?`);
|
||||
} else if (seenProviderIdx == providerIdx + 1) {
|
||||
soFar.push('...');
|
||||
}
|
||||
return soFar;
|
||||
},
|
||||
[]))
|
||||
.join(', ');
|
||||
|
||||
throw new Error(
|
||||
`Invalid ${debugInfo ? debugInfo : 'provider'} - only instances of Provider and Type are allowed, got: [${providersInfo}]`);
|
||||
|
@ -6,7 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
import {ParseError, ParseSourceSpan} from '../parse_util';
|
||||
|
||||
@ -231,7 +230,7 @@ class _TreeBuilder {
|
||||
|
||||
private _closeVoidElement(): void {
|
||||
if (this._elementStack.length > 0) {
|
||||
const el = ListWrapper.last(this._elementStack);
|
||||
const el = this._elementStack[this._elementStack.length - 1];
|
||||
|
||||
if (this.getTagDefinition(el.name).isVoid) {
|
||||
this._elementStack.pop();
|
||||
@ -275,7 +274,7 @@ class _TreeBuilder {
|
||||
|
||||
private _pushElement(el: html.Element) {
|
||||
if (this._elementStack.length > 0) {
|
||||
const parentEl = ListWrapper.last(this._elementStack);
|
||||
const parentEl = this._elementStack[this._elementStack.length - 1];
|
||||
if (this.getTagDefinition(parentEl.name).isClosedByChild(el.name)) {
|
||||
this._elementStack.pop();
|
||||
}
|
||||
@ -316,7 +315,7 @@ class _TreeBuilder {
|
||||
for (let stackIndex = this._elementStack.length - 1; stackIndex >= 0; stackIndex--) {
|
||||
const el = this._elementStack[stackIndex];
|
||||
if (el.name == fullName) {
|
||||
ListWrapper.splice(this._elementStack, stackIndex, this._elementStack.length - stackIndex);
|
||||
this._elementStack.splice(stackIndex, this._elementStack.length - stackIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -343,7 +342,7 @@ class _TreeBuilder {
|
||||
}
|
||||
|
||||
private _getParentElement(): html.Element {
|
||||
return this._elementStack.length > 0 ? ListWrapper.last(this._elementStack) : null;
|
||||
return this._elementStack.length > 0 ? this._elementStack[this._elementStack.length - 1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -361,7 +360,7 @@ class _TreeBuilder {
|
||||
container = this._elementStack[i];
|
||||
}
|
||||
|
||||
return {parent: ListWrapper.last(this._elementStack), container};
|
||||
return {parent: this._elementStack[this._elementStack.length - 1], container};
|
||||
}
|
||||
|
||||
private _addToParent(node: html.Node) {
|
||||
|
@ -9,15 +9,16 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata} from './compile_metadata';
|
||||
import {createDiTokenExpression} from './compiler_util/identifier_util';
|
||||
import {isPresent} from './facade/lang';
|
||||
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers';
|
||||
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
||||
import * as o from './output/output_ast';
|
||||
import {convertValueToOutputAst} from './output/value_util';
|
||||
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
|
||||
import {LifecycleHooks} from './private_import_core';
|
||||
import {NgModuleProviderAnalyzer} from './provider_analyzer';
|
||||
import {ProviderAst} from './template_parser/template_ast';
|
||||
import {createDiTokenExpression} from './util';
|
||||
|
||||
export class ComponentFactoryDependency {
|
||||
constructor(
|
||||
@ -82,13 +83,15 @@ export class NgModuleCompiler {
|
||||
}
|
||||
}
|
||||
|
||||
class _InjectorBuilder {
|
||||
class _InjectorBuilder implements ClassBuilder {
|
||||
fields: o.ClassField[] = [];
|
||||
getters: o.ClassGetter[] = [];
|
||||
methods: o.ClassMethod[] = [];
|
||||
ctorStmts: o.Statement[] = [];
|
||||
private _tokens: CompileTokenMetadata[] = [];
|
||||
private _instances = new Map<any, o.Expression>();
|
||||
private _fields: o.ClassField[] = [];
|
||||
private _createStmts: o.Statement[] = [];
|
||||
private _destroyStmts: o.Statement[] = [];
|
||||
private _getters: o.ClassGetter[] = [];
|
||||
|
||||
constructor(
|
||||
private _ngModuleMeta: CompileNgModuleMetadata,
|
||||
@ -136,26 +139,23 @@ class _InjectorBuilder {
|
||||
),
|
||||
];
|
||||
|
||||
var ctor = new o.ClassMethod(
|
||||
null,
|
||||
[new o.FnParam(
|
||||
InjectorProps.parent.name, o.importType(resolveIdentifier(Identifiers.Injector)))],
|
||||
[o.SUPER_EXPR
|
||||
.callFn([
|
||||
o.variable(InjectorProps.parent.name),
|
||||
o.literalArr(this._entryComponentFactories.map(
|
||||
(componentFactory) => o.importExpr(componentFactory))),
|
||||
o.literalArr(this._bootstrapComponentFactories.map(
|
||||
(componentFactory) => o.importExpr(componentFactory)))
|
||||
])
|
||||
.toStmt()]);
|
||||
|
||||
var parentArgs = [
|
||||
o.variable(InjectorProps.parent.name),
|
||||
o.literalArr(
|
||||
this._entryComponentFactories.map((componentFactory) => o.importExpr(componentFactory))),
|
||||
o.literalArr(this._bootstrapComponentFactories.map(
|
||||
(componentFactory) => o.importExpr(componentFactory)))
|
||||
];
|
||||
var injClassName = `${this._ngModuleMeta.type.name}Injector`;
|
||||
return new o.ClassStmt(
|
||||
injClassName, o.importExpr(
|
||||
resolveIdentifier(Identifiers.NgModuleInjector),
|
||||
[o.importType(this._ngModuleMeta.type)]),
|
||||
this._fields, this._getters, ctor, methods);
|
||||
return createClassStmt({
|
||||
name: injClassName,
|
||||
ctorParams: [new o.FnParam(
|
||||
InjectorProps.parent.name, o.importType(resolveIdentifier(Identifiers.Injector)))],
|
||||
parent: o.importExpr(
|
||||
resolveIdentifier(Identifiers.NgModuleInjector), [o.importType(this._ngModuleMeta.type)]),
|
||||
parentArgs: parentArgs,
|
||||
builders: [{methods}, this]
|
||||
});
|
||||
}
|
||||
|
||||
private _getProviderValue(provider: CompileProviderMetadata): o.Expression {
|
||||
@ -194,11 +194,11 @@ class _InjectorBuilder {
|
||||
type = o.DYNAMIC_TYPE;
|
||||
}
|
||||
if (isEager) {
|
||||
this._fields.push(new o.ClassField(propName, type));
|
||||
this.fields.push(new o.ClassField(propName, type));
|
||||
this._createStmts.push(o.THIS_EXPR.prop(propName).set(resolvedProviderValueExpr).toStmt());
|
||||
} else {
|
||||
var internalField = `_${propName}`;
|
||||
this._fields.push(new o.ClassField(internalField, type));
|
||||
this.fields.push(new o.ClassField(internalField, type));
|
||||
// Note: Equals is important for JS so that it also checks the undefined case!
|
||||
var getterStmts = [
|
||||
new o.IfStmt(
|
||||
@ -206,7 +206,7 @@ class _InjectorBuilder {
|
||||
[o.THIS_EXPR.prop(internalField).set(resolvedProviderValueExpr).toStmt()]),
|
||||
new o.ReturnStatement(o.THIS_EXPR.prop(internalField))
|
||||
];
|
||||
this._getters.push(new o.ClassGetter(propName, getterStmts, type));
|
||||
this.getters.push(new o.ClassGetter(propName, getterStmts, type));
|
||||
}
|
||||
return o.THIS_EXPR.prop(propName);
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import {AnimationParser} from './animation/animation_parser';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata';
|
||||
import {DirectiveNormalizer} from './directive_normalizer';
|
||||
import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from './directive_wrapper_compiler';
|
||||
import {ListWrapper, MapWrapper} from './facade/collection';
|
||||
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers';
|
||||
import {CompileMetadataResolver} from './metadata_resolver';
|
||||
import {NgModuleCompiler} from './ng_module_compiler';
|
||||
@ -23,28 +24,88 @@ import {TemplateParser} from './template_parser/template_parser';
|
||||
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewCompileResult, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler';
|
||||
|
||||
export class SourceModule {
|
||||
constructor(public moduleUrl: string, public source: string) {}
|
||||
constructor(public fileUrl: string, public moduleUrl: string, public source: string) {}
|
||||
}
|
||||
|
||||
export class NgModulesSummary {
|
||||
constructor(
|
||||
public ngModuleByDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
public ngModules: CompileNgModuleMetadata[]) {}
|
||||
}
|
||||
// Returns all the source files and a mapping from modules to directives
|
||||
export function analyzeNgModules(
|
||||
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
|
||||
metadataResolver: CompileMetadataResolver): {
|
||||
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>
|
||||
} {
|
||||
const {
|
||||
ngModules: programNgModules,
|
||||
pipesAndDirectives: programPipesOrDirectives,
|
||||
} = _extractModulesAndPipesOrDirectives(programStaticSymbols, metadataResolver);
|
||||
|
||||
export function analyzeModules(
|
||||
ngModules: StaticSymbol[], metadataResolver: CompileMetadataResolver) {
|
||||
const ngModuleByDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
|
||||
const modules: CompileNgModuleMetadata[] = [];
|
||||
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
|
||||
|
||||
programNgModules.forEach(modMeta => {
|
||||
if (options.transitiveModules) {
|
||||
// For every input modules add the list of transitively included modules
|
||||
modMeta.transitiveModule.modules.forEach(
|
||||
modMeta => { moduleMetasByRef.set(modMeta.type.reference, modMeta); });
|
||||
} else {
|
||||
moduleMetasByRef.set(modMeta.type.reference, modMeta);
|
||||
}
|
||||
});
|
||||
|
||||
const ngModuleMetas = MapWrapper.values(moduleMetasByRef);
|
||||
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
|
||||
const ngModulesByFile = new Map<string, StaticSymbol[]>();
|
||||
const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
|
||||
const filePaths = new Set<string>();
|
||||
|
||||
// Looping over all modules to construct:
|
||||
// - a map from file to modules `ngModulesByFile`,
|
||||
// - a map from file to directives `ngDirectivesByFile`,
|
||||
// - a map from directive/pipe to module `ngModuleByPipeOrDirective`.
|
||||
ngModuleMetas.forEach((ngModuleMeta) => {
|
||||
const srcFileUrl = ngModuleMeta.type.reference.filePath;
|
||||
filePaths.add(srcFileUrl);
|
||||
ngModulesByFile.set(
|
||||
srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference));
|
||||
|
||||
ngModules.forEach((ngModule) => {
|
||||
const ngModuleMeta = metadataResolver.getNgModuleMetadata(<any>ngModule);
|
||||
modules.push(ngModuleMeta);
|
||||
ngModuleMeta.declaredDirectives.forEach((dirMeta: CompileDirectiveMetadata) => {
|
||||
ngModuleByDirective.set(dirMeta.type.reference, ngModuleMeta);
|
||||
const fileUrl = dirMeta.type.reference.filePath;
|
||||
filePaths.add(fileUrl);
|
||||
ngDirectivesByFile.set(
|
||||
fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirMeta.type.reference));
|
||||
ngModuleByPipeOrDirective.set(dirMeta.type.reference, ngModuleMeta);
|
||||
});
|
||||
|
||||
ngModuleMeta.declaredPipes.forEach((pipeMeta: CompilePipeMetadata) => {
|
||||
const fileUrl = pipeMeta.type.reference.filePath;
|
||||
filePaths.add(fileUrl);
|
||||
ngModuleByPipeOrDirective.set(pipeMeta.type.reference, ngModuleMeta);
|
||||
});
|
||||
});
|
||||
return new NgModulesSummary(ngModuleByDirective, modules);
|
||||
|
||||
// Throw an error if any of the program pipe or directives is not declared by a module
|
||||
const symbolsMissingModule =
|
||||
programPipesOrDirectives.filter(s => !ngModuleByPipeOrDirective.has(s));
|
||||
|
||||
if (symbolsMissingModule.length) {
|
||||
const messages = symbolsMissingModule.map(
|
||||
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
|
||||
throw new Error(messages.join('\n'));
|
||||
}
|
||||
|
||||
const files: {srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}[] = [];
|
||||
|
||||
filePaths.forEach((srcUrl) => {
|
||||
const directives = ngDirectivesByFile.get(srcUrl) || [];
|
||||
const ngModules = ngModulesByFile.get(srcUrl) || [];
|
||||
files.push({srcUrl, directives, ngModules});
|
||||
});
|
||||
|
||||
return {
|
||||
// map directive/pipe to module
|
||||
ngModuleByPipeOrDirective,
|
||||
// list modules and directives for every source file
|
||||
files,
|
||||
};
|
||||
}
|
||||
|
||||
export class OfflineCompiler {
|
||||
@ -59,19 +120,28 @@ export class OfflineCompiler {
|
||||
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
|
||||
private _localeId: string, private _translationFormat: string) {}
|
||||
|
||||
analyzeModules(ngModules: StaticSymbol[]): NgModulesSummary {
|
||||
return analyzeModules(ngModules, this._metadataResolver);
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
this._directiveNormalizer.clearCache();
|
||||
this._metadataResolver.clearCache();
|
||||
}
|
||||
|
||||
compile(
|
||||
moduleUrl: string, ngModulesSummary: NgModulesSummary, directives: StaticSymbol[],
|
||||
ngModules: StaticSymbol[]): Promise<SourceModule[]> {
|
||||
const fileSuffix = _splitTypescriptSuffix(moduleUrl)[1];
|
||||
compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}):
|
||||
Promise<SourceModule[]> {
|
||||
const {ngModuleByPipeOrDirective, files} =
|
||||
analyzeNgModules(staticSymbols, options, this._metadataResolver);
|
||||
|
||||
const sourceModules = files.map(
|
||||
file => this._compileSrcFile(
|
||||
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules));
|
||||
|
||||
return Promise.all(sourceModules)
|
||||
.then((modules: SourceModule[][]) => ListWrapper.flatten(modules));
|
||||
}
|
||||
|
||||
private _compileSrcFile(
|
||||
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
directives: StaticSymbol[], ngModules: StaticSymbol[]): Promise<SourceModule[]> {
|
||||
const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1];
|
||||
const statements: o.Statement[] = [];
|
||||
const exportedVars: string[] = [];
|
||||
const outputSourceModules: SourceModule[] = [];
|
||||
@ -91,9 +161,10 @@ export class OfflineCompiler {
|
||||
if (!compMeta.isComponent) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
const ngModule = ngModulesSummary.ngModuleByDirective.get(dirType);
|
||||
const ngModule = ngModuleByPipeOrDirective.get(dirType);
|
||||
if (!ngModule) {
|
||||
throw new Error(`Cannot determine the module for component ${compMeta.type.name}!`);
|
||||
throw new Error(
|
||||
`Internal Error: cannot determine the module for component ${compMeta.type.name}!`);
|
||||
}
|
||||
|
||||
return Promise
|
||||
@ -106,7 +177,8 @@ export class OfflineCompiler {
|
||||
// compile styles
|
||||
const stylesCompileResults = this._styleCompiler.compileComponent(compMeta);
|
||||
stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => {
|
||||
outputSourceModules.push(this._codgenStyles(compiledStyleSheet, fileSuffix));
|
||||
outputSourceModules.push(
|
||||
this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix));
|
||||
});
|
||||
|
||||
// compile components
|
||||
@ -119,8 +191,9 @@ export class OfflineCompiler {
|
||||
}))
|
||||
.then(() => {
|
||||
if (statements.length > 0) {
|
||||
outputSourceModules.unshift(this._codegenSourceModule(
|
||||
_ngfactoryModuleUrl(moduleUrl), statements, exportedVars));
|
||||
const srcModule = this._codegenSourceModule(
|
||||
srcFileUrl, _ngfactoryModuleUrl(srcFileUrl), statements, exportedVars);
|
||||
outputSourceModules.unshift(srcModule);
|
||||
}
|
||||
return outputSourceModules;
|
||||
});
|
||||
@ -209,18 +282,21 @@ export class OfflineCompiler {
|
||||
return viewResult.viewFactoryVar;
|
||||
}
|
||||
|
||||
private _codgenStyles(stylesCompileResult: CompiledStylesheet, fileSuffix: string): SourceModule {
|
||||
private _codgenStyles(
|
||||
fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): SourceModule {
|
||||
_resolveStyleStatements(stylesCompileResult, fileSuffix);
|
||||
return this._codegenSourceModule(
|
||||
_stylesModuleUrl(
|
||||
stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix),
|
||||
fileUrl, _stylesModuleUrl(
|
||||
stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix),
|
||||
stylesCompileResult.statements, [stylesCompileResult.stylesVar]);
|
||||
}
|
||||
|
||||
private _codegenSourceModule(
|
||||
moduleUrl: string, statements: o.Statement[], exportedVars: string[]): SourceModule {
|
||||
fileUrl: string, moduleUrl: string, statements: o.Statement[],
|
||||
exportedVars: string[]): SourceModule {
|
||||
return new SourceModule(
|
||||
moduleUrl, this._outputEmitter.emitStatements(moduleUrl, statements, exportedVars));
|
||||
fileUrl, moduleUrl,
|
||||
this._outputEmitter.emitStatements(moduleUrl, statements, exportedVars));
|
||||
}
|
||||
}
|
||||
|
||||
@ -282,3 +358,28 @@ function _splitTypescriptSuffix(path: string): string[] {
|
||||
|
||||
return [path, ''];
|
||||
}
|
||||
|
||||
// Group the symbols by types:
|
||||
// - NgModules,
|
||||
// - Pipes and Directives.
|
||||
function _extractModulesAndPipesOrDirectives(
|
||||
programStaticSymbols: StaticSymbol[], metadataResolver: CompileMetadataResolver) {
|
||||
const ngModules: CompileNgModuleMetadata[] = [];
|
||||
const pipesAndDirectives: StaticSymbol[] = [];
|
||||
|
||||
programStaticSymbols.forEach(staticSymbol => {
|
||||
const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false);
|
||||
const directive = metadataResolver.getDirectiveMetadata(staticSymbol, false);
|
||||
const pipe = metadataResolver.getPipeMetadata(<any>staticSymbol, false);
|
||||
|
||||
if (ngModule) {
|
||||
ngModules.push(ngModule);
|
||||
} else if (directive) {
|
||||
pipesAndDirectives.push(staticSymbol);
|
||||
} else if (pipe) {
|
||||
pipesAndDirectives.push(staticSymbol);
|
||||
}
|
||||
});
|
||||
|
||||
return {ngModules, pipesAndDirectives};
|
||||
}
|
||||
|
60
modules/@angular/compiler/src/output/class_builder.ts
Normal file
60
modules/@angular/compiler/src/output/class_builder.ts
Normal file
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @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 o from './output_ast';
|
||||
|
||||
/**
|
||||
* Create a new class stmts based on the given data.
|
||||
*/
|
||||
export function createClassStmt(config: {
|
||||
name: string,
|
||||
parent?: o.Expression,
|
||||
parentArgs?: o.Expression[],
|
||||
ctorParams?: o.FnParam[],
|
||||
builders: ClassBuilderPart | ClassBuilderPart[], modifiers?: o.StmtModifier[]
|
||||
}): o.ClassStmt {
|
||||
const parentArgs = config.parentArgs || [];
|
||||
const superCtorStmts = config.parent ? [o.SUPER_EXPR.callFn(parentArgs).toStmt()] : [];
|
||||
const builder =
|
||||
concatClassBuilderParts(Array.isArray(config.builders) ? config.builders : [config.builders]);
|
||||
const ctor =
|
||||
new o.ClassMethod(null, config.ctorParams || [], superCtorStmts.concat(builder.ctorStmts));
|
||||
|
||||
return new o.ClassStmt(
|
||||
config.name, config.parent, builder.fields, builder.getters, ctor, builder.methods,
|
||||
config.modifiers || []);
|
||||
}
|
||||
|
||||
function concatClassBuilderParts(builders: ClassBuilderPart[]) {
|
||||
return {
|
||||
fields: [].concat(...builders.map(builder => builder.fields || [])),
|
||||
methods: [].concat(...builders.map(builder => builder.methods || [])),
|
||||
getters: [].concat(...builders.map(builder => builder.getters || [])),
|
||||
ctorStmts: [].concat(...builders.map(builder => builder.ctorStmts || [])),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects data for a generated class.
|
||||
*/
|
||||
export interface ClassBuilderPart {
|
||||
fields?: o.ClassField[];
|
||||
methods?: o.ClassMethod[];
|
||||
getters?: o.ClassGetter[];
|
||||
ctorStmts?: o.Statement[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects data for a generated class.
|
||||
*/
|
||||
export interface ClassBuilder {
|
||||
fields: o.ClassField[];
|
||||
methods: o.ClassMethod[];
|
||||
getters: o.ClassGetter[];
|
||||
ctorStmts: o.Statement[];
|
||||
}
|
@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {isPresent} from '../facade/lang';
|
||||
|
||||
import * as o from './output_ast';
|
||||
@ -153,7 +152,7 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor {
|
||||
if (isPresent(expr.builtin)) {
|
||||
switch (expr.builtin) {
|
||||
case o.BuiltinMethod.ConcatArray:
|
||||
result = ListWrapper.concat(receiver, args[0]);
|
||||
result = receiver.concat(args[0]);
|
||||
break;
|
||||
case o.BuiltinMethod.SubscribeObservable:
|
||||
result = receiver.subscribe({next: args[0]});
|
||||
|
@ -35,7 +35,7 @@ export enum ParseErrorLevel {
|
||||
FATAL
|
||||
}
|
||||
|
||||
export abstract class ParseError {
|
||||
export class ParseError {
|
||||
constructor(
|
||||
public span: ParseSourceSpan, public msg: string,
|
||||
public level: ParseErrorLevel = ParseErrorLevel.FATAL) {}
|
||||
|
@ -8,8 +8,8 @@
|
||||
|
||||
|
||||
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata} from './compile_metadata';
|
||||
import {ListWrapper, MapWrapper} from './facade/collection';
|
||||
import {isBlank, isPresent, normalizeBlank} from './facade/lang';
|
||||
import {MapWrapper} from './facade/collection';
|
||||
import {isBlank, isPresent} from './facade/lang';
|
||||
import {Identifiers, resolveIdentifierToken} from './identifiers';
|
||||
import {ParseError, ParseSourceSpan} from './parse_util';
|
||||
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst} from './template_parser/template_ast';
|
||||
@ -91,9 +91,9 @@ export class ProviderElementContext {
|
||||
|
||||
get transformedDirectiveAsts(): DirectiveAst[] {
|
||||
var sortedProviderTypes = this.transformProviders.map(provider => provider.token.identifier);
|
||||
var sortedDirectives = ListWrapper.clone(this._directiveAsts);
|
||||
ListWrapper.sort(
|
||||
sortedDirectives, (dir1, dir2) => sortedProviderTypes.indexOf(dir1.directive.type) -
|
||||
var sortedDirectives = this._directiveAsts.slice();
|
||||
sortedDirectives.sort(
|
||||
(dir1, dir2) => sortedProviderTypes.indexOf(dir1.directive.type) -
|
||||
sortedProviderTypes.indexOf(dir2.directive.type));
|
||||
return sortedDirectives;
|
||||
}
|
||||
@ -117,7 +117,7 @@ export class ProviderElementContext {
|
||||
while (currentEl !== null) {
|
||||
queries = currentEl._contentQueries.get(token.reference);
|
||||
if (isPresent(queries)) {
|
||||
ListWrapper.addAll(result, queries.filter((query) => query.descendants || distance <= 1));
|
||||
result.push(...queries.filter((query) => query.descendants || distance <= 1));
|
||||
}
|
||||
if (currentEl._directiveAsts.length > 0) {
|
||||
distance++;
|
||||
@ -126,7 +126,7 @@ export class ProviderElementContext {
|
||||
}
|
||||
queries = this.viewContext.viewQueries.get(token.reference);
|
||||
if (isPresent(queries)) {
|
||||
ListWrapper.addAll(result, queries);
|
||||
result.push(...queries);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -194,7 +194,8 @@ export class ProviderElementContext {
|
||||
eager: boolean = null): CompileDiDependencyMetadata {
|
||||
if (dep.isAttribute) {
|
||||
var attrValue = this._attrs[dep.token.value];
|
||||
return new CompileDiDependencyMetadata({isValue: true, value: normalizeBlank(attrValue)});
|
||||
return new CompileDiDependencyMetadata(
|
||||
{isValue: true, value: attrValue == null ? null : attrValue});
|
||||
}
|
||||
if (isPresent(dep.query) || isPresent(dep.viewQuery)) {
|
||||
return dep;
|
||||
@ -489,7 +490,7 @@ function _resolveProviders(
|
||||
targetProvidersByToken.set(provider.token.reference, resolvedProvider);
|
||||
} else {
|
||||
if (!provider.multi) {
|
||||
ListWrapper.clear(resolvedProvider.providers);
|
||||
resolvedProvider.providers.length = 0;
|
||||
}
|
||||
resolvedProvider.providers.push(provider);
|
||||
}
|
||||
|
@ -328,7 +328,12 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry {
|
||||
* 'NONE' security context, i.e. that they are safe inert string values. Only specific well known
|
||||
* attack vectors are assigned their appropriate context.
|
||||
*/
|
||||
securityContext(tagName: string, propName: string): SecurityContext {
|
||||
securityContext(tagName: string, propName: string, isAttribute: boolean): SecurityContext {
|
||||
if (isAttribute) {
|
||||
// NB: For security purposes, use the mapped property name, not the attribute name.
|
||||
propName = this.getMappedPropName(propName);
|
||||
}
|
||||
|
||||
// Make sure comparisons are case insensitive, so that case differences between attribute and
|
||||
// property names do not have a security impact.
|
||||
tagName = tagName.toLowerCase();
|
||||
@ -366,4 +371,6 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry {
|
||||
return {error: false};
|
||||
}
|
||||
}
|
||||
|
||||
allKnownElementNames(): string[] { return Object.keys(this._schema); }
|
||||
}
|
||||
|
@ -6,12 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {SchemaMetadata} from '@angular/core';
|
||||
import {SchemaMetadata, SecurityContext} from '@angular/core';
|
||||
|
||||
export abstract class ElementSchemaRegistry {
|
||||
abstract hasProperty(tagName: string, propName: string, schemaMetas: SchemaMetadata[]): boolean;
|
||||
abstract hasElement(tagName: string, schemaMetas: SchemaMetadata[]): boolean;
|
||||
abstract securityContext(tagName: string, propName: string): any;
|
||||
abstract securityContext(elementName: string, propName: string, isAttribute: boolean):
|
||||
SecurityContext;
|
||||
abstract allKnownElementNames(): string[];
|
||||
abstract getMappedPropName(propName: string): string;
|
||||
abstract getDefaultComponentElementName(): string;
|
||||
abstract validateProperty(name: string): {error: boolean, msg?: string};
|
||||
|
@ -135,12 +135,12 @@ export class SelectorMatcher {
|
||||
return notMatcher;
|
||||
}
|
||||
|
||||
private _elementMap: {[k: string]: SelectorContext[]} = {};
|
||||
private _elementPartialMap: {[k: string]: SelectorMatcher} = {};
|
||||
private _classMap: {[k: string]: SelectorContext[]} = {};
|
||||
private _classPartialMap: {[k: string]: SelectorMatcher} = {};
|
||||
private _attrValueMap: {[k: string]: {[k: string]: SelectorContext[]}} = {};
|
||||
private _attrValuePartialMap: {[k: string]: {[k: string]: SelectorMatcher}} = {};
|
||||
private _elementMap = new Map<string, SelectorContext[]>();
|
||||
private _elementPartialMap = new Map<string, SelectorMatcher>();
|
||||
private _classMap = new Map<string, SelectorContext[]>();
|
||||
private _classPartialMap = new Map<string, SelectorMatcher>();
|
||||
private _attrValueMap = new Map<string, Map<string, SelectorContext[]>>();
|
||||
private _attrValuePartialMap = new Map<string, Map<string, SelectorMatcher>>();
|
||||
private _listContexts: SelectorListContext[] = [];
|
||||
|
||||
addSelectables(cssSelectors: CssSelector[], callbackCtxt?: any) {
|
||||
@ -195,18 +195,18 @@ export class SelectorMatcher {
|
||||
const value = attrs[i + 1];
|
||||
if (isTerminal) {
|
||||
const terminalMap = matcher._attrValueMap;
|
||||
let terminalValuesMap = terminalMap[name];
|
||||
let terminalValuesMap = terminalMap.get(name);
|
||||
if (!terminalValuesMap) {
|
||||
terminalValuesMap = {};
|
||||
terminalMap[name] = terminalValuesMap;
|
||||
terminalValuesMap = new Map<string, SelectorContext[]>();
|
||||
terminalMap.set(name, terminalValuesMap);
|
||||
}
|
||||
this._addTerminal(terminalValuesMap, value, selectable);
|
||||
} else {
|
||||
let partialMap = matcher._attrValuePartialMap;
|
||||
let partialValuesMap = partialMap[name];
|
||||
let partialValuesMap = partialMap.get(name);
|
||||
if (!partialValuesMap) {
|
||||
partialValuesMap = {};
|
||||
partialMap[name] = partialValuesMap;
|
||||
partialValuesMap = new Map<string, SelectorMatcher>();
|
||||
partialMap.set(name, partialValuesMap);
|
||||
}
|
||||
matcher = this._addPartial(partialValuesMap, value);
|
||||
}
|
||||
@ -215,20 +215,20 @@ export class SelectorMatcher {
|
||||
}
|
||||
|
||||
private _addTerminal(
|
||||
map: {[k: string]: SelectorContext[]}, name: string, selectable: SelectorContext) {
|
||||
let terminalList = map[name];
|
||||
map: Map<string, SelectorContext[]>, name: string, selectable: SelectorContext) {
|
||||
let terminalList = map.get(name);
|
||||
if (!terminalList) {
|
||||
terminalList = [];
|
||||
map[name] = terminalList;
|
||||
map.set(name, terminalList);
|
||||
}
|
||||
terminalList.push(selectable);
|
||||
}
|
||||
|
||||
private _addPartial(map: {[k: string]: SelectorMatcher}, name: string): SelectorMatcher {
|
||||
let matcher = map[name];
|
||||
private _addPartial(map: Map<string, SelectorMatcher>, name: string): SelectorMatcher {
|
||||
let matcher = map.get(name);
|
||||
if (!matcher) {
|
||||
matcher = new SelectorMatcher();
|
||||
map[name] = matcher;
|
||||
map.set(name, matcher);
|
||||
}
|
||||
return matcher;
|
||||
}
|
||||
@ -270,7 +270,7 @@ export class SelectorMatcher {
|
||||
const name = attrs[i];
|
||||
const value = attrs[i + 1];
|
||||
|
||||
const terminalValuesMap = this._attrValueMap[name];
|
||||
const terminalValuesMap = this._attrValueMap.get(name);
|
||||
if (value) {
|
||||
result =
|
||||
this._matchTerminal(terminalValuesMap, '', cssSelector, matchedCallback) || result;
|
||||
@ -278,7 +278,7 @@ export class SelectorMatcher {
|
||||
result =
|
||||
this._matchTerminal(terminalValuesMap, value, cssSelector, matchedCallback) || result;
|
||||
|
||||
const partialValuesMap = this._attrValuePartialMap[name];
|
||||
const partialValuesMap = this._attrValuePartialMap.get(name);
|
||||
if (value) {
|
||||
result = this._matchPartial(partialValuesMap, '', cssSelector, matchedCallback) || result;
|
||||
}
|
||||
@ -291,14 +291,14 @@ export class SelectorMatcher {
|
||||
|
||||
/** @internal */
|
||||
_matchTerminal(
|
||||
map: {[k: string]: SelectorContext[]}, name: string, cssSelector: CssSelector,
|
||||
map: Map<string, SelectorContext[]>, name: string, cssSelector: CssSelector,
|
||||
matchedCallback: (c: CssSelector, a: any) => void): boolean {
|
||||
if (!map || typeof name !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let selectables = map[name];
|
||||
const starSelectables = map['*'];
|
||||
let selectables = map.get(name);
|
||||
const starSelectables = map.get('*');
|
||||
if (starSelectables) {
|
||||
selectables = selectables.concat(starSelectables);
|
||||
}
|
||||
@ -316,13 +316,13 @@ export class SelectorMatcher {
|
||||
|
||||
/** @internal */
|
||||
_matchPartial(
|
||||
map: {[k: string]: SelectorMatcher}, name: string, cssSelector: CssSelector,
|
||||
map: Map<string, SelectorMatcher>, name: string, cssSelector: CssSelector,
|
||||
matchedCallback: (c: CssSelector, a: any) => void): boolean {
|
||||
if (!map || typeof name !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nestedSelector = map[name];
|
||||
const nestedSelector = map.get(name);
|
||||
if (!nestedSelector) {
|
||||
return false;
|
||||
}
|
||||
|
439
modules/@angular/compiler/src/template_parser/binding_parser.ts
Normal file
439
modules/@angular/compiler/src/template_parser/binding_parser.ts
Normal file
@ -0,0 +1,439 @@
|
||||
/**
|
||||
* @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 {SecurityContext} from '@angular/core';
|
||||
|
||||
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata';
|
||||
import {AST, ASTWithSource, BindingPipe, EmptyExpr, Interpolation, LiteralPrimitive, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
|
||||
import {Parser} from '../expression_parser/parser';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {mergeNsAndName} from '../ml_parser/tags';
|
||||
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
|
||||
import {view_utils} from '../private_import_core';
|
||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||
import {CssSelector} from '../selector';
|
||||
import {splitAtColon, splitAtPeriod} from '../util';
|
||||
|
||||
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType, VariableAst} from './template_ast';
|
||||
|
||||
const PROPERTY_PARTS_SEPARATOR = '.';
|
||||
const ATTRIBUTE_PREFIX = 'attr';
|
||||
const CLASS_PREFIX = 'class';
|
||||
const STYLE_PREFIX = 'style';
|
||||
|
||||
const ANIMATE_PROP_PREFIX = 'animate-';
|
||||
|
||||
export enum BoundPropertyType {
|
||||
DEFAULT,
|
||||
LITERAL_ATTR,
|
||||
ANIMATION
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a parsed property.
|
||||
*/
|
||||
export class BoundProperty {
|
||||
constructor(
|
||||
public name: string, public expression: ASTWithSource, public type: BoundPropertyType,
|
||||
public sourceSpan: ParseSourceSpan) {}
|
||||
|
||||
get isLiteral() { return this.type === BoundPropertyType.LITERAL_ATTR; }
|
||||
|
||||
get isAnimation() { return this.type === BoundPropertyType.ANIMATION; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses bindings in templates and in the directive host area.
|
||||
*/
|
||||
export class BindingParser {
|
||||
pipesByName: Map<string, CompilePipeMetadata> = new Map();
|
||||
|
||||
constructor(
|
||||
private _exprParser: Parser, private _interpolationConfig: InterpolationConfig,
|
||||
private _schemaRegistry: ElementSchemaRegistry, pipes: CompilePipeMetadata[],
|
||||
private _targetErrors: ParseError[]) {
|
||||
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
|
||||
}
|
||||
|
||||
createDirectiveHostPropertyAsts(dirMeta: CompileDirectiveMetadata, sourceSpan: ParseSourceSpan):
|
||||
BoundElementPropertyAst[] {
|
||||
if (dirMeta.hostProperties) {
|
||||
const boundProps: BoundProperty[] = [];
|
||||
Object.keys(dirMeta.hostProperties).forEach(propName => {
|
||||
const expression = dirMeta.hostProperties[propName];
|
||||
if (typeof expression === 'string') {
|
||||
this.parsePropertyBinding(propName, expression, true, sourceSpan, [], boundProps);
|
||||
} else {
|
||||
this._reportError(
|
||||
`Value of the host property binding "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`,
|
||||
sourceSpan);
|
||||
}
|
||||
});
|
||||
return boundProps.map((prop) => this.createElementPropertyAst(dirMeta.selector, prop));
|
||||
}
|
||||
}
|
||||
|
||||
createDirectiveHostEventAsts(dirMeta: CompileDirectiveMetadata, sourceSpan: ParseSourceSpan):
|
||||
BoundEventAst[] {
|
||||
if (dirMeta.hostListeners) {
|
||||
const targetEventAsts: BoundEventAst[] = [];
|
||||
Object.keys(dirMeta.hostListeners).forEach(propName => {
|
||||
const expression = dirMeta.hostListeners[propName];
|
||||
if (typeof expression === 'string') {
|
||||
this.parseEvent(propName, expression, sourceSpan, [], targetEventAsts);
|
||||
} else {
|
||||
this._reportError(
|
||||
`Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`,
|
||||
sourceSpan);
|
||||
}
|
||||
});
|
||||
return targetEventAsts;
|
||||
}
|
||||
}
|
||||
|
||||
parseInterpolation(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
|
||||
const sourceInfo = sourceSpan.start.toString();
|
||||
|
||||
try {
|
||||
const ast = this._exprParser.parseInterpolation(value, sourceInfo, this._interpolationConfig);
|
||||
if (ast) this._reportExpressionParserErrors(ast.errors, sourceSpan);
|
||||
this._checkPipes(ast, sourceSpan);
|
||||
if (ast &&
|
||||
(<Interpolation>ast.ast).expressions.length > view_utils.MAX_INTERPOLATION_VALUES) {
|
||||
throw new Error(
|
||||
`Only support at most ${view_utils.MAX_INTERPOLATION_VALUES} interpolation values!`);
|
||||
}
|
||||
return ast;
|
||||
} catch (e) {
|
||||
this._reportError(`${e}`, sourceSpan);
|
||||
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
parseInlineTemplateBinding(
|
||||
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
|
||||
targetProps: BoundProperty[], targetVars: VariableAst[]) {
|
||||
const bindings = this._parseTemplateBindings(value, sourceSpan);
|
||||
for (let i = 0; i < bindings.length; i++) {
|
||||
const binding = bindings[i];
|
||||
if (binding.keyIsVar) {
|
||||
targetVars.push(new VariableAst(binding.key, binding.name, sourceSpan));
|
||||
} else if (isPresent(binding.expression)) {
|
||||
this._parsePropertyAst(
|
||||
binding.key, binding.expression, sourceSpan, targetMatchableAttrs, targetProps);
|
||||
} else {
|
||||
targetMatchableAttrs.push([binding.key, '']);
|
||||
this.parseLiteralAttr(binding.key, null, sourceSpan, targetMatchableAttrs, targetProps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _parseTemplateBindings(value: string, sourceSpan: ParseSourceSpan): TemplateBinding[] {
|
||||
const sourceInfo = sourceSpan.start.toString();
|
||||
|
||||
try {
|
||||
const bindingsResult = this._exprParser.parseTemplateBindings(value, sourceInfo);
|
||||
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
|
||||
bindingsResult.templateBindings.forEach((binding) => {
|
||||
if (isPresent(binding.expression)) {
|
||||
this._checkPipes(binding.expression, sourceSpan);
|
||||
}
|
||||
});
|
||||
bindingsResult.warnings.forEach(
|
||||
(warning) => { this._reportError(warning, sourceSpan, ParseErrorLevel.WARNING); });
|
||||
return bindingsResult.templateBindings;
|
||||
} catch (e) {
|
||||
this._reportError(`${e}`, sourceSpan);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
parseLiteralAttr(
|
||||
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
|
||||
targetProps: BoundProperty[]) {
|
||||
if (_isAnimationLabel(name)) {
|
||||
name = name.substring(1);
|
||||
if (value) {
|
||||
this._reportError(
|
||||
`Assigning animation triggers via @prop="exp" attributes with an expression is invalid.` +
|
||||
` Use property bindings (e.g. [@prop]="exp") or use an attribute without a value (e.g. @prop) instead.`,
|
||||
sourceSpan, ParseErrorLevel.FATAL);
|
||||
}
|
||||
this._parseAnimation(name, value, sourceSpan, targetMatchableAttrs, targetProps);
|
||||
} else {
|
||||
targetProps.push(new BoundProperty(
|
||||
name, this._exprParser.wrapLiteralPrimitive(value, ''), BoundPropertyType.LITERAL_ATTR,
|
||||
sourceSpan));
|
||||
}
|
||||
}
|
||||
|
||||
parsePropertyBinding(
|
||||
name: string, expression: string, isHost: boolean, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
|
||||
let isAnimationProp = false;
|
||||
if (name.startsWith(ANIMATE_PROP_PREFIX)) {
|
||||
isAnimationProp = true;
|
||||
name = name.substring(ANIMATE_PROP_PREFIX.length);
|
||||
} else if (_isAnimationLabel(name)) {
|
||||
isAnimationProp = true;
|
||||
name = name.substring(1);
|
||||
}
|
||||
|
||||
if (isAnimationProp) {
|
||||
this._parseAnimation(name, expression, sourceSpan, targetMatchableAttrs, targetProps);
|
||||
} else {
|
||||
this._parsePropertyAst(
|
||||
name, this._parseBinding(expression, isHost, sourceSpan), sourceSpan,
|
||||
targetMatchableAttrs, targetProps);
|
||||
}
|
||||
}
|
||||
|
||||
parsePropertyInterpolation(
|
||||
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
|
||||
targetProps: BoundProperty[]): boolean {
|
||||
const expr = this.parseInterpolation(value, sourceSpan);
|
||||
if (isPresent(expr)) {
|
||||
this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _parsePropertyAst(
|
||||
name: string, ast: ASTWithSource, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
|
||||
targetMatchableAttrs.push([name, ast.source]);
|
||||
targetProps.push(new BoundProperty(name, ast, BoundPropertyType.DEFAULT, sourceSpan));
|
||||
}
|
||||
|
||||
private _parseAnimation(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
|
||||
// This will occur when a @trigger is not paired with an expression.
|
||||
// For animations it is valid to not have an expression since */void
|
||||
// states will be applied by angular when the element is attached/detached
|
||||
const ast = this._parseBinding(expression || 'null', false, sourceSpan);
|
||||
targetMatchableAttrs.push([name, ast.source]);
|
||||
targetProps.push(new BoundProperty(name, ast, BoundPropertyType.ANIMATION, sourceSpan));
|
||||
}
|
||||
|
||||
private _parseBinding(value: string, isHostBinding: boolean, sourceSpan: ParseSourceSpan):
|
||||
ASTWithSource {
|
||||
const sourceInfo = sourceSpan.start.toString();
|
||||
|
||||
try {
|
||||
const ast = isHostBinding ?
|
||||
this._exprParser.parseSimpleBinding(value, sourceInfo, this._interpolationConfig) :
|
||||
this._exprParser.parseBinding(value, sourceInfo, this._interpolationConfig);
|
||||
if (ast) this._reportExpressionParserErrors(ast.errors, sourceSpan);
|
||||
this._checkPipes(ast, sourceSpan);
|
||||
return ast;
|
||||
} catch (e) {
|
||||
this._reportError(`${e}`, sourceSpan);
|
||||
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
createElementPropertyAst(elementSelector: string, boundProp: BoundProperty):
|
||||
BoundElementPropertyAst {
|
||||
if (boundProp.isAnimation) {
|
||||
return new BoundElementPropertyAst(
|
||||
boundProp.name, PropertyBindingType.Animation, SecurityContext.NONE, false,
|
||||
boundProp.expression, null, boundProp.sourceSpan);
|
||||
}
|
||||
|
||||
let unit: string = null;
|
||||
let bindingType: PropertyBindingType;
|
||||
let boundPropertyName: string;
|
||||
const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR);
|
||||
let securityContexts: SecurityContext[];
|
||||
|
||||
if (parts.length === 1) {
|
||||
var partValue = parts[0];
|
||||
boundPropertyName = this._schemaRegistry.getMappedPropName(partValue);
|
||||
securityContexts = calcPossibleSecurityContexts(
|
||||
this._schemaRegistry, elementSelector, boundPropertyName, false);
|
||||
bindingType = PropertyBindingType.Property;
|
||||
this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, false);
|
||||
} else {
|
||||
if (parts[0] == ATTRIBUTE_PREFIX) {
|
||||
boundPropertyName = parts[1];
|
||||
this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, true);
|
||||
securityContexts = calcPossibleSecurityContexts(
|
||||
this._schemaRegistry, elementSelector, boundPropertyName, true);
|
||||
|
||||
const nsSeparatorIdx = boundPropertyName.indexOf(':');
|
||||
if (nsSeparatorIdx > -1) {
|
||||
const ns = boundPropertyName.substring(0, nsSeparatorIdx);
|
||||
const name = boundPropertyName.substring(nsSeparatorIdx + 1);
|
||||
boundPropertyName = mergeNsAndName(ns, name);
|
||||
}
|
||||
|
||||
bindingType = PropertyBindingType.Attribute;
|
||||
} else if (parts[0] == CLASS_PREFIX) {
|
||||
boundPropertyName = parts[1];
|
||||
bindingType = PropertyBindingType.Class;
|
||||
securityContexts = [SecurityContext.NONE];
|
||||
} else if (parts[0] == STYLE_PREFIX) {
|
||||
unit = parts.length > 2 ? parts[2] : null;
|
||||
boundPropertyName = parts[1];
|
||||
bindingType = PropertyBindingType.Style;
|
||||
securityContexts = [SecurityContext.STYLE];
|
||||
} else {
|
||||
this._reportError(`Invalid property name '${boundProp.name}'`, boundProp.sourceSpan);
|
||||
bindingType = null;
|
||||
securityContexts = [];
|
||||
}
|
||||
}
|
||||
return new BoundElementPropertyAst(
|
||||
boundPropertyName, bindingType, securityContexts.length === 1 ? securityContexts[0] : null,
|
||||
securityContexts.length > 1, boundProp.expression, unit, boundProp.sourceSpan);
|
||||
}
|
||||
|
||||
parseEvent(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
|
||||
if (_isAnimationLabel(name)) {
|
||||
name = name.substr(1);
|
||||
this._parseAnimationEvent(name, expression, sourceSpan, targetEvents);
|
||||
} else {
|
||||
this._parseEvent(name, expression, sourceSpan, targetMatchableAttrs, targetEvents);
|
||||
}
|
||||
}
|
||||
|
||||
private _parseAnimationEvent(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetEvents: BoundEventAst[]) {
|
||||
const matches = splitAtPeriod(name, [name, '']);
|
||||
const eventName = matches[0];
|
||||
const phase = matches[1].toLowerCase();
|
||||
if (phase) {
|
||||
switch (phase) {
|
||||
case 'start':
|
||||
case 'done':
|
||||
const ast = this._parseAction(expression, sourceSpan);
|
||||
targetEvents.push(new BoundEventAst(eventName, null, phase, ast, sourceSpan));
|
||||
break;
|
||||
|
||||
default:
|
||||
this._reportError(
|
||||
`The provided animation output phase value "${phase}" for "@${eventName}" is not supported (use start or done)`,
|
||||
sourceSpan);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this._reportError(
|
||||
`The animation trigger output event (@${eventName}) is missing its phase value name (start or done are currently supported)`,
|
||||
sourceSpan);
|
||||
}
|
||||
}
|
||||
|
||||
private _parseEvent(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
|
||||
// long format: 'target: eventName'
|
||||
const [target, eventName] = splitAtColon(name, [null, name]);
|
||||
const ast = this._parseAction(expression, sourceSpan);
|
||||
targetMatchableAttrs.push([name, ast.source]);
|
||||
targetEvents.push(new BoundEventAst(eventName, target, null, ast, sourceSpan));
|
||||
// Don't detect directives for event names for now,
|
||||
// so don't add the event name to the matchableAttrs
|
||||
}
|
||||
|
||||
private _parseAction(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
|
||||
const sourceInfo = sourceSpan.start.toString();
|
||||
|
||||
try {
|
||||
const ast = this._exprParser.parseAction(value, sourceInfo, this._interpolationConfig);
|
||||
if (ast) {
|
||||
this._reportExpressionParserErrors(ast.errors, sourceSpan);
|
||||
}
|
||||
if (!ast || ast.ast instanceof EmptyExpr) {
|
||||
this._reportError(`Empty expressions are not allowed`, sourceSpan);
|
||||
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
|
||||
}
|
||||
this._checkPipes(ast, sourceSpan);
|
||||
return ast;
|
||||
} catch (e) {
|
||||
this._reportError(`${e}`, sourceSpan);
|
||||
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private _reportError(
|
||||
message: string, sourceSpan: ParseSourceSpan,
|
||||
level: ParseErrorLevel = ParseErrorLevel.FATAL) {
|
||||
this._targetErrors.push(new ParseError(sourceSpan, message, level));
|
||||
}
|
||||
|
||||
private _reportExpressionParserErrors(errors: ParserError[], sourceSpan: ParseSourceSpan) {
|
||||
for (const error of errors) {
|
||||
this._reportError(error.message, sourceSpan);
|
||||
}
|
||||
}
|
||||
|
||||
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
|
||||
if (isPresent(ast)) {
|
||||
const collector = new PipeCollector();
|
||||
ast.visit(collector);
|
||||
collector.pipes.forEach((pipeName) => {
|
||||
if (!this.pipesByName.has(pipeName)) {
|
||||
this._reportError(`The pipe '${pipeName}' could not be found`, sourceSpan);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param propName the name of the property / attribute
|
||||
* @param sourceSpan
|
||||
* @param isAttr true when binding to an attribute
|
||||
* @private
|
||||
*/
|
||||
private _validatePropertyOrAttributeName(
|
||||
propName: string, sourceSpan: ParseSourceSpan, isAttr: boolean): void {
|
||||
const report = isAttr ? this._schemaRegistry.validateAttribute(propName) :
|
||||
this._schemaRegistry.validateProperty(propName);
|
||||
if (report.error) {
|
||||
this._reportError(report.msg, sourceSpan, ParseErrorLevel.FATAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class PipeCollector extends RecursiveAstVisitor {
|
||||
pipes = new Set<string>();
|
||||
visitPipe(ast: BindingPipe, context: any): any {
|
||||
this.pipes.add(ast.name);
|
||||
ast.exp.visit(this);
|
||||
this.visitAll(ast.args, context);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function _isAnimationLabel(name: string): boolean {
|
||||
return name[0] == '@';
|
||||
}
|
||||
|
||||
export function calcPossibleSecurityContexts(
|
||||
registry: ElementSchemaRegistry, selector: string, propName: string,
|
||||
isAttribute: boolean): SecurityContext[] {
|
||||
const ctxs: SecurityContext[] = [];
|
||||
CssSelector.parse(selector).forEach((selector) => {
|
||||
const elementNames = selector.element ? [selector.element] : registry.allKnownElementNames();
|
||||
const notElementNames =
|
||||
new Set(selector.notSelectors.filter(selector => selector.isElementSelector())
|
||||
.map((selector) => selector.element));
|
||||
const possibleElementNames =
|
||||
elementNames.filter(elementName => !notElementNames.has(elementName));
|
||||
|
||||
ctxs.push(...possibleElementNames.map(
|
||||
elementName => registry.securityContext(elementName, propName, isAttribute)));
|
||||
});
|
||||
return ctxs.length === 0 ? [SecurityContext.NONE] : Array.from(new Set(ctxs)).sort();
|
||||
}
|
@ -63,8 +63,8 @@ export class AttrAst implements TemplateAst {
|
||||
export class BoundElementPropertyAst implements TemplateAst {
|
||||
constructor(
|
||||
public name: string, public type: PropertyBindingType,
|
||||
public securityContext: SecurityContext, public value: AST, public unit: string,
|
||||
public sourceSpan: ParseSourceSpan) {}
|
||||
public securityContext: SecurityContext, public needsRuntimeSecurityContext: boolean,
|
||||
public value: AST, public unit: string, public sourceSpan: ParseSourceSpan) {}
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitElementProperty(this, context);
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ import {ProviderElementContext, ProviderViewContext} from '../provider_analyzer'
|
||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||
import {CssSelector, SelectorMatcher} from '../selector';
|
||||
import {isStyleUrlResolvable} from '../style_url_resolver';
|
||||
import {splitAtColon, splitAtPeriod} from '../util';
|
||||
|
||||
import {BindingParser, BoundProperty} from './binding_parser';
|
||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from './template_ast';
|
||||
import {PreparsedElementType, preparseElement} from './template_preparser';
|
||||
|
||||
@ -56,17 +56,11 @@ const IDENT_BANANA_BOX_IDX = 8;
|
||||
const IDENT_PROPERTY_IDX = 9;
|
||||
const IDENT_EVENT_IDX = 10;
|
||||
|
||||
const ANIMATE_PROP_PREFIX = 'animate-';
|
||||
const TEMPLATE_ELEMENT = 'template';
|
||||
const TEMPLATE_ATTR = 'template';
|
||||
const TEMPLATE_ATTR_PREFIX = '*';
|
||||
const CLASS_ATTR = 'class';
|
||||
|
||||
const PROPERTY_PARTS_SEPARATOR = '.';
|
||||
const ATTRIBUTE_PREFIX = 'attr';
|
||||
const CLASS_PREFIX = 'class';
|
||||
const STYLE_PREFIX = 'style';
|
||||
|
||||
const TEXT_CSS_SELECTOR = CssSelector.parse('*')[0];
|
||||
|
||||
/**
|
||||
@ -135,11 +129,20 @@ export class TemplateParser {
|
||||
const uniqPipes = removeIdentifierDuplicates(pipes);
|
||||
const providerViewContext =
|
||||
new ProviderViewContext(component, htmlAstWithErrors.rootNodes[0].sourceSpan);
|
||||
let interpolationConfig: InterpolationConfig;
|
||||
if (component.template && component.template.interpolation) {
|
||||
interpolationConfig = {
|
||||
start: component.template.interpolation[0],
|
||||
end: component.template.interpolation[1]
|
||||
};
|
||||
}
|
||||
const bindingParser = new BindingParser(
|
||||
this._exprParser, interpolationConfig, this._schemaRegistry, uniqPipes, errors);
|
||||
const parseVisitor = new TemplateParseVisitor(
|
||||
providerViewContext, uniqDirectives, uniqPipes, schemas, this._exprParser,
|
||||
this._schemaRegistry);
|
||||
providerViewContext, uniqDirectives, bindingParser, this._schemaRegistry, schemas,
|
||||
errors);
|
||||
result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT);
|
||||
errors.push(...parseVisitor.errors, ...providerViewContext.errors);
|
||||
errors.push(...providerViewContext.errors);
|
||||
} else {
|
||||
result = [];
|
||||
}
|
||||
@ -197,130 +200,18 @@ export class TemplateParser {
|
||||
|
||||
class TemplateParseVisitor implements html.Visitor {
|
||||
selectorMatcher = new SelectorMatcher();
|
||||
errors: TemplateParseError[] = [];
|
||||
directivesIndex = new Map<CompileDirectiveMetadata, number>();
|
||||
ngContentCount: number = 0;
|
||||
pipesByName: Map<string, CompilePipeMetadata> = new Map();
|
||||
|
||||
private _interpolationConfig: InterpolationConfig;
|
||||
|
||||
constructor(
|
||||
public providerViewContext: ProviderViewContext, directives: CompileDirectiveMetadata[],
|
||||
pipes: CompilePipeMetadata[], private _schemas: SchemaMetadata[], private _exprParser: Parser,
|
||||
private _schemaRegistry: ElementSchemaRegistry) {
|
||||
const tempMeta = providerViewContext.component.template;
|
||||
|
||||
if (tempMeta && tempMeta.interpolation) {
|
||||
this._interpolationConfig = {
|
||||
start: tempMeta.interpolation[0],
|
||||
end: tempMeta.interpolation[1]
|
||||
};
|
||||
}
|
||||
|
||||
private _bindingParser: BindingParser, private _schemaRegistry: ElementSchemaRegistry,
|
||||
private _schemas: SchemaMetadata[], private _targetErrors: TemplateParseError[]) {
|
||||
directives.forEach((directive: CompileDirectiveMetadata, index: number) => {
|
||||
const selector = CssSelector.parse(directive.selector);
|
||||
this.selectorMatcher.addSelectables(selector, directive);
|
||||
this.directivesIndex.set(directive, index);
|
||||
});
|
||||
|
||||
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
|
||||
}
|
||||
|
||||
private _reportError(
|
||||
message: string, sourceSpan: ParseSourceSpan,
|
||||
level: ParseErrorLevel = ParseErrorLevel.FATAL) {
|
||||
this.errors.push(new TemplateParseError(message, sourceSpan, level));
|
||||
}
|
||||
|
||||
private _reportParserErrors(errors: ParserError[], sourceSpan: ParseSourceSpan) {
|
||||
for (const error of errors) {
|
||||
this._reportError(error.message, sourceSpan);
|
||||
}
|
||||
}
|
||||
|
||||
private _parseInterpolation(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
|
||||
const sourceInfo = sourceSpan.start.toString();
|
||||
|
||||
try {
|
||||
const ast = this._exprParser.parseInterpolation(value, sourceInfo, this._interpolationConfig);
|
||||
if (ast) this._reportParserErrors(ast.errors, sourceSpan);
|
||||
this._checkPipes(ast, sourceSpan);
|
||||
if (isPresent(ast) &&
|
||||
(<Interpolation>ast.ast).expressions.length > view_utils.MAX_INTERPOLATION_VALUES) {
|
||||
throw new Error(
|
||||
`Only support at most ${view_utils.MAX_INTERPOLATION_VALUES} interpolation values!`);
|
||||
}
|
||||
return ast;
|
||||
} catch (e) {
|
||||
this._reportError(`${e}`, sourceSpan);
|
||||
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private _parseAction(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
|
||||
const sourceInfo = sourceSpan.start.toString();
|
||||
|
||||
try {
|
||||
const ast = this._exprParser.parseAction(value, sourceInfo, this._interpolationConfig);
|
||||
if (ast) {
|
||||
this._reportParserErrors(ast.errors, sourceSpan);
|
||||
}
|
||||
if (!ast || ast.ast instanceof EmptyExpr) {
|
||||
this._reportError(`Empty expressions are not allowed`, sourceSpan);
|
||||
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
|
||||
}
|
||||
this._checkPipes(ast, sourceSpan);
|
||||
return ast;
|
||||
} catch (e) {
|
||||
this._reportError(`${e}`, sourceSpan);
|
||||
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private _parseBinding(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
|
||||
const sourceInfo = sourceSpan.start.toString();
|
||||
|
||||
try {
|
||||
const ast = this._exprParser.parseBinding(value, sourceInfo, this._interpolationConfig);
|
||||
if (ast) this._reportParserErrors(ast.errors, sourceSpan);
|
||||
this._checkPipes(ast, sourceSpan);
|
||||
return ast;
|
||||
} catch (e) {
|
||||
this._reportError(`${e}`, sourceSpan);
|
||||
return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private _parseTemplateBindings(value: string, sourceSpan: ParseSourceSpan): TemplateBinding[] {
|
||||
const sourceInfo = sourceSpan.start.toString();
|
||||
|
||||
try {
|
||||
const bindingsResult = this._exprParser.parseTemplateBindings(value, sourceInfo);
|
||||
this._reportParserErrors(bindingsResult.errors, sourceSpan);
|
||||
bindingsResult.templateBindings.forEach((binding) => {
|
||||
if (isPresent(binding.expression)) {
|
||||
this._checkPipes(binding.expression, sourceSpan);
|
||||
}
|
||||
});
|
||||
bindingsResult.warnings.forEach(
|
||||
(warning) => { this._reportError(warning, sourceSpan, ParseErrorLevel.WARNING); });
|
||||
return bindingsResult.templateBindings;
|
||||
} catch (e) {
|
||||
this._reportError(`${e}`, sourceSpan);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
|
||||
if (isPresent(ast)) {
|
||||
const collector = new PipeCollector();
|
||||
ast.visit(collector);
|
||||
collector.pipes.forEach((pipeName) => {
|
||||
if (!this.pipesByName.has(pipeName)) {
|
||||
this._reportError(`The pipe '${pipeName}' could not be found`, sourceSpan);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
visitExpansion(expansion: html.Expansion, context: any): any { return null; }
|
||||
@ -329,7 +220,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
|
||||
visitText(text: html.Text, parent: ElementContext): any {
|
||||
const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
|
||||
const expr = this._parseInterpolation(text.value, text.sourceSpan);
|
||||
const expr = this._bindingParser.parseInterpolation(text.value, text.sourceSpan);
|
||||
if (isPresent(expr)) {
|
||||
return new BoundTextAst(expr, ngContentIndex, text.sourceSpan);
|
||||
} else {
|
||||
@ -361,13 +252,12 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
}
|
||||
|
||||
const matchableAttrs: string[][] = [];
|
||||
const elementOrDirectiveProps: BoundElementOrDirectiveProperty[] = [];
|
||||
const elementOrDirectiveProps: BoundProperty[] = [];
|
||||
const elementOrDirectiveRefs: ElementOrDirectiveRef[] = [];
|
||||
const elementVars: VariableAst[] = [];
|
||||
const animationProps: BoundElementPropertyAst[] = [];
|
||||
const events: BoundEventAst[] = [];
|
||||
|
||||
const templateElementOrDirectiveProps: BoundElementOrDirectiveProperty[] = [];
|
||||
const templateElementOrDirectiveProps: BoundProperty[] = [];
|
||||
const templateMatchableAttrs: string[][] = [];
|
||||
const templateElementVars: VariableAst[] = [];
|
||||
|
||||
@ -378,16 +268,27 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
|
||||
element.attrs.forEach(attr => {
|
||||
const hasBinding = this._parseAttr(
|
||||
isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, animationProps, events,
|
||||
isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events,
|
||||
elementOrDirectiveRefs, elementVars);
|
||||
|
||||
const hasTemplateBinding = this._parseInlineTemplateBinding(
|
||||
attr, templateMatchableAttrs, templateElementOrDirectiveProps, templateElementVars);
|
||||
|
||||
if (hasTemplateBinding && hasInlineTemplates) {
|
||||
this._reportError(
|
||||
`Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with *`,
|
||||
attr.sourceSpan);
|
||||
let templateBindingsSource: string;
|
||||
if (this._normalizeAttributeName(attr.name) == TEMPLATE_ATTR) {
|
||||
templateBindingsSource = attr.value;
|
||||
} else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) {
|
||||
const key = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star
|
||||
templateBindingsSource = (attr.value.length == 0) ? key : key + ' ' + attr.value;
|
||||
}
|
||||
const hasTemplateBinding = isPresent(templateBindingsSource);
|
||||
if (hasTemplateBinding) {
|
||||
if (hasInlineTemplates) {
|
||||
this._reportError(
|
||||
`Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with *`,
|
||||
attr.sourceSpan);
|
||||
}
|
||||
hasInlineTemplates = true;
|
||||
this._bindingParser.parseInlineTemplateBinding(
|
||||
attr.name, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs,
|
||||
templateElementOrDirectiveProps, templateElementVars);
|
||||
}
|
||||
|
||||
if (!hasBinding && !hasTemplateBinding) {
|
||||
@ -395,10 +296,6 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
attrs.push(this.visitAttribute(attr, null));
|
||||
matchableAttrs.push([attr.name, attr.value]);
|
||||
}
|
||||
|
||||
if (hasTemplateBinding) {
|
||||
hasInlineTemplates = true;
|
||||
}
|
||||
});
|
||||
|
||||
const elementCssSelector = createElementCssSelector(nodeName, matchableAttrs);
|
||||
@ -409,8 +306,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
isTemplateElement, element.name, directiveMetas, elementOrDirectiveProps,
|
||||
elementOrDirectiveRefs, element.sourceSpan, references);
|
||||
const elementProps: BoundElementPropertyAst[] =
|
||||
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts)
|
||||
.concat(animationProps);
|
||||
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts);
|
||||
const isViewRoot = parent.isTemplateElement || hasInlineTemplates;
|
||||
const providerContext = new ProviderElementContext(
|
||||
this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs,
|
||||
@ -519,39 +415,9 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
});
|
||||
}
|
||||
|
||||
private _parseInlineTemplateBinding(
|
||||
attr: html.Attribute, targetMatchableAttrs: string[][],
|
||||
targetProps: BoundElementOrDirectiveProperty[], targetVars: VariableAst[]): boolean {
|
||||
let templateBindingsSource: string = null;
|
||||
if (this._normalizeAttributeName(attr.name) == TEMPLATE_ATTR) {
|
||||
templateBindingsSource = attr.value;
|
||||
} else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) {
|
||||
const key = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star
|
||||
templateBindingsSource = (attr.value.length == 0) ? key : key + ' ' + attr.value;
|
||||
}
|
||||
if (isPresent(templateBindingsSource)) {
|
||||
const bindings = this._parseTemplateBindings(templateBindingsSource, attr.sourceSpan);
|
||||
for (let i = 0; i < bindings.length; i++) {
|
||||
const binding = bindings[i];
|
||||
if (binding.keyIsVar) {
|
||||
targetVars.push(new VariableAst(binding.key, binding.name, attr.sourceSpan));
|
||||
} else if (isPresent(binding.expression)) {
|
||||
this._parsePropertyAst(
|
||||
binding.key, binding.expression, attr.sourceSpan, targetMatchableAttrs, targetProps);
|
||||
} else {
|
||||
targetMatchableAttrs.push([binding.key, '']);
|
||||
this._parseLiteralAttr(binding.key, null, attr.sourceSpan, targetProps);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _parseAttr(
|
||||
isTemplateElement: boolean, attr: html.Attribute, targetMatchableAttrs: string[][],
|
||||
targetProps: BoundElementOrDirectiveProperty[],
|
||||
targetAnimationProps: BoundElementPropertyAst[], targetEvents: BoundEventAst[],
|
||||
targetProps: BoundProperty[], targetEvents: BoundEventAst[],
|
||||
targetRefs: ElementOrDirectiveRef[], targetVars: VariableAst[]): boolean {
|
||||
const name = this._normalizeAttributeName(attr.name);
|
||||
const value = attr.value;
|
||||
@ -563,9 +429,8 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
if (bindParts !== null) {
|
||||
hasBinding = true;
|
||||
if (isPresent(bindParts[KW_BIND_IDX])) {
|
||||
this._parsePropertyOrAnimation(
|
||||
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetProps,
|
||||
targetAnimationProps);
|
||||
this._bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_KW_IDX], value, false, srcSpan, targetMatchableAttrs, targetProps);
|
||||
|
||||
} else if (bindParts[KW_LET_IDX]) {
|
||||
if (isTemplateElement) {
|
||||
@ -580,48 +445,42 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
this._parseReference(identifier, value, srcSpan, targetRefs);
|
||||
|
||||
} else if (bindParts[KW_ON_IDX]) {
|
||||
this._parseEventOrAnimationEvent(
|
||||
this._bindingParser.parseEvent(
|
||||
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
|
||||
|
||||
} else if (bindParts[KW_BINDON_IDX]) {
|
||||
this._parsePropertyOrAnimation(
|
||||
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetProps,
|
||||
targetAnimationProps);
|
||||
this._bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_KW_IDX], value, false, srcSpan, targetMatchableAttrs, targetProps);
|
||||
this._parseAssignmentEvent(
|
||||
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
|
||||
|
||||
} else if (bindParts[KW_AT_IDX]) {
|
||||
if (_isAnimationLabel(name) && isPresent(value) && value.length > 0) {
|
||||
this._reportError(
|
||||
`Assigning animation triggers via @prop="exp" attributes with an expression is invalid.` +
|
||||
` Use property bindings (e.g. [@prop]="exp") or use an attribute without a value (e.g. @prop) instead.`,
|
||||
srcSpan, ParseErrorLevel.FATAL);
|
||||
}
|
||||
this._parseAnimation(
|
||||
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetAnimationProps);
|
||||
this._bindingParser.parseLiteralAttr(
|
||||
name, value, srcSpan, targetMatchableAttrs, targetProps);
|
||||
|
||||
} else if (bindParts[IDENT_BANANA_BOX_IDX]) {
|
||||
this._parsePropertyOrAnimation(
|
||||
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, targetMatchableAttrs, targetProps,
|
||||
targetAnimationProps);
|
||||
this._bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_BANANA_BOX_IDX], value, false, srcSpan, targetMatchableAttrs,
|
||||
targetProps);
|
||||
this._parseAssignmentEvent(
|
||||
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
|
||||
|
||||
} else if (bindParts[IDENT_PROPERTY_IDX]) {
|
||||
this._parsePropertyOrAnimation(
|
||||
bindParts[IDENT_PROPERTY_IDX], value, srcSpan, targetMatchableAttrs, targetProps,
|
||||
targetAnimationProps);
|
||||
this._bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_PROPERTY_IDX], value, false, srcSpan, targetMatchableAttrs,
|
||||
targetProps);
|
||||
|
||||
} else if (bindParts[IDENT_EVENT_IDX]) {
|
||||
this._parseEventOrAnimationEvent(
|
||||
this._bindingParser.parseEvent(
|
||||
bindParts[IDENT_EVENT_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
|
||||
}
|
||||
} else {
|
||||
hasBinding =
|
||||
this._parsePropertyInterpolation(name, value, srcSpan, targetMatchableAttrs, targetProps);
|
||||
hasBinding = this._bindingParser.parsePropertyInterpolation(
|
||||
name, value, srcSpan, targetMatchableAttrs, targetProps);
|
||||
}
|
||||
|
||||
if (!hasBinding) {
|
||||
this._parseLiteralAttr(name, value, srcSpan, targetProps);
|
||||
this._bindingParser.parseLiteralAttr(name, value, srcSpan, targetMatchableAttrs, targetProps);
|
||||
}
|
||||
|
||||
return hasBinding;
|
||||
@ -650,127 +509,13 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
targetRefs.push(new ElementOrDirectiveRef(identifier, value, sourceSpan));
|
||||
}
|
||||
|
||||
private _parsePropertyOrAnimation(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: BoundElementOrDirectiveProperty[],
|
||||
targetAnimationProps: BoundElementPropertyAst[]) {
|
||||
const animatePropLength = ANIMATE_PROP_PREFIX.length;
|
||||
var isAnimationProp = _isAnimationLabel(name);
|
||||
var animationPrefixLength = 1;
|
||||
if (name.substring(0, animatePropLength) == ANIMATE_PROP_PREFIX) {
|
||||
isAnimationProp = true;
|
||||
animationPrefixLength = animatePropLength;
|
||||
}
|
||||
|
||||
if (isAnimationProp) {
|
||||
this._parseAnimation(
|
||||
name.substr(animationPrefixLength), expression, sourceSpan, targetMatchableAttrs,
|
||||
targetAnimationProps);
|
||||
} else {
|
||||
this._parsePropertyAst(
|
||||
name, this._parseBinding(expression, sourceSpan), sourceSpan, targetMatchableAttrs,
|
||||
targetProps);
|
||||
}
|
||||
}
|
||||
|
||||
private _parseAnimation(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetAnimationProps: BoundElementPropertyAst[]) {
|
||||
// This will occur when a @trigger is not paired with an expression.
|
||||
// For animations it is valid to not have an expression since */void
|
||||
// states will be applied by angular when the element is attached/detached
|
||||
if (!isPresent(expression) || expression.length == 0) {
|
||||
expression = 'null';
|
||||
}
|
||||
|
||||
const ast = this._parseBinding(expression, sourceSpan);
|
||||
targetMatchableAttrs.push([name, ast.source]);
|
||||
targetAnimationProps.push(new BoundElementPropertyAst(
|
||||
name, PropertyBindingType.Animation, SecurityContext.NONE, ast, null, sourceSpan));
|
||||
}
|
||||
|
||||
private _parsePropertyInterpolation(
|
||||
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
|
||||
targetProps: BoundElementOrDirectiveProperty[]): boolean {
|
||||
const expr = this._parseInterpolation(value, sourceSpan);
|
||||
if (isPresent(expr)) {
|
||||
this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _parsePropertyAst(
|
||||
name: string, ast: ASTWithSource, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: BoundElementOrDirectiveProperty[]) {
|
||||
targetMatchableAttrs.push([name, ast.source]);
|
||||
targetProps.push(new BoundElementOrDirectiveProperty(name, ast, false, sourceSpan));
|
||||
}
|
||||
|
||||
private _parseAssignmentEvent(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
|
||||
this._parseEventOrAnimationEvent(
|
||||
this._bindingParser.parseEvent(
|
||||
`${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, targetEvents);
|
||||
}
|
||||
|
||||
private _parseEventOrAnimationEvent(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
|
||||
if (_isAnimationLabel(name)) {
|
||||
name = name.substr(1);
|
||||
this._parseAnimationEvent(name, expression, sourceSpan, targetEvents);
|
||||
} else {
|
||||
this._parseEvent(name, expression, sourceSpan, targetMatchableAttrs, targetEvents);
|
||||
}
|
||||
}
|
||||
|
||||
private _parseAnimationEvent(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetEvents: BoundEventAst[]) {
|
||||
const matches = splitAtPeriod(name, [name, '']);
|
||||
const eventName = matches[0];
|
||||
const phase = matches[1].toLowerCase();
|
||||
if (phase) {
|
||||
switch (phase) {
|
||||
case 'start':
|
||||
case 'done':
|
||||
const ast = this._parseAction(expression, sourceSpan);
|
||||
targetEvents.push(new BoundEventAst(eventName, null, phase, ast, sourceSpan));
|
||||
break;
|
||||
|
||||
default:
|
||||
this._reportError(
|
||||
`The provided animation output phase value "${phase}" for "@${eventName}" is not supported (use start or done)`,
|
||||
sourceSpan);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
this._reportError(
|
||||
`The animation trigger output event (@${eventName}) is missing its phase value name (start or done are currently supported)`,
|
||||
sourceSpan);
|
||||
}
|
||||
}
|
||||
|
||||
private _parseEvent(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
|
||||
// long format: 'target: eventName'
|
||||
const [target, eventName] = splitAtColon(name, [null, name]);
|
||||
const ast = this._parseAction(expression, sourceSpan);
|
||||
targetMatchableAttrs.push([name, ast.source]);
|
||||
targetEvents.push(new BoundEventAst(eventName, target, null, ast, sourceSpan));
|
||||
// Don't detect directives for event names for now,
|
||||
// so don't add the event name to the matchableAttrs
|
||||
}
|
||||
|
||||
private _parseLiteralAttr(
|
||||
name: string, value: string, sourceSpan: ParseSourceSpan,
|
||||
targetProps: BoundElementOrDirectiveProperty[]) {
|
||||
targetProps.push(new BoundElementOrDirectiveProperty(
|
||||
name, this._exprParser.wrapLiteralPrimitive(value, ''), true, sourceSpan));
|
||||
}
|
||||
|
||||
private _parseDirectives(selectorMatcher: SelectorMatcher, elementCssSelector: CssSelector):
|
||||
{directives: CompileDirectiveMetadata[], matchElement: boolean} {
|
||||
// Need to sort the directives so that we get consistent results throughout,
|
||||
@ -793,7 +538,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
|
||||
private _createDirectiveAsts(
|
||||
isTemplateElement: boolean, elementName: string, directives: CompileDirectiveMetadata[],
|
||||
props: BoundElementOrDirectiveProperty[], elementOrDirectiveRefs: ElementOrDirectiveRef[],
|
||||
props: BoundProperty[], elementOrDirectiveRefs: ElementOrDirectiveRef[],
|
||||
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] {
|
||||
const matchedReferences = new Set<string>();
|
||||
let component: CompileDirectiveMetadata = null;
|
||||
@ -803,12 +548,13 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
if (directive.isComponent) {
|
||||
component = directive;
|
||||
}
|
||||
const hostProperties: BoundElementPropertyAst[] = [];
|
||||
const hostEvents: BoundEventAst[] = [];
|
||||
const directiveProperties: BoundDirectivePropertyAst[] = [];
|
||||
this._createDirectiveHostPropertyAsts(
|
||||
elementName, directive.hostProperties, sourceSpan, hostProperties);
|
||||
this._createDirectiveHostEventAsts(directive.hostListeners, sourceSpan, hostEvents);
|
||||
const hostProperties =
|
||||
this._bindingParser.createDirectiveHostPropertyAsts(directive, sourceSpan);
|
||||
// Note: We need to check the host properties here as well,
|
||||
// as we don't know the element name in the DirectiveWrapperCompiler yet.
|
||||
this._checkPropertiesInSchema(elementName, hostProperties);
|
||||
const hostEvents = this._bindingParser.createDirectiveHostEventAsts(directive, sourceSpan);
|
||||
this._createDirectivePropertyAsts(directive.inputs, props, directiveProperties);
|
||||
elementOrDirectiveRefs.forEach((elOrDirRef) => {
|
||||
if ((elOrDirRef.value.length === 0 && directive.isComponent) ||
|
||||
@ -839,47 +585,11 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
return directiveAsts;
|
||||
}
|
||||
|
||||
private _createDirectiveHostPropertyAsts(
|
||||
elementName: string, hostProps: {[key: string]: string}, sourceSpan: ParseSourceSpan,
|
||||
targetPropertyAsts: BoundElementPropertyAst[]) {
|
||||
if (hostProps) {
|
||||
Object.keys(hostProps).forEach(propName => {
|
||||
const expression = hostProps[propName];
|
||||
if (typeof expression === 'string') {
|
||||
const exprAst = this._parseBinding(expression, sourceSpan);
|
||||
targetPropertyAsts.push(
|
||||
this._createElementPropertyAst(elementName, propName, exprAst, sourceSpan));
|
||||
} else {
|
||||
this._reportError(
|
||||
`Value of the host property binding "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`,
|
||||
sourceSpan);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _createDirectiveHostEventAsts(
|
||||
hostListeners: {[key: string]: string}, sourceSpan: ParseSourceSpan,
|
||||
targetEventAsts: BoundEventAst[]) {
|
||||
if (hostListeners) {
|
||||
Object.keys(hostListeners).forEach(propName => {
|
||||
const expression = hostListeners[propName];
|
||||
if (typeof expression === 'string') {
|
||||
this._parseEventOrAnimationEvent(propName, expression, sourceSpan, [], targetEventAsts);
|
||||
} else {
|
||||
this._reportError(
|
||||
`Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`,
|
||||
sourceSpan);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _createDirectivePropertyAsts(
|
||||
directiveProperties: {[key: string]: string}, boundProps: BoundElementOrDirectiveProperty[],
|
||||
directiveProperties: {[key: string]: string}, boundProps: BoundProperty[],
|
||||
targetBoundDirectiveProps: BoundDirectivePropertyAst[]) {
|
||||
if (directiveProperties) {
|
||||
const boundPropsByName = new Map<string, BoundElementOrDirectiveProperty>();
|
||||
const boundPropsByName = new Map<string, BoundProperty>();
|
||||
boundProps.forEach(boundProp => {
|
||||
const prevValue = boundPropsByName.get(boundProp.name);
|
||||
if (!prevValue || prevValue.isLiteral) {
|
||||
@ -902,7 +612,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
}
|
||||
|
||||
private _createElementPropertyAsts(
|
||||
elementName: string, props: BoundElementOrDirectiveProperty[],
|
||||
elementName: string, props: BoundProperty[],
|
||||
directives: DirectiveAst[]): BoundElementPropertyAst[] {
|
||||
const boundElementProps: BoundElementPropertyAst[] = [];
|
||||
const boundDirectivePropsIndex = new Map<string, BoundDirectivePropertyAst>();
|
||||
@ -913,98 +623,15 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
});
|
||||
});
|
||||
|
||||
props.forEach((prop: BoundElementOrDirectiveProperty) => {
|
||||
props.forEach((prop: BoundProperty) => {
|
||||
if (!prop.isLiteral && !boundDirectivePropsIndex.get(prop.name)) {
|
||||
boundElementProps.push(this._createElementPropertyAst(
|
||||
elementName, prop.name, prop.expression, prop.sourceSpan));
|
||||
boundElementProps.push(this._bindingParser.createElementPropertyAst(elementName, prop));
|
||||
}
|
||||
});
|
||||
this._checkPropertiesInSchema(elementName, boundElementProps);
|
||||
return boundElementProps;
|
||||
}
|
||||
|
||||
private _createElementPropertyAst(
|
||||
elementName: string, name: string, ast: AST,
|
||||
sourceSpan: ParseSourceSpan): BoundElementPropertyAst {
|
||||
let unit: string = null;
|
||||
let bindingType: PropertyBindingType;
|
||||
let boundPropertyName: string;
|
||||
const parts = name.split(PROPERTY_PARTS_SEPARATOR);
|
||||
let securityContext: SecurityContext;
|
||||
|
||||
if (parts.length === 1) {
|
||||
var partValue = parts[0];
|
||||
if (_isAnimationLabel(partValue)) {
|
||||
boundPropertyName = partValue.substr(1);
|
||||
bindingType = PropertyBindingType.Animation;
|
||||
securityContext = SecurityContext.NONE;
|
||||
} else {
|
||||
boundPropertyName = this._schemaRegistry.getMappedPropName(partValue);
|
||||
securityContext = this._schemaRegistry.securityContext(elementName, boundPropertyName);
|
||||
bindingType = PropertyBindingType.Property;
|
||||
this._validatePropertyOrAttributeName(boundPropertyName, sourceSpan, false);
|
||||
if (!this._schemaRegistry.hasProperty(elementName, boundPropertyName, this._schemas)) {
|
||||
let errorMsg =
|
||||
`Can't bind to '${boundPropertyName}' since it isn't a known property of '${elementName}'.`;
|
||||
if (elementName.indexOf('-') > -1) {
|
||||
errorMsg +=
|
||||
`\n1. If '${elementName}' is an Angular component and it has '${boundPropertyName}' input, then verify that it is part of this module.` +
|
||||
`\n2. If '${elementName}' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message.\n`;
|
||||
}
|
||||
this._reportError(errorMsg, sourceSpan);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (parts[0] == ATTRIBUTE_PREFIX) {
|
||||
boundPropertyName = parts[1];
|
||||
this._validatePropertyOrAttributeName(boundPropertyName, sourceSpan, true);
|
||||
// NB: For security purposes, use the mapped property name, not the attribute name.
|
||||
const mapPropName = this._schemaRegistry.getMappedPropName(boundPropertyName);
|
||||
securityContext = this._schemaRegistry.securityContext(elementName, mapPropName);
|
||||
|
||||
const nsSeparatorIdx = boundPropertyName.indexOf(':');
|
||||
if (nsSeparatorIdx > -1) {
|
||||
const ns = boundPropertyName.substring(0, nsSeparatorIdx);
|
||||
const name = boundPropertyName.substring(nsSeparatorIdx + 1);
|
||||
boundPropertyName = mergeNsAndName(ns, name);
|
||||
}
|
||||
|
||||
bindingType = PropertyBindingType.Attribute;
|
||||
} else if (parts[0] == CLASS_PREFIX) {
|
||||
boundPropertyName = parts[1];
|
||||
bindingType = PropertyBindingType.Class;
|
||||
securityContext = SecurityContext.NONE;
|
||||
} else if (parts[0] == STYLE_PREFIX) {
|
||||
unit = parts.length > 2 ? parts[2] : null;
|
||||
boundPropertyName = parts[1];
|
||||
bindingType = PropertyBindingType.Style;
|
||||
securityContext = SecurityContext.STYLE;
|
||||
} else {
|
||||
this._reportError(`Invalid property name '${name}'`, sourceSpan);
|
||||
bindingType = null;
|
||||
securityContext = null;
|
||||
}
|
||||
}
|
||||
|
||||
return new BoundElementPropertyAst(
|
||||
boundPropertyName, bindingType, securityContext, ast, unit, sourceSpan);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param propName the name of the property / attribute
|
||||
* @param sourceSpan
|
||||
* @param isAttr true when binding to an attribute
|
||||
* @private
|
||||
*/
|
||||
private _validatePropertyOrAttributeName(
|
||||
propName: string, sourceSpan: ParseSourceSpan, isAttr: boolean): void {
|
||||
const report = isAttr ? this._schemaRegistry.validateAttribute(propName) :
|
||||
this._schemaRegistry.validateProperty(propName);
|
||||
if (report.error) {
|
||||
this._reportError(report.msg, sourceSpan, ParseErrorLevel.FATAL);
|
||||
}
|
||||
}
|
||||
|
||||
private _findComponentDirectives(directives: DirectiveAst[]): DirectiveAst[] {
|
||||
return directives.filter(directive => directive.directive.isComponent);
|
||||
}
|
||||
@ -1075,6 +702,28 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _checkPropertiesInSchema(elementName: string, boundProps: BoundElementPropertyAst[]) {
|
||||
boundProps.forEach((boundProp) => {
|
||||
if (boundProp.type === PropertyBindingType.Property &&
|
||||
!this._schemaRegistry.hasProperty(elementName, boundProp.name, this._schemas)) {
|
||||
let errorMsg =
|
||||
`Can't bind to '${boundProp.name}' since it isn't a known property of '${elementName}'.`;
|
||||
if (elementName.indexOf('-') > -1) {
|
||||
errorMsg +=
|
||||
`\n1. If '${elementName}' is an Angular component and it has '${boundProp.name}' input, then verify that it is part of this module.` +
|
||||
`\n2. If '${elementName}' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message.\n`;
|
||||
}
|
||||
this._reportError(errorMsg, boundProp.sourceSpan);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _reportError(
|
||||
message: string, sourceSpan: ParseSourceSpan,
|
||||
level: ParseErrorLevel = ParseErrorLevel.FATAL) {
|
||||
this._targetErrors.push(new ParseError(sourceSpan, message, level));
|
||||
}
|
||||
}
|
||||
|
||||
class NonBindableVisitor implements html.Visitor {
|
||||
@ -1113,12 +762,6 @@ class NonBindableVisitor implements html.Visitor {
|
||||
visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any { return expansionCase; }
|
||||
}
|
||||
|
||||
class BoundElementOrDirectiveProperty {
|
||||
constructor(
|
||||
public name: string, public expression: AST, public isLiteral: boolean,
|
||||
public sourceSpan: ParseSourceSpan) {}
|
||||
}
|
||||
|
||||
class ElementOrDirectiveRef {
|
||||
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||
}
|
||||
@ -1186,21 +829,6 @@ function createElementCssSelector(elementName: string, matchableAttrs: string[][
|
||||
const EMPTY_ELEMENT_CONTEXT = new ElementContext(true, new SelectorMatcher(), null, null);
|
||||
const NON_BINDABLE_VISITOR = new NonBindableVisitor();
|
||||
|
||||
|
||||
export class PipeCollector extends RecursiveAstVisitor {
|
||||
pipes: Set<string> = new Set<string>();
|
||||
visitPipe(ast: BindingPipe, context: any): any {
|
||||
this.pipes.add(ast.name);
|
||||
ast.exp.visit(this);
|
||||
this.visitAll(ast.args, context);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function _isAnimationLabel(name: string): boolean {
|
||||
return name[0] == '@';
|
||||
}
|
||||
|
||||
function _isEmptyTextNode(node: html.Node): boolean {
|
||||
return node instanceof html.Text && node.value.trim().length == 0;
|
||||
}
|
@ -6,9 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CompileTokenMetadata} from './compile_metadata';
|
||||
import {isBlank, isPresent, isPrimitive, isStrictStringMap} from './facade/lang';
|
||||
import * as o from './output/output_ast';
|
||||
import {isBlank, isPrimitive, isStrictStringMap} from './facade/lang';
|
||||
|
||||
export const MODULE_SUFFIX = '';
|
||||
|
||||
@ -72,25 +70,6 @@ export class ValueTransformer implements ValueVisitor {
|
||||
visitOther(value: any, context: any): any { return value; }
|
||||
}
|
||||
|
||||
export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string {
|
||||
if (path == null) {
|
||||
return `asset:@angular/lib/${pkg}/index`;
|
||||
} else {
|
||||
return `asset:@angular/lib/${pkg}/src/${path}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function createDiTokenExpression(token: CompileTokenMetadata): o.Expression {
|
||||
if (isPresent(token.value)) {
|
||||
return o.literal(token.value);
|
||||
} else if (token.identifierIsInstance) {
|
||||
return o.importExpr(token.identifier)
|
||||
.instantiate([], o.importType(token.identifier, [], [o.TypeModifier.Const]));
|
||||
} else {
|
||||
return o.importExpr(token.identifier);
|
||||
}
|
||||
}
|
||||
|
||||
export class SyncAsyncResult<T> {
|
||||
constructor(public syncResult: T, public asyncResult: Promise<T> = null) {
|
||||
if (!asyncResult) {
|
||||
|
@ -1,15 +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 {TemplateAst} from '../template_parser/template_ast';
|
||||
|
||||
import {CompileNode} from './compile_element';
|
||||
|
||||
export class CompileBinding {
|
||||
constructor(public node: CompileNode, public sourceAst: TemplateAst) {}
|
||||
}
|
@ -8,14 +8,14 @@
|
||||
|
||||
|
||||
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileIdentifierMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata} from '../compile_metadata';
|
||||
import {createDiTokenExpression} from '../compiler_util/identifier_util';
|
||||
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
|
||||
import {ListWrapper, MapWrapper} from '../facade/collection';
|
||||
import {MapWrapper} from '../facade/collection';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, identifierToken, resolveIdentifier, resolveIdentifierToken} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {convertValueToOutputAst} from '../output/value_util';
|
||||
import {ProviderAst, ProviderAstType, ReferenceAst, TemplateAst} from '../template_parser/template_ast';
|
||||
import {createDiTokenExpression} from '../util';
|
||||
|
||||
import {CompileMethod} from './compile_method';
|
||||
import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query';
|
||||
@ -218,9 +218,8 @@ export class CompileElement extends CompileNode {
|
||||
var queriesWithReads: _QueryWithRead[] = [];
|
||||
MapWrapper.values(this._resolvedProviders).forEach((resolvedProvider) => {
|
||||
var queriesForProvider = this._getQueriesFor(resolvedProvider.token);
|
||||
ListWrapper.addAll(
|
||||
queriesWithReads,
|
||||
queriesForProvider.map(query => new _QueryWithRead(query, resolvedProvider.token)));
|
||||
queriesWithReads.push(
|
||||
...queriesForProvider.map(query => new _QueryWithRead(query, resolvedProvider.token)));
|
||||
});
|
||||
Object.keys(this.referenceTokens).forEach(varName => {
|
||||
var token = this.referenceTokens[varName];
|
||||
@ -232,9 +231,8 @@ export class CompileElement extends CompileNode {
|
||||
}
|
||||
this.view.locals.set(varName, varValue);
|
||||
var varToken = new CompileTokenMetadata({value: varName});
|
||||
ListWrapper.addAll(
|
||||
queriesWithReads,
|
||||
this._getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
|
||||
queriesWithReads.push(
|
||||
...this._getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
|
||||
});
|
||||
queriesWithReads.forEach((queryWithRead) => {
|
||||
var value: o.Expression;
|
||||
@ -315,8 +313,7 @@ export class CompileElement extends CompileNode {
|
||||
while (!currentEl.isNull()) {
|
||||
queries = currentEl._queries.get(token.reference);
|
||||
if (isPresent(queries)) {
|
||||
ListWrapper.addAll(
|
||||
result, queries.filter((query) => query.meta.descendants || distance <= 1));
|
||||
result.push(...queries.filter((query) => query.meta.descendants || distance <= 1));
|
||||
}
|
||||
if (currentEl._directives.length > 0) {
|
||||
distance++;
|
||||
@ -325,7 +322,7 @@ export class CompileElement extends CompileNode {
|
||||
}
|
||||
queries = this.view.componentView.viewQueries.get(token.reference);
|
||||
if (isPresent(queries)) {
|
||||
ListWrapper.addAll(result, queries);
|
||||
result.push(...queries);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -6,7 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import * as o from '../output/output_ast';
|
||||
import {TemplateAst} from '../template_parser/template_ast';
|
||||
@ -66,6 +65,8 @@ export class CompileMethod {
|
||||
this._newState = new _DebugState(nodeIndex, templateAst);
|
||||
}
|
||||
|
||||
push(...stmts: o.Statement[]) { this.addStmts(stmts); }
|
||||
|
||||
addStmt(stmt: o.Statement) {
|
||||
this._updateDebugContextIfNeeded();
|
||||
this._bodyStatements.push(stmt);
|
||||
@ -73,7 +74,7 @@ export class CompileMethod {
|
||||
|
||||
addStmts(stmts: o.Statement[]) {
|
||||
this._updateDebugContextIfNeeded();
|
||||
ListWrapper.addAll(this._bodyStatements, stmts);
|
||||
this._bodyStatements.push(...stmts);
|
||||
}
|
||||
|
||||
finish(): o.Statement[] { return this._bodyStatements; }
|
||||
|
@ -8,11 +8,12 @@
|
||||
|
||||
|
||||
import {CompilePipeMetadata} from '../compile_metadata';
|
||||
import {createPureProxy} from '../compiler_util/identifier_util';
|
||||
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
|
||||
import {CompileView} from './compile_view';
|
||||
import {createPureProxy, getPropertyInView, injectFromViewParentInjector} from './util';
|
||||
import {getPropertyInView, injectFromViewParentInjector} from './util';
|
||||
|
||||
export class CompilePipe {
|
||||
static call(view: CompileView, name: string, args: o.Expression[]): o.Expression {
|
||||
@ -65,7 +66,8 @@ export class CompilePipe {
|
||||
createPureProxy(
|
||||
pipeInstanceSeenFromPureProxy.prop('transform')
|
||||
.callMethod(o.BuiltinMethod.Bind, [pipeInstanceSeenFromPureProxy]),
|
||||
args.length, purePipeProxyInstance, callingView);
|
||||
args.length, purePipeProxyInstance,
|
||||
{fields: callingView.fields, ctorStmts: callingView.createMethod});
|
||||
return o.importExpr(resolveIdentifier(Identifiers.castByValue))
|
||||
.callFn([purePipeProxyInstance, pipeInstanceSeenFromPureProxy.prop('transform')])
|
||||
.callFn(args);
|
||||
|
@ -8,21 +8,20 @@
|
||||
|
||||
import {AnimationEntryCompileResult} from '../animation/animation_compiler';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompilePipeMetadata} from '../compile_metadata';
|
||||
import {EventHandlerVars, NameResolver} from '../compiler_util/expression_converter';
|
||||
import {createPureProxy} from '../compiler_util/identifier_util';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {ListWrapper, MapWrapper} from '../facade/collection';
|
||||
import {MapWrapper} from '../facade/collection';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, resolveIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ViewType} from '../private_import_core';
|
||||
|
||||
import {CompileBinding} from './compile_binding';
|
||||
import {CompileElement, CompileNode} from './compile_element';
|
||||
import {CompileMethod} from './compile_method';
|
||||
import {CompilePipe} from './compile_pipe';
|
||||
import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query';
|
||||
import {EventHandlerVars} from './constants';
|
||||
import {NameResolver} from './expression_converter';
|
||||
import {createPureProxy, getPropertyInView, getViewFactoryName} from './util';
|
||||
import {getPropertyInView, getViewFactoryName} from './util';
|
||||
|
||||
export class CompileView implements NameResolver {
|
||||
public viewType: ViewType;
|
||||
@ -32,9 +31,6 @@ export class CompileView implements NameResolver {
|
||||
// root nodes or AppElements for ViewContainers
|
||||
public rootNodesOrAppElements: o.Expression[] = [];
|
||||
|
||||
public bindings: CompileBinding[] = [];
|
||||
|
||||
public classStatements: o.Statement[] = [];
|
||||
public createMethod: CompileMethod;
|
||||
public animationBindingsMethod: CompileMethod;
|
||||
public injectorGetMethod: CompileMethod;
|
||||
@ -47,8 +43,9 @@ export class CompileView implements NameResolver {
|
||||
public afterViewLifecycleCallbacksMethod: CompileMethod;
|
||||
public destroyMethod: CompileMethod;
|
||||
public detachMethod: CompileMethod;
|
||||
public eventHandlerMethods: o.ClassMethod[] = [];
|
||||
public methods: o.ClassMethod[] = [];
|
||||
|
||||
public ctorStmts: o.Statement[] = [];
|
||||
public fields: o.ClassField[] = [];
|
||||
public getters: o.ClassGetter[] = [];
|
||||
public disposables: o.Expression[] = [];
|
||||
@ -102,7 +99,7 @@ export class CompileView implements NameResolver {
|
||||
var viewQueries = new Map<any, CompileQuery[]>();
|
||||
if (this.viewType === ViewType.COMPONENT) {
|
||||
var directiveInstance = o.THIS_EXPR.prop('context');
|
||||
ListWrapper.forEachWithIndex(this.component.viewQueries, (queryMeta, queryIndex) => {
|
||||
this.component.viewQueries.forEach((queryMeta, queryIndex) => {
|
||||
var propName = `_viewQuery_${queryMeta.selectors[0].name}_${queryIndex}`;
|
||||
var queryList = createQueryList(queryMeta, directiveInstance, propName, this);
|
||||
var query = new CompileQuery(queryMeta, queryList, directiveInstance, this);
|
||||
@ -149,48 +146,6 @@ export class CompileView implements NameResolver {
|
||||
}
|
||||
}
|
||||
|
||||
createLiteralArray(values: o.Expression[]): o.Expression {
|
||||
if (values.length === 0) {
|
||||
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_ARRAY));
|
||||
}
|
||||
var proxyExpr = o.THIS_EXPR.prop(`_arr_${this.literalArrayCount++}`);
|
||||
var proxyParams: o.FnParam[] = [];
|
||||
var proxyReturnEntries: o.Expression[] = [];
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var paramName = `p${i}`;
|
||||
proxyParams.push(new o.FnParam(paramName));
|
||||
proxyReturnEntries.push(o.variable(paramName));
|
||||
}
|
||||
createPureProxy(
|
||||
o.fn(
|
||||
proxyParams, [new o.ReturnStatement(o.literalArr(proxyReturnEntries))],
|
||||
new o.ArrayType(o.DYNAMIC_TYPE)),
|
||||
values.length, proxyExpr, this);
|
||||
return proxyExpr.callFn(values);
|
||||
}
|
||||
|
||||
createLiteralMap(entries: [string, o.Expression][]): o.Expression {
|
||||
if (entries.length === 0) {
|
||||
return o.importExpr(resolveIdentifier(Identifiers.EMPTY_MAP));
|
||||
}
|
||||
const proxyExpr = o.THIS_EXPR.prop(`_map_${this.literalMapCount++}`);
|
||||
const proxyParams: o.FnParam[] = [];
|
||||
const proxyReturnEntries: [string, o.Expression][] = [];
|
||||
const values: o.Expression[] = [];
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
const paramName = `p${i}`;
|
||||
proxyParams.push(new o.FnParam(paramName));
|
||||
proxyReturnEntries.push([entries[i][0], o.variable(paramName)]);
|
||||
values.push(<o.Expression>entries[i][1]);
|
||||
}
|
||||
createPureProxy(
|
||||
o.fn(
|
||||
proxyParams, [new o.ReturnStatement(o.literalMap(proxyReturnEntries))],
|
||||
new o.MapType(o.DYNAMIC_TYPE)),
|
||||
entries.length, proxyExpr, this);
|
||||
return proxyExpr.callFn(values);
|
||||
}
|
||||
|
||||
afterNodes() {
|
||||
MapWrapper.values(this.viewQueries)
|
||||
.forEach(
|
||||
|
@ -9,9 +9,9 @@
|
||||
import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {CompileIdentifierMetadata} from '../compile_metadata';
|
||||
import {Identifiers, resolveEnumIdentifier, resolveIdentifier} from '../identifiers';
|
||||
import {createEnumExpression} from '../compiler_util/identifier_util';
|
||||
import {Identifiers, resolveEnumIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
|
||||
import {ChangeDetectorStatus, ViewType} from '../private_import_core';
|
||||
|
||||
function _enumExpression(classIdentifier: CompileIdentifierMetadata, name: string): o.Expression {
|
||||
@ -20,69 +20,25 @@ function _enumExpression(classIdentifier: CompileIdentifierMetadata, name: strin
|
||||
|
||||
export class ViewTypeEnum {
|
||||
static fromValue(value: ViewType): o.Expression {
|
||||
const viewType = resolveIdentifier(Identifiers.ViewType);
|
||||
switch (value) {
|
||||
case ViewType.HOST:
|
||||
return _enumExpression(viewType, 'HOST');
|
||||
case ViewType.COMPONENT:
|
||||
return _enumExpression(viewType, 'COMPONENT');
|
||||
case ViewType.EMBEDDED:
|
||||
return _enumExpression(viewType, 'EMBEDDED');
|
||||
default:
|
||||
throw Error(`Inavlid ViewType value: ${value}`);
|
||||
}
|
||||
return createEnumExpression(Identifiers.ViewType, value);
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewEncapsulationEnum {
|
||||
static fromValue(value: ViewEncapsulation): o.Expression {
|
||||
const viewEncapsulation = resolveIdentifier(Identifiers.ViewEncapsulation);
|
||||
switch (value) {
|
||||
case ViewEncapsulation.Emulated:
|
||||
return _enumExpression(viewEncapsulation, 'Emulated');
|
||||
case ViewEncapsulation.Native:
|
||||
return _enumExpression(viewEncapsulation, 'Native');
|
||||
case ViewEncapsulation.None:
|
||||
return _enumExpression(viewEncapsulation, 'None');
|
||||
default:
|
||||
throw Error(`Inavlid ViewEncapsulation value: ${value}`);
|
||||
}
|
||||
return createEnumExpression(Identifiers.ViewEncapsulation, value);
|
||||
}
|
||||
}
|
||||
|
||||
export class ChangeDetectionStrategyEnum {
|
||||
static fromValue(value: ChangeDetectionStrategy): o.Expression {
|
||||
const changeDetectionStrategy = resolveIdentifier(Identifiers.ChangeDetectionStrategy);
|
||||
switch (value) {
|
||||
case ChangeDetectionStrategy.OnPush:
|
||||
return _enumExpression(changeDetectionStrategy, 'OnPush');
|
||||
case ChangeDetectionStrategy.Default:
|
||||
return _enumExpression(changeDetectionStrategy, 'Default');
|
||||
default:
|
||||
throw Error(`Inavlid ChangeDetectionStrategy value: ${value}`);
|
||||
}
|
||||
return createEnumExpression(Identifiers.ChangeDetectionStrategy, value);
|
||||
}
|
||||
}
|
||||
|
||||
export class ChangeDetectorStatusEnum {
|
||||
static fromValue(value: ChangeDetectorStatusEnum): o.Expression {
|
||||
const changeDetectorStatus = resolveIdentifier(Identifiers.ChangeDetectorStatus);
|
||||
switch (value) {
|
||||
case ChangeDetectorStatus.CheckOnce:
|
||||
return _enumExpression(changeDetectorStatus, 'CheckOnce');
|
||||
case ChangeDetectorStatus.Checked:
|
||||
return _enumExpression(changeDetectorStatus, 'Checked');
|
||||
case ChangeDetectorStatus.CheckAlways:
|
||||
return _enumExpression(changeDetectorStatus, 'CheckAlways');
|
||||
case ChangeDetectorStatus.Detached:
|
||||
return _enumExpression(changeDetectorStatus, 'Detached');
|
||||
case ChangeDetectorStatus.Errored:
|
||||
return _enumExpression(changeDetectorStatus, 'Errored');
|
||||
case ChangeDetectorStatus.Destroyed:
|
||||
return _enumExpression(changeDetectorStatus, 'Destroyed');
|
||||
default:
|
||||
throw Error(`Inavlid ChangeDetectorStatus value: ${value}`);
|
||||
}
|
||||
return createEnumExpression(Identifiers.ChangeDetectorStatus, value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,8 +54,6 @@ export class ViewProperties {
|
||||
static viewUtils = o.THIS_EXPR.prop('viewUtils');
|
||||
}
|
||||
|
||||
export class EventHandlerVars { static event = o.variable('$event'); }
|
||||
|
||||
export class InjectMethodVars {
|
||||
static token = o.variable('token');
|
||||
static requestNodeIndex = o.variable('requestNodeIndex');
|
||||
@ -110,5 +64,4 @@ export class DetectChangesVars {
|
||||
static throwOnChange = o.variable(`throwOnChange`);
|
||||
static changes = o.variable(`changes`);
|
||||
static changed = o.variable(`changed`);
|
||||
static valUnwrapper = o.variable(`valUnwrapper`);
|
||||
}
|
||||
|
@ -7,16 +7,15 @@
|
||||
*/
|
||||
|
||||
import {CompileDirectiveMetadata} from '../compile_metadata';
|
||||
import {EventHandlerVars, convertActionBinding} from '../compiler_util/expression_converter';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {identifierToken} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast';
|
||||
|
||||
import {CompileBinding} from './compile_binding';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileMethod} from './compile_method';
|
||||
import {EventHandlerVars, ViewProperties} from './constants';
|
||||
import {convertCdStatementToIr} from './expression_converter';
|
||||
import {ViewProperties} from './constants';
|
||||
|
||||
export class CompileEventListener {
|
||||
private _method: CompileMethod;
|
||||
@ -61,23 +60,14 @@ export class CompileEventListener {
|
||||
}
|
||||
this._method.resetDebugInfo(this.compileElement.nodeIndex, hostEvent);
|
||||
var context = directiveInstance || this.compileElement.view.componentContext;
|
||||
var actionStmts = convertCdStatementToIr(
|
||||
this.compileElement.view, context, hostEvent.handler, this.compileElement.nodeIndex);
|
||||
var lastIndex = actionStmts.length - 1;
|
||||
if (lastIndex >= 0) {
|
||||
var lastStatement = actionStmts[lastIndex];
|
||||
var returnExpr = convertStmtIntoExpression(lastStatement);
|
||||
var preventDefaultVar = o.variable(`pd_${this._actionResultExprs.length}`);
|
||||
this._actionResultExprs.push(preventDefaultVar);
|
||||
if (isPresent(returnExpr)) {
|
||||
// Note: We need to cast the result of the method call to dynamic,
|
||||
// as it might be a void method!
|
||||
actionStmts[lastIndex] =
|
||||
preventDefaultVar.set(returnExpr.cast(o.DYNAMIC_TYPE).notIdentical(o.literal(false)))
|
||||
.toDeclStmt(null, [o.StmtModifier.Final]);
|
||||
}
|
||||
const view = this.compileElement.view;
|
||||
const evalResult = convertActionBinding(
|
||||
view, directive ? null : view, context, hostEvent.handler,
|
||||
`${this.compileElement.nodeIndex}_${this._actionResultExprs.length}`);
|
||||
if (evalResult.preventDefault) {
|
||||
this._actionResultExprs.push(evalResult.preventDefault);
|
||||
}
|
||||
this._method.addStmts(actionStmts);
|
||||
this._method.addStmts(evalResult.stmts);
|
||||
}
|
||||
|
||||
finishMethod() {
|
||||
@ -91,7 +81,7 @@ export class CompileEventListener {
|
||||
.concat(this._method.finish())
|
||||
.concat([new o.ReturnStatement(resultExpr)]);
|
||||
// private is fine here as no child view will reference the event handler...
|
||||
this.compileElement.view.eventHandlerMethods.push(new o.ClassMethod(
|
||||
this.compileElement.view.methods.push(new o.ClassMethod(
|
||||
this._methodName, [this._eventParam], stmts, o.BOOL_TYPE, [o.StmtModifier.Private]));
|
||||
}
|
||||
|
||||
@ -143,7 +133,6 @@ export function collectEventListeners(
|
||||
const eventListeners: CompileEventListener[] = [];
|
||||
|
||||
hostEvents.forEach((hostEvent) => {
|
||||
compileElement.view.bindings.push(new CompileBinding(compileElement, hostEvent));
|
||||
var listener = CompileEventListener.getOrCreate(
|
||||
compileElement, hostEvent.target, hostEvent.name, hostEvent.phase, eventListeners);
|
||||
listener.addAction(hostEvent, null, null);
|
||||
@ -153,7 +142,6 @@ export function collectEventListeners(
|
||||
var directiveInstance =
|
||||
compileElement.instances.get(identifierToken(directiveAst.directive.type).reference);
|
||||
directiveAst.hostEvents.forEach((hostEvent) => {
|
||||
compileElement.view.bindings.push(new CompileBinding(compileElement, hostEvent));
|
||||
var listener = CompileEventListener.getOrCreate(
|
||||
compileElement, hostEvent.target, hostEvent.name, hostEvent.phase, eventListeners);
|
||||
listener.addAction(hostEvent, directiveAst.directive, directiveInstance);
|
||||
|
@ -8,103 +8,40 @@
|
||||
|
||||
import {SecurityContext} from '@angular/core';
|
||||
|
||||
import {createCheckBindingField, createCheckBindingStmt} from '../compiler_util/binding_util';
|
||||
import {ConvertPropertyBindingResult, convertPropertyBinding} from '../compiler_util/expression_converter';
|
||||
import {createEnumExpression} from '../compiler_util/identifier_util';
|
||||
import {writeToRenderer} from '../compiler_util/render_util';
|
||||
import * as cdAst from '../expression_parser/ast';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, resolveIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {EMPTY_STATE as EMPTY_ANIMATION_STATE, LifecycleHooks, isDefaultChangeDetectionStrategy} from '../private_import_core';
|
||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||
import {BoundElementPropertyAst, BoundTextAst, DirectiveAst, PropertyBindingType} from '../template_parser/template_ast';
|
||||
import {camelCaseToDashCase} from '../util';
|
||||
|
||||
import {CompileBinding} from './compile_binding';
|
||||
import {CompileElement, CompileNode} from './compile_element';
|
||||
import {CompileMethod} from './compile_method';
|
||||
import {CompileView} from './compile_view';
|
||||
import {DetectChangesVars, ViewProperties} from './constants';
|
||||
import {CompileEventListener} from './event_binder';
|
||||
import {convertCdExpressionToIr, temporaryDeclaration} from './expression_converter';
|
||||
|
||||
function createBindFieldExpr(exprIndex: number): o.ReadPropExpr {
|
||||
return o.THIS_EXPR.prop(`_expr_${exprIndex}`);
|
||||
}
|
||||
|
||||
function createCurrValueExpr(exprIndex: number): o.ReadVarExpr {
|
||||
return o.variable(`currVal_${exprIndex}`); // fix syntax highlighting: `
|
||||
}
|
||||
|
||||
class EvalResult {
|
||||
constructor(public forceUpdate: o.Expression) {}
|
||||
}
|
||||
|
||||
function evalCdAst(
|
||||
view: CompileView, currValExpr: o.ReadVarExpr, parsedExpression: cdAst.AST,
|
||||
context: o.Expression, method: CompileMethod, bindingIndex: number): EvalResult {
|
||||
var checkExpression = convertCdExpressionToIr(
|
||||
view, context, parsedExpression, DetectChangesVars.valUnwrapper, bindingIndex);
|
||||
if (!checkExpression.expression) {
|
||||
// e.g. an empty expression was given
|
||||
export function bindRenderText(
|
||||
boundText: BoundTextAst, compileNode: CompileNode, view: CompileView): void {
|
||||
const valueField = createCheckBindingField(view);
|
||||
const evalResult = convertPropertyBinding(
|
||||
view, view, view.componentContext, boundText.value, valueField.bindingId);
|
||||
if (!evalResult) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (checkExpression.temporaryCount) {
|
||||
for (let i = 0; i < checkExpression.temporaryCount; i++) {
|
||||
method.addStmt(temporaryDeclaration(bindingIndex, i));
|
||||
}
|
||||
}
|
||||
|
||||
if (checkExpression.needsValueUnwrapper) {
|
||||
var initValueUnwrapperStmt = DetectChangesVars.valUnwrapper.callMethod('reset', []).toStmt();
|
||||
method.addStmt(initValueUnwrapperStmt);
|
||||
}
|
||||
method.addStmt(
|
||||
currValExpr.set(checkExpression.expression).toDeclStmt(null, [o.StmtModifier.Final]));
|
||||
if (checkExpression.needsValueUnwrapper) {
|
||||
return new EvalResult(DetectChangesVars.valUnwrapper.prop('hasWrappedValue'));
|
||||
} else {
|
||||
return new EvalResult(null);
|
||||
}
|
||||
}
|
||||
|
||||
function bind(
|
||||
view: CompileView, currValExpr: o.ReadVarExpr, fieldExpr: o.ReadPropExpr,
|
||||
parsedExpression: cdAst.AST, context: o.Expression, actions: o.Statement[],
|
||||
method: CompileMethod, bindingIndex: number) {
|
||||
const evalResult = evalCdAst(view, currValExpr, parsedExpression, context, method, bindingIndex);
|
||||
if (!evalResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
// private is fine here as no child view will reference the cached value...
|
||||
view.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private]));
|
||||
view.createMethod.addStmt(o.THIS_EXPR.prop(fieldExpr.name)
|
||||
.set(o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED)))
|
||||
.toStmt());
|
||||
|
||||
var condition: o.Expression = o.importExpr(resolveIdentifier(Identifiers.checkBinding)).callFn([
|
||||
DetectChangesVars.throwOnChange, fieldExpr, currValExpr
|
||||
]);
|
||||
if (evalResult.forceUpdate) {
|
||||
condition = evalResult.forceUpdate.or(condition);
|
||||
}
|
||||
method.addStmt(new o.IfStmt(
|
||||
condition,
|
||||
actions.concat([<o.Statement>o.THIS_EXPR.prop(fieldExpr.name).set(currValExpr).toStmt()])));
|
||||
}
|
||||
|
||||
export function bindRenderText(
|
||||
boundText: BoundTextAst, compileNode: CompileNode, view: CompileView) {
|
||||
var bindingIndex = view.bindings.length;
|
||||
view.bindings.push(new CompileBinding(compileNode, boundText));
|
||||
var currValExpr = createCurrValueExpr(bindingIndex);
|
||||
var valueField = createBindFieldExpr(bindingIndex);
|
||||
view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileNode.nodeIndex, boundText);
|
||||
|
||||
bind(
|
||||
view, currValExpr, valueField, boundText.value, view.componentContext,
|
||||
view.detectChangesRenderPropertiesMethod.addStmts(createCheckBindingStmt(
|
||||
evalResult, valueField.expression, DetectChangesVars.throwOnChange,
|
||||
[o.THIS_EXPR.prop('renderer')
|
||||
.callMethod('setText', [compileNode.renderNode, currValExpr])
|
||||
.toStmt()],
|
||||
view.detectChangesRenderPropertiesMethod, bindingIndex);
|
||||
.callMethod('setText', [compileNode.renderNode, evalResult.currValExpr])
|
||||
.toStmt()]));
|
||||
}
|
||||
|
||||
function bindAndWriteToRenderer(
|
||||
@ -113,52 +50,20 @@ function bindAndWriteToRenderer(
|
||||
var view = compileElement.view;
|
||||
var renderNode = compileElement.renderNode;
|
||||
boundProps.forEach((boundProp) => {
|
||||
var bindingIndex = view.bindings.length;
|
||||
view.bindings.push(new CompileBinding(compileElement, boundProp));
|
||||
const bindingField = createCheckBindingField(view);
|
||||
view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileElement.nodeIndex, boundProp);
|
||||
var fieldExpr = createBindFieldExpr(bindingIndex);
|
||||
var currValExpr = createCurrValueExpr(bindingIndex);
|
||||
var oldRenderValue: o.Expression = sanitizedValue(boundProp, fieldExpr);
|
||||
var renderValue: o.Expression = sanitizedValue(boundProp, currValExpr);
|
||||
const evalResult = convertPropertyBinding(
|
||||
view, isHostProp ? null : view, context, boundProp.value, bindingField.bindingId);
|
||||
var updateStmts: o.Statement[] = [];
|
||||
var compileMethod = view.detectChangesRenderPropertiesMethod;
|
||||
switch (boundProp.type) {
|
||||
case PropertyBindingType.Property:
|
||||
if (view.genConfig.logBindingUpdate) {
|
||||
updateStmts.push(logBindingUpdateStmt(renderNode, boundProp.name, renderValue));
|
||||
}
|
||||
updateStmts.push(
|
||||
o.THIS_EXPR.prop('renderer')
|
||||
.callMethod(
|
||||
'setElementProperty', [renderNode, o.literal(boundProp.name), renderValue])
|
||||
.toStmt());
|
||||
break;
|
||||
case PropertyBindingType.Attribute:
|
||||
renderValue =
|
||||
renderValue.isBlank().conditional(o.NULL_EXPR, renderValue.callMethod('toString', []));
|
||||
updateStmts.push(
|
||||
o.THIS_EXPR.prop('renderer')
|
||||
.callMethod(
|
||||
'setElementAttribute', [renderNode, o.literal(boundProp.name), renderValue])
|
||||
.toStmt());
|
||||
break;
|
||||
case PropertyBindingType.Class:
|
||||
updateStmts.push(
|
||||
o.THIS_EXPR.prop('renderer')
|
||||
.callMethod('setElementClass', [renderNode, o.literal(boundProp.name), renderValue])
|
||||
.toStmt());
|
||||
break;
|
||||
case PropertyBindingType.Style:
|
||||
var strValue: o.Expression = renderValue.callMethod('toString', []);
|
||||
if (isPresent(boundProp.unit)) {
|
||||
strValue = strValue.plus(o.literal(boundProp.unit));
|
||||
}
|
||||
|
||||
renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, strValue);
|
||||
updateStmts.push(
|
||||
o.THIS_EXPR.prop('renderer')
|
||||
.callMethod('setElementStyle', [renderNode, o.literal(boundProp.name), renderValue])
|
||||
.toStmt());
|
||||
updateStmts.push(...writeToRenderer(
|
||||
o.THIS_EXPR, boundProp, renderNode, evalResult.currValExpr,
|
||||
view.genConfig.logBindingUpdate));
|
||||
break;
|
||||
case PropertyBindingType.Animation:
|
||||
compileMethod = view.animationBindingsMethod;
|
||||
@ -177,20 +82,22 @@ function bindAndWriteToRenderer(
|
||||
const unitializedValue = o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED));
|
||||
const animationTransitionVar = o.variable('animationTransition_' + animationName);
|
||||
|
||||
updateStmts.push(
|
||||
animationTransitionVar
|
||||
.set(animationFnExpr.callFn([
|
||||
o.THIS_EXPR, renderNode, oldRenderValue.equals(unitializedValue)
|
||||
.conditional(emptyStateValue, oldRenderValue),
|
||||
renderValue.equals(unitializedValue).conditional(emptyStateValue, renderValue)
|
||||
]))
|
||||
.toDeclStmt());
|
||||
|
||||
detachStmts.push(animationTransitionVar
|
||||
.set(animationFnExpr.callFn(
|
||||
[o.THIS_EXPR, renderNode, oldRenderValue, emptyStateValue]))
|
||||
updateStmts.push(animationTransitionVar
|
||||
.set(animationFnExpr.callFn([
|
||||
o.THIS_EXPR, renderNode,
|
||||
bindingField.expression.equals(unitializedValue)
|
||||
.conditional(emptyStateValue, bindingField.expression),
|
||||
evalResult.currValExpr.equals(unitializedValue)
|
||||
.conditional(emptyStateValue, evalResult.currValExpr)
|
||||
]))
|
||||
.toDeclStmt());
|
||||
|
||||
detachStmts.push(
|
||||
animationTransitionVar
|
||||
.set(animationFnExpr.callFn(
|
||||
[o.THIS_EXPR, renderNode, bindingField.expression, emptyStateValue]))
|
||||
.toDeclStmt());
|
||||
|
||||
eventListeners.forEach(listener => {
|
||||
if (listener.isAnimation && listener.eventName === animationName) {
|
||||
let animationStmt = listener.listenToAnimation(animationTransitionVar);
|
||||
@ -203,43 +110,11 @@ function bindAndWriteToRenderer(
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
bind(
|
||||
view, currValExpr, fieldExpr, boundProp.value, context, updateStmts, compileMethod,
|
||||
view.bindings.length);
|
||||
compileMethod.addStmts(createCheckBindingStmt(
|
||||
evalResult, bindingField.expression, DetectChangesVars.throwOnChange, updateStmts));
|
||||
});
|
||||
}
|
||||
|
||||
function sanitizedValue(
|
||||
boundProp: BoundElementPropertyAst, renderValue: o.Expression): o.Expression {
|
||||
let enumValue: string;
|
||||
switch (boundProp.securityContext) {
|
||||
case SecurityContext.NONE:
|
||||
return renderValue; // No sanitization needed.
|
||||
case SecurityContext.HTML:
|
||||
enumValue = 'HTML';
|
||||
break;
|
||||
case SecurityContext.STYLE:
|
||||
enumValue = 'STYLE';
|
||||
break;
|
||||
case SecurityContext.SCRIPT:
|
||||
enumValue = 'SCRIPT';
|
||||
break;
|
||||
case SecurityContext.URL:
|
||||
enumValue = 'URL';
|
||||
break;
|
||||
case SecurityContext.RESOURCE_URL:
|
||||
enumValue = 'RESOURCE_URL';
|
||||
break;
|
||||
default:
|
||||
throw new Error(`internal error, unexpected SecurityContext ${boundProp.securityContext}.`);
|
||||
}
|
||||
let ctx = ViewProperties.viewUtils.prop('sanitizer');
|
||||
let args =
|
||||
[o.importExpr(resolveIdentifier(Identifiers.SecurityContext)).prop(enumValue), renderValue];
|
||||
return ctx.callMethod('sanitize', args);
|
||||
}
|
||||
|
||||
export function bindRenderInputs(
|
||||
boundProps: BoundElementPropertyAst[], compileElement: CompileElement,
|
||||
eventListeners: CompileEventListener[]): void {
|
||||
@ -248,43 +123,72 @@ export function bindRenderInputs(
|
||||
}
|
||||
|
||||
export function bindDirectiveHostProps(
|
||||
directiveAst: DirectiveAst, directiveInstance: o.Expression, compileElement: CompileElement,
|
||||
eventListeners: CompileEventListener[]): void {
|
||||
directiveAst: DirectiveAst, directiveWrapperInstance: o.Expression,
|
||||
compileElement: CompileElement, eventListeners: CompileEventListener[], elementName: string,
|
||||
schemaRegistry: ElementSchemaRegistry): void {
|
||||
// host properties are change detected by the DirectiveWrappers,
|
||||
// except for the animation properties as they need close integration with animation events
|
||||
// and DirectiveWrappers don't support
|
||||
// event listeners right now.
|
||||
bindAndWriteToRenderer(
|
||||
directiveAst.hostProperties, directiveInstance, compileElement, true, eventListeners);
|
||||
directiveAst.hostProperties.filter(boundProp => boundProp.isAnimation),
|
||||
directiveWrapperInstance.prop('context'), compileElement, true, eventListeners);
|
||||
|
||||
|
||||
const methodArgs: o.Expression[] =
|
||||
[o.THIS_EXPR, compileElement.renderNode, DetectChangesVars.throwOnChange];
|
||||
// We need to provide the SecurityContext for properties that could need sanitization.
|
||||
directiveAst.hostProperties.filter(boundProp => boundProp.needsRuntimeSecurityContext)
|
||||
.forEach((boundProp) => {
|
||||
let ctx: SecurityContext;
|
||||
switch (boundProp.type) {
|
||||
case PropertyBindingType.Property:
|
||||
ctx = schemaRegistry.securityContext(elementName, boundProp.name, false);
|
||||
break;
|
||||
case PropertyBindingType.Attribute:
|
||||
ctx = schemaRegistry.securityContext(elementName, boundProp.name, true);
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Illegal state: Only property / attribute bindings can have an unknown security context! Binding ${boundProp.name}`);
|
||||
}
|
||||
methodArgs.push(createEnumExpression(Identifiers.SecurityContext, ctx));
|
||||
});
|
||||
compileElement.view.detectChangesRenderPropertiesMethod.addStmt(
|
||||
directiveWrapperInstance.callMethod('detectChangesInHostProps', methodArgs).toStmt());
|
||||
}
|
||||
|
||||
export function bindDirectiveInputs(
|
||||
directiveAst: DirectiveAst, directiveWrapperInstance: o.Expression,
|
||||
directiveAst: DirectiveAst, directiveWrapperInstance: o.Expression, dirIndex: number,
|
||||
compileElement: CompileElement) {
|
||||
var view = compileElement.view;
|
||||
var detectChangesInInputsMethod = view.detectChangesInInputsMethod;
|
||||
detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst);
|
||||
|
||||
directiveAst.inputs.forEach((input) => {
|
||||
var bindingIndex = view.bindings.length;
|
||||
view.bindings.push(new CompileBinding(compileElement, input));
|
||||
directiveAst.inputs.forEach((input, inputIdx) => {
|
||||
// Note: We can't use `fields.length` here, as we are not adding a field!
|
||||
const bindingId = `${compileElement.nodeIndex}_${dirIndex}_${inputIdx}`;
|
||||
detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, input);
|
||||
var currValExpr = createCurrValueExpr(bindingIndex);
|
||||
const evalResult = evalCdAst(
|
||||
view, currValExpr, input.value, view.componentContext, detectChangesInInputsMethod,
|
||||
bindingIndex);
|
||||
const evalResult =
|
||||
convertPropertyBinding(view, view, view.componentContext, input.value, bindingId);
|
||||
if (!evalResult) {
|
||||
return;
|
||||
}
|
||||
detectChangesInInputsMethod.addStmt(directiveWrapperInstance
|
||||
.callMethod(
|
||||
`check_${input.directiveName}`,
|
||||
[
|
||||
currValExpr, DetectChangesVars.throwOnChange,
|
||||
evalResult.forceUpdate || o.literal(false)
|
||||
])
|
||||
.toStmt());
|
||||
detectChangesInInputsMethod.addStmts(evalResult.stmts);
|
||||
detectChangesInInputsMethod.addStmt(
|
||||
directiveWrapperInstance
|
||||
.callMethod(
|
||||
`check_${input.directiveName}`,
|
||||
[
|
||||
evalResult.currValExpr, DetectChangesVars.throwOnChange,
|
||||
evalResult.forceUpdate || o.literal(false)
|
||||
])
|
||||
.toStmt());
|
||||
});
|
||||
var isOnPushComp = directiveAst.directive.isComponent &&
|
||||
!isDefaultChangeDetectionStrategy(directiveAst.directive.changeDetection);
|
||||
let directiveDetectChangesExpr = directiveWrapperInstance.callMethod(
|
||||
'detectChangesInternal',
|
||||
'detectChangesInInputProps',
|
||||
[o.THIS_EXPR, compileElement.renderNode, DetectChangesVars.throwOnChange]);
|
||||
const directiveDetectChangesStmt = isOnPushComp ?
|
||||
new o.IfStmt(directiveDetectChangesExpr, [compileElement.appElement.prop('componentView')
|
||||
@ -293,10 +197,3 @@ export function bindDirectiveInputs(
|
||||
directiveDetectChangesExpr.toStmt();
|
||||
detectChangesInInputsMethod.addStmt(directiveDetectChangesStmt);
|
||||
}
|
||||
|
||||
function logBindingUpdateStmt(
|
||||
renderNode: o.Expression, propName: string, value: o.Expression): o.Statement {
|
||||
return o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfo))
|
||||
.callFn([o.THIS_EXPR.prop('renderer'), renderNode, o.literal(propName), value])
|
||||
.toStmt();
|
||||
}
|
||||
|
@ -8,10 +8,10 @@
|
||||
|
||||
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata';
|
||||
import {createDiTokenExpression} from '../compiler_util/identifier_util';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, resolveIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {createDiTokenExpression} from '../util';
|
||||
|
||||
import {CompileView} from './compile_view';
|
||||
|
||||
@ -91,16 +91,3 @@ export function createFlatArray(expressions: o.Expression[]): o.Expression {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createPureProxy(
|
||||
fn: o.Expression, argCount: number, pureProxyProp: o.ReadPropExpr, view: CompileView) {
|
||||
view.fields.push(new o.ClassField(pureProxyProp.name, null));
|
||||
var pureProxyId =
|
||||
argCount < Identifiers.pureProxies.length ? Identifiers.pureProxies[argCount] : null;
|
||||
if (!pureProxyId) {
|
||||
throw new Error(`Unsupported number of argument for pure functions: ${argCount}`);
|
||||
}
|
||||
view.createMethod.addStmt(o.THIS_EXPR.prop(pureProxyProp.name)
|
||||
.set(o.importExpr(resolveIdentifier(pureProxyId)).callFn([fn]))
|
||||
.toStmt());
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
|
||||
|
||||
import {CompileElement} from './compile_element';
|
||||
@ -14,8 +15,9 @@ import {CompileEventListener, bindDirectiveOutputs, bindRenderOutputs, collectEv
|
||||
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
|
||||
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
|
||||
|
||||
export function bindView(view: CompileView, parsedTemplate: TemplateAst[]): void {
|
||||
var visitor = new ViewBinderVisitor(view);
|
||||
export function bindView(
|
||||
view: CompileView, parsedTemplate: TemplateAst[], schemaRegistry: ElementSchemaRegistry): void {
|
||||
var visitor = new ViewBinderVisitor(view, schemaRegistry);
|
||||
templateVisitAll(visitor, parsedTemplate);
|
||||
view.pipes.forEach(
|
||||
(pipe) => { bindPipeDestroyLifecycleCallbacks(pipe.meta, pipe.instance, pipe.view); });
|
||||
@ -24,7 +26,7 @@ export function bindView(view: CompileView, parsedTemplate: TemplateAst[]): void
|
||||
class ViewBinderVisitor implements TemplateAstVisitor {
|
||||
private _nodeIndex: number = 0;
|
||||
|
||||
constructor(public view: CompileView) {}
|
||||
constructor(public view: CompileView, private _schemaRegistry: ElementSchemaRegistry) {}
|
||||
|
||||
visitBoundText(ast: BoundTextAst, parent: CompileElement): any {
|
||||
var node = this.view.nodes[this._nodeIndex++];
|
||||
@ -46,13 +48,15 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
||||
});
|
||||
bindRenderInputs(ast.inputs, compileElement, eventListeners);
|
||||
bindRenderOutputs(eventListeners);
|
||||
ast.directives.forEach((directiveAst) => {
|
||||
ast.directives.forEach((directiveAst, dirIndex) => {
|
||||
var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
|
||||
var directiveWrapperInstance =
|
||||
compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference);
|
||||
bindDirectiveInputs(directiveAst, directiveWrapperInstance, compileElement);
|
||||
bindDirectiveInputs(directiveAst, directiveWrapperInstance, dirIndex, compileElement);
|
||||
|
||||
bindDirectiveHostProps(directiveAst, directiveInstance, compileElement, eventListeners);
|
||||
bindDirectiveHostProps(
|
||||
directiveAst, directiveWrapperInstance, compileElement, eventListeners, ast.name,
|
||||
this._schemaRegistry);
|
||||
bindDirectiveOutputs(directiveAst, directiveInstance, eventListeners);
|
||||
});
|
||||
templateVisitAll(this, ast.children, compileElement);
|
||||
@ -75,11 +79,11 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
||||
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any {
|
||||
var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
||||
var eventListeners = collectEventListeners(ast.outputs, ast.directives, compileElement);
|
||||
ast.directives.forEach((directiveAst) => {
|
||||
ast.directives.forEach((directiveAst, dirIndex) => {
|
||||
var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
|
||||
var directiveWrapperInstance =
|
||||
compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference);
|
||||
bindDirectiveInputs(directiveAst, directiveWrapperInstance, compileElement);
|
||||
bindDirectiveInputs(directiveAst, directiveWrapperInstance, dirIndex, compileElement);
|
||||
|
||||
bindDirectiveOutputs(directiveAst, directiveInstance, eventListeners);
|
||||
bindDirectiveAfterContentLifecycleCallbacks(
|
||||
@ -91,7 +95,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
||||
var providerInstance = compileElement.instances.get(providerAst.token.reference);
|
||||
bindInjectableDestroyLifecycleCallbacks(providerAst, providerInstance, compileElement);
|
||||
});
|
||||
bindView(compileElement.embeddedView, ast.children);
|
||||
bindView(compileElement.embeddedView, ast.children, this._schemaRegistry);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -9,13 +9,14 @@
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {createSharedBindingVariablesIfNeeded} from '../compiler_util/expression_converter';
|
||||
import {createDiTokenExpression, createInlineArray} from '../compiler_util/identifier_util';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, identifierToken, resolveIdentifier} from '../identifiers';
|
||||
import {createClassStmt} from '../output/class_builder';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ChangeDetectorStatus, ViewType, isDefaultChangeDetectionStrategy} from '../private_import_core';
|
||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
|
||||
import {createDiTokenExpression} from '../util';
|
||||
|
||||
import {CompileElement, CompileNode} from './compile_element';
|
||||
import {CompileView} from './compile_view';
|
||||
@ -157,19 +158,29 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
|
||||
|
||||
visitElement(ast: ElementAst, parent: CompileElement): any {
|
||||
var nodeIndex = this.view.nodes.length;
|
||||
var createRenderNodeExpr: o.InvokeMethodExpr;
|
||||
var createRenderNodeExpr: o.Expression;
|
||||
var debugContextExpr = this.view.createMethod.resetDebugInfoExpr(nodeIndex, ast);
|
||||
if (nodeIndex === 0 && this.view.viewType === ViewType.HOST) {
|
||||
createRenderNodeExpr = o.THIS_EXPR.callMethod(
|
||||
'selectOrCreateHostElement', [o.literal(ast.name), rootSelectorVar, debugContextExpr]);
|
||||
var directives = ast.directives.map(directiveAst => directiveAst.directive);
|
||||
var component = directives.find(directive => directive.isComponent);
|
||||
if (ast.name === NG_CONTAINER_TAG) {
|
||||
createRenderNodeExpr = ViewProperties.renderer.callMethod(
|
||||
'createTemplateAnchor', [this._getParentRenderNode(parent), debugContextExpr]);
|
||||
} else {
|
||||
if (ast.name === NG_CONTAINER_TAG) {
|
||||
createRenderNodeExpr = ViewProperties.renderer.callMethod(
|
||||
'createTemplateAnchor', [this._getParentRenderNode(parent), debugContextExpr]);
|
||||
const htmlAttrs = _readHtmlAttrs(ast.attrs);
|
||||
const attrNameAndValues = createInlineArray(
|
||||
_mergeHtmlAndDirectiveAttrs(htmlAttrs, directives).map(v => o.literal(v)));
|
||||
if (nodeIndex === 0 && this.view.viewType === ViewType.HOST) {
|
||||
createRenderNodeExpr =
|
||||
o.importExpr(resolveIdentifier(Identifiers.selectOrCreateRenderHostElement)).callFn([
|
||||
ViewProperties.renderer, o.literal(ast.name), attrNameAndValues, rootSelectorVar,
|
||||
debugContextExpr
|
||||
]);
|
||||
} else {
|
||||
createRenderNodeExpr = ViewProperties.renderer.callMethod(
|
||||
'createElement',
|
||||
[this._getParentRenderNode(parent), o.literal(ast.name), debugContextExpr]);
|
||||
createRenderNodeExpr =
|
||||
o.importExpr(resolveIdentifier(Identifiers.createRenderElement)).callFn([
|
||||
ViewProperties.renderer, this._getParentRenderNode(parent), o.literal(ast.name),
|
||||
attrNameAndValues, debugContextExpr
|
||||
]);
|
||||
}
|
||||
}
|
||||
var fieldName = `_el_${nodeIndex}`;
|
||||
@ -179,22 +190,6 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
|
||||
|
||||
var renderNode = o.THIS_EXPR.prop(fieldName);
|
||||
|
||||
var directives = ast.directives.map(directiveAst => directiveAst.directive);
|
||||
var component = directives.find(directive => directive.isComponent);
|
||||
var htmlAttrs = _readHtmlAttrs(ast.attrs);
|
||||
var attrNameAndValues = _mergeHtmlAndDirectiveAttrs(htmlAttrs, directives);
|
||||
for (var i = 0; i < attrNameAndValues.length; i++) {
|
||||
const attrName = attrNameAndValues[i][0];
|
||||
if (ast.name !== NG_CONTAINER_TAG) {
|
||||
// <ng-container> are not rendered in the DOM
|
||||
const attrValue = attrNameAndValues[i][1];
|
||||
this.view.createMethod.addStmt(
|
||||
ViewProperties.renderer
|
||||
.callMethod(
|
||||
'setElementAttribute', [renderNode, o.literal(attrName), o.literal(attrValue)])
|
||||
.toStmt());
|
||||
}
|
||||
}
|
||||
var compileElement = new CompileElement(
|
||||
parent, this.view, nodeIndex, renderNode, ast, component, directives, ast.providers,
|
||||
ast.hasViewContainer, false, ast.references, this.targetDependencies);
|
||||
@ -329,18 +324,22 @@ function _isNgContainer(node: CompileNode, view: CompileView): boolean {
|
||||
|
||||
|
||||
function _mergeHtmlAndDirectiveAttrs(
|
||||
declaredHtmlAttrs: {[key: string]: string},
|
||||
directives: CompileDirectiveMetadata[]): string[][] {
|
||||
var result: {[key: string]: string} = {};
|
||||
Object.keys(declaredHtmlAttrs).forEach(key => { result[key] = declaredHtmlAttrs[key]; });
|
||||
declaredHtmlAttrs: {[key: string]: string}, directives: CompileDirectiveMetadata[]): string[] {
|
||||
const mapResult: {[key: string]: string} = {};
|
||||
Object.keys(declaredHtmlAttrs).forEach(key => { mapResult[key] = declaredHtmlAttrs[key]; });
|
||||
directives.forEach(directiveMeta => {
|
||||
Object.keys(directiveMeta.hostAttributes).forEach(name => {
|
||||
const value = directiveMeta.hostAttributes[name];
|
||||
var prevValue = result[name];
|
||||
result[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value;
|
||||
const prevValue = mapResult[name];
|
||||
mapResult[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value;
|
||||
});
|
||||
});
|
||||
return mapToKeyValueArray(result);
|
||||
const arrResult: string[] = [];
|
||||
// Note: We need to sort to get a defined output order
|
||||
// for tests and for caching generated artifacts...
|
||||
Object.keys(mapResult).sort().forEach(
|
||||
(attrName) => { arrResult.push(attrName, mapResult[attrName]); });
|
||||
return arrResult;
|
||||
}
|
||||
|
||||
function _readHtmlAttrs(attrs: AttrAst[]): {[key: string]: string} {
|
||||
@ -357,15 +356,6 @@ function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: s
|
||||
}
|
||||
}
|
||||
|
||||
function mapToKeyValueArray(data: {[key: string]: string}): string[][] {
|
||||
var entryArray: string[][] = [];
|
||||
Object.keys(data).forEach(name => { entryArray.push([name, data[name]]); });
|
||||
// We need to sort to get a defined output order
|
||||
// for tests and for caching generated artifacts...
|
||||
ListWrapper.sort(entryArray);
|
||||
return entryArray;
|
||||
}
|
||||
|
||||
function createViewTopLevelStmts(view: CompileView, targetStatements: o.Statement[]) {
|
||||
var nodeDebugInfosVar: o.Expression = o.NULL_EXPR;
|
||||
if (view.genConfig.genDebugInfo) {
|
||||
@ -444,9 +434,6 @@ function createViewClass(
|
||||
if (view.genConfig.genDebugInfo) {
|
||||
superConstructorArgs.push(nodeDebugInfosVar);
|
||||
}
|
||||
var viewConstructor = new o.ClassMethod(
|
||||
null, viewConstructorArgs, [o.SUPER_EXPR.callFn(superConstructorArgs).toStmt()]);
|
||||
|
||||
var viewMethods = [
|
||||
new o.ClassMethod(
|
||||
'createInternal', [new o.FnParam(rootSelectorVar.name, o.STRING_TYPE)],
|
||||
@ -467,12 +454,16 @@ function createViewClass(
|
||||
new o.ClassMethod('dirtyParentQueriesInternal', [], view.dirtyParentQueriesMethod.finish()),
|
||||
new o.ClassMethod('destroyInternal', [], view.destroyMethod.finish()),
|
||||
new o.ClassMethod('detachInternal', [], view.detachMethod.finish())
|
||||
].concat(view.eventHandlerMethods);
|
||||
].filter((method) => method.body.length > 0);
|
||||
var superClass = view.genConfig.genDebugInfo ? Identifiers.DebugAppView : Identifiers.AppView;
|
||||
var viewClass = new o.ClassStmt(
|
||||
view.className, o.importExpr(resolveIdentifier(superClass), [getContextType(view)]),
|
||||
view.fields, view.getters, viewConstructor,
|
||||
viewMethods.filter((method) => method.body.length > 0));
|
||||
|
||||
var viewClass = createClassStmt({
|
||||
name: view.className,
|
||||
parent: o.importExpr(resolveIdentifier(superClass), [getContextType(view)]),
|
||||
parentArgs: superConstructorArgs,
|
||||
ctorParams: viewConstructorArgs,
|
||||
builders: [{methods: viewMethods}, view]
|
||||
});
|
||||
return viewClass;
|
||||
}
|
||||
|
||||
@ -567,8 +558,8 @@ function generateDetectChangesMethod(view: CompileView): o.Statement[] {
|
||||
view.updateViewQueriesMethod.isEmpty() && view.afterViewLifecycleCallbacksMethod.isEmpty()) {
|
||||
return stmts;
|
||||
}
|
||||
ListWrapper.addAll(stmts, view.animationBindingsMethod.finish());
|
||||
ListWrapper.addAll(stmts, view.detectChangesInInputsMethod.finish());
|
||||
stmts.push(...view.animationBindingsMethod.finish());
|
||||
stmts.push(...view.detectChangesInInputsMethod.finish());
|
||||
stmts.push(
|
||||
o.THIS_EXPR.callMethod('detectContentChildrenChanges', [DetectChangesVars.throwOnChange])
|
||||
.toStmt());
|
||||
@ -577,7 +568,7 @@ function generateDetectChangesMethod(view: CompileView): o.Statement[] {
|
||||
if (afterContentStmts.length > 0) {
|
||||
stmts.push(new o.IfStmt(o.not(DetectChangesVars.throwOnChange), afterContentStmts));
|
||||
}
|
||||
ListWrapper.addAll(stmts, view.detectChangesRenderPropertiesMethod.finish());
|
||||
stmts.push(...view.detectChangesRenderPropertiesMethod.finish());
|
||||
stmts.push(o.THIS_EXPR.callMethod('detectViewChildrenChanges', [DetectChangesVars.throwOnChange])
|
||||
.toStmt());
|
||||
var afterViewStmts =
|
||||
@ -596,12 +587,7 @@ function generateDetectChangesMethod(view: CompileView): o.Statement[] {
|
||||
DetectChangesVars.changes.set(o.NULL_EXPR)
|
||||
.toDeclStmt(new o.MapType(o.importType(resolveIdentifier(Identifiers.SimpleChange)))));
|
||||
}
|
||||
if (readVars.has(DetectChangesVars.valUnwrapper.name)) {
|
||||
varStmts.push(
|
||||
DetectChangesVars.valUnwrapper
|
||||
.set(o.importExpr(resolveIdentifier(Identifiers.ValueUnwrapper)).instantiate([]))
|
||||
.toDeclStmt(null, [o.StmtModifier.Final]));
|
||||
}
|
||||
varStmts.push(...createSharedBindingVariablesIfNeeded(stmts));
|
||||
return varStmts.concat(stmts);
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import {AnimationEntryCompileResult} from '../animation/animation_compiler';
|
||||
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata';
|
||||
import {CompilerConfig} from '../config';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||
import {TemplateAst} from '../template_parser/template_ast';
|
||||
|
||||
import {CompileElement} from './compile_element';
|
||||
@ -31,7 +32,7 @@ export class ViewCompileResult {
|
||||
|
||||
@Injectable()
|
||||
export class ViewCompiler {
|
||||
constructor(private _genConfig: CompilerConfig) {}
|
||||
constructor(private _genConfig: CompilerConfig, private _schemaRegistry: ElementSchemaRegistry) {}
|
||||
|
||||
compileComponent(
|
||||
component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression,
|
||||
@ -47,7 +48,7 @@ export class ViewCompiler {
|
||||
buildView(view, template, dependencies);
|
||||
// Need to separate binding from creation to be able to refer to
|
||||
// variables that have been declared after usage.
|
||||
bindView(view, template);
|
||||
bindView(view, template, this._schemaRegistry);
|
||||
finishView(view, statements);
|
||||
|
||||
return new ViewCompileResult(statements, view.viewFactory.name, dependencies);
|
||||
|
@ -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 {createInlineArray} from '../../src/compiler_util/identifier_util';
|
||||
import {Identifiers, resolveIdentifier} from '../../src/identifiers';
|
||||
import * as o from '../../src/output/output_ast';
|
||||
|
||||
export function main() {
|
||||
describe('createInlineArray', () => {
|
||||
|
||||
function check(argCount: number, expectedIdentifier: any) {
|
||||
const args = createArgs(argCount);
|
||||
expect(createInlineArray(args))
|
||||
.toEqual(o.importExpr(resolveIdentifier(expectedIdentifier)).instantiate([
|
||||
<o.Expression>o.literal(argCount)
|
||||
].concat(args)));
|
||||
}
|
||||
|
||||
function createArgs(count: number): o.Expression[] {
|
||||
const result: o.Expression[] = [];
|
||||
for (var i = 0; i < count; i++) {
|
||||
result.push(o.NULL_EXPR);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
it('should work for arrays of length 0', () => {
|
||||
expect(createInlineArray([
|
||||
])).toEqual(o.importExpr(resolveIdentifier(Identifiers.EMPTY_INLINE_ARRAY)));
|
||||
});
|
||||
|
||||
it('should work for arrays of length 1 - 2', () => {
|
||||
check(1, Identifiers.inlineArrays[0]);
|
||||
check(2, Identifiers.inlineArrays[1]);
|
||||
});
|
||||
|
||||
it('should work for arrays of length 3 - 4', () => {
|
||||
for (var i = 3; i <= 4; i++) {
|
||||
check(i, Identifiers.inlineArrays[2]);
|
||||
}
|
||||
});
|
||||
|
||||
it('should work for arrays of length 5 - 8', () => {
|
||||
for (var i = 5; i <= 8; i++) {
|
||||
check(i, Identifiers.inlineArrays[3]);
|
||||
}
|
||||
});
|
||||
|
||||
it('should work for arrays of length 9 - 16', () => {
|
||||
for (var i = 9; i <= 16; i++) {
|
||||
check(i, Identifiers.inlineArrays[4]);
|
||||
}
|
||||
});
|
||||
|
||||
it('should work for arrays of length > 16',
|
||||
() => { check(17, Identifiers.InlineArrayDynamic); });
|
||||
});
|
||||
}
|
@ -74,7 +74,10 @@ export function main() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw Error(`Expected an error containing "${message}" to be reported`);
|
||||
const errMsgs = ast.errors.map(err => err.message).join('\n');
|
||||
throw Error(
|
||||
`Expected an error containing "${message}" to be reported, but got the errors:\n` +
|
||||
errMsgs);
|
||||
}
|
||||
|
||||
function expectActionError(text: string, message: string) {
|
||||
@ -504,16 +507,10 @@ export function main() {
|
||||
validate(p);
|
||||
});
|
||||
|
||||
it('should parse a constant', () => {
|
||||
var p = parseSimpleBinding('[1, 2]');
|
||||
expect(unparse(p)).toEqual('[1, 2]');
|
||||
validate(p);
|
||||
});
|
||||
|
||||
it('should report when the given expression is not just a field name', () => {
|
||||
it('should report when encountering pipes', () => {
|
||||
expectError(
|
||||
validate(parseSimpleBinding('name + 1')),
|
||||
'Host binding expression can only contain field access and constants');
|
||||
validate(parseSimpleBinding('a | somePipe')),
|
||||
'Host binding expression cannot contain pipes');
|
||||
});
|
||||
|
||||
it('should report when encountering interpolation', () => {
|
||||
@ -521,6 +518,10 @@ export function main() {
|
||||
validate(parseSimpleBinding('{{exp}}')),
|
||||
'Got interpolation ({{}}) where expression was expected');
|
||||
});
|
||||
|
||||
it('should report when encountering field write', () => {
|
||||
expectError(validate(parseSimpleBinding('a = b')), 'Bindings cannot contain assignments');
|
||||
});
|
||||
});
|
||||
|
||||
describe('wrapLiteralPrimitive', () => {
|
||||
|
@ -11,7 +11,7 @@
|
||||
import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter';
|
||||
|
||||
import {print} from '../../src/facade/lang';
|
||||
import {assetUrl} from '../../src/util';
|
||||
import {assetUrl} from '../../src/identifiers';
|
||||
|
||||
function unimplemented(): any {
|
||||
throw new Error('unimplemented');
|
||||
|
@ -10,7 +10,7 @@
|
||||
import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter';
|
||||
|
||||
import {print} from '../../src/facade/lang';
|
||||
import {assetUrl} from '../../src/util';
|
||||
import {assetUrl} from '../../src/identifiers';
|
||||
|
||||
import {SimpleJsImportGenerator, codegenExportsVars, codegenStmts} from './output_emitter_util';
|
||||
|
||||
|
@ -7,9 +7,9 @@
|
||||
*/
|
||||
|
||||
import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata';
|
||||
import {assetUrl} from '@angular/compiler/src/identifiers';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
import {ImportGenerator} from '@angular/compiler/src/output/path_util';
|
||||
import {assetUrl} from '@angular/compiler/src/util';
|
||||
import {EventEmitter} from '@angular/core';
|
||||
import {BaseError} from '@angular/core/src/facade/errors';
|
||||
import {ViewType} from '@angular/core/src/linker/view_type';
|
||||
|
@ -147,12 +147,12 @@ If 'onAnything' is a directive input, make sure the directive is imported by the
|
||||
});
|
||||
|
||||
it('should return security contexts for elements', () => {
|
||||
expect(registry.securityContext('iframe', 'srcdoc')).toBe(SecurityContext.HTML);
|
||||
expect(registry.securityContext('p', 'innerHTML')).toBe(SecurityContext.HTML);
|
||||
expect(registry.securityContext('a', 'href')).toBe(SecurityContext.URL);
|
||||
expect(registry.securityContext('a', 'style')).toBe(SecurityContext.STYLE);
|
||||
expect(registry.securityContext('ins', 'cite')).toBe(SecurityContext.URL);
|
||||
expect(registry.securityContext('base', 'href')).toBe(SecurityContext.RESOURCE_URL);
|
||||
expect(registry.securityContext('iframe', 'srcdoc', false)).toBe(SecurityContext.HTML);
|
||||
expect(registry.securityContext('p', 'innerHTML', false)).toBe(SecurityContext.HTML);
|
||||
expect(registry.securityContext('a', 'href', false)).toBe(SecurityContext.URL);
|
||||
expect(registry.securityContext('a', 'style', false)).toBe(SecurityContext.STYLE);
|
||||
expect(registry.securityContext('ins', 'cite', false)).toBe(SecurityContext.URL);
|
||||
expect(registry.securityContext('base', 'href', false)).toBe(SecurityContext.RESOURCE_URL);
|
||||
});
|
||||
|
||||
it('should detect properties on namespaced elements', () => {
|
||||
@ -162,9 +162,14 @@ If 'onAnything' is a directive input, make sure the directive is imported by the
|
||||
});
|
||||
|
||||
it('should check security contexts case insensitive', () => {
|
||||
expect(registry.securityContext('p', 'iNnErHtMl')).toBe(SecurityContext.HTML);
|
||||
expect(registry.securityContext('p', 'formaction')).toBe(SecurityContext.URL);
|
||||
expect(registry.securityContext('p', 'formAction')).toBe(SecurityContext.URL);
|
||||
expect(registry.securityContext('p', 'iNnErHtMl', false)).toBe(SecurityContext.HTML);
|
||||
expect(registry.securityContext('p', 'formaction', false)).toBe(SecurityContext.URL);
|
||||
expect(registry.securityContext('p', 'formAction', false)).toBe(SecurityContext.URL);
|
||||
});
|
||||
|
||||
it('should check security contexts for attributes', () => {
|
||||
expect(registry.securityContext('p', 'innerHtml', true)).toBe(SecurityContext.HTML);
|
||||
expect(registry.securityContext('p', 'formaction', true)).toBe(SecurityContext.URL);
|
||||
});
|
||||
|
||||
describe('Angular custom elements', () => {
|
||||
|
@ -58,6 +58,12 @@ export function main() {
|
||||
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
|
||||
});
|
||||
|
||||
it('should not throw for class name "constructor"', () => {
|
||||
expect(matcher.match(CssSelector.parse('.constructor')[0], selectableCollector))
|
||||
.toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
});
|
||||
|
||||
it('should select by attr name case sensitive independent of the value', () => {
|
||||
matcher.addSelectables(s1 = CssSelector.parse('[someAttr]'), 1);
|
||||
matcher.addSelectables(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2);
|
||||
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @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 {SecurityContext} from '@angular/core';
|
||||
import {inject} from '@angular/core/testing';
|
||||
|
||||
import {ElementSchemaRegistry} from '../../src/schema/element_schema_registry';
|
||||
import {calcPossibleSecurityContexts} from '../../src/template_parser/binding_parser';
|
||||
|
||||
export function main() {
|
||||
describe('BindingParser', () => {
|
||||
let registry: ElementSchemaRegistry;
|
||||
|
||||
beforeEach(inject(
|
||||
[ElementSchemaRegistry], (_registry: ElementSchemaRegistry) => { registry = _registry; }));
|
||||
|
||||
describe('possibleSecurityContexts', () => {
|
||||
function hrefSecurityContexts(selector: string) {
|
||||
return calcPossibleSecurityContexts(registry, selector, 'href', false);
|
||||
}
|
||||
|
||||
it('should return a single security context if the selector as an element name',
|
||||
() => { expect(hrefSecurityContexts('a')).toEqual([SecurityContext.URL]); });
|
||||
|
||||
it('should return the possible security contexts if the selector has no element name', () => {
|
||||
expect(hrefSecurityContexts('[myDir]')).toEqual([
|
||||
SecurityContext.NONE, SecurityContext.URL, SecurityContext.RESOURCE_URL
|
||||
]);
|
||||
});
|
||||
|
||||
it('should exclude possible elements via :not', () => {
|
||||
expect(hrefSecurityContexts('[myDir]:not(link):not(base)')).toEqual([
|
||||
SecurityContext.NONE, SecurityContext.URL
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not exclude possible narrowed elements via :not', () => {
|
||||
expect(hrefSecurityContexts('[myDir]:not(link.someClass):not(base.someClass)')).toEqual([
|
||||
SecurityContext.NONE, SecurityContext.URL, SecurityContext.RESOURCE_URL
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return SecurityContext.NONE if there are no possible elements',
|
||||
() => { expect(hrefSecurityContexts('img:not(img)')).toEqual([SecurityContext.NONE]); });
|
||||
|
||||
it('should return the union of the possible security contexts if multiple selectors are specified',
|
||||
() => {
|
||||
expect(calcPossibleSecurityContexts(registry, 'a,link', 'href', false)).toEqual([
|
||||
SecurityContext.URL, SecurityContext.RESOURCE_URL
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -124,7 +124,7 @@ export function main() {
|
||||
new class extends NullVisitor{
|
||||
visitElementProperty(ast: BoundElementPropertyAst, context: any): any{return ast;}
|
||||
},
|
||||
new BoundElementPropertyAst('foo', null, null, null, 'bar', null));
|
||||
new BoundElementPropertyAst('foo', null, null, false, null, 'bar', null));
|
||||
});
|
||||
|
||||
it('should visit AttrAst', () => {
|
||||
@ -171,7 +171,7 @@ export function main() {
|
||||
new ElementAst('foo', [], [], [], [], [], [], false, [], 0, null, null),
|
||||
new ReferenceAst('foo', null, null), new VariableAst('foo', 'bar', null),
|
||||
new BoundEventAst('foo', 'bar', 'goo', null, null),
|
||||
new BoundElementPropertyAst('foo', null, null, null, 'bar', null),
|
||||
new BoundElementPropertyAst('foo', null, null, false, null, 'bar', null),
|
||||
new AttrAst('foo', 'bar', null), new BoundTextAst(null, 0, null),
|
||||
new TextAst('foo', 0, null), new DirectiveAst(null, [], [], [], null),
|
||||
new BoundDirectivePropertyAst('foo', 'bar', null, null)
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {ResourceLoader} from '@angular/compiler';
|
||||
import {ListWrapper} from './facade/collection';
|
||||
import {isBlank, normalizeBlank} from './facade/lang';
|
||||
import {isBlank} from './facade/lang';
|
||||
|
||||
/**
|
||||
* A mock implementation of {@link ResourceLoader} that allows outgoing requests to be mocked
|
||||
@ -20,7 +20,7 @@ export class MockResourceLoader extends ResourceLoader {
|
||||
private _requests: _PendingRequest[] = [];
|
||||
|
||||
get(url: string): Promise<string> {
|
||||
var request = new _PendingRequest(url);
|
||||
const request = new _PendingRequest(url);
|
||||
this._requests.push(request);
|
||||
return request.getPromise();
|
||||
}
|
||||
@ -33,7 +33,7 @@ export class MockResourceLoader extends ResourceLoader {
|
||||
* The response given will be returned if the expectation matches.
|
||||
*/
|
||||
expect(url: string, response: string) {
|
||||
var expectation = new _Expectation(url, response);
|
||||
const expectation = new _Expectation(url, response);
|
||||
this._expectations.push(expectation);
|
||||
}
|
||||
|
||||
@ -90,7 +90,7 @@ export class MockResourceLoader extends ResourceLoader {
|
||||
|
||||
if (this._definitions.has(url)) {
|
||||
var response = this._definitions.get(url);
|
||||
request.complete(normalizeBlank(response));
|
||||
request.complete(response == null ? null : response);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,9 @@ export class MockSchemaRegistry implements ElementSchemaRegistry {
|
||||
return value === void 0 ? true : value;
|
||||
}
|
||||
|
||||
securityContext(tagName: string, property: string): SecurityContext {
|
||||
allKnownElementNames(): string[] { return Object.keys(this.existingElements); }
|
||||
|
||||
securityContext(selector: string, property: string, isAttribute: boolean): SecurityContext {
|
||||
return SecurityContext.NONE;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ListWrapper, StringMapWrapper} from '../facade/collection';
|
||||
import {StringMapWrapper} from '../facade/collection';
|
||||
import {isPresent} from '../facade/lang';
|
||||
|
||||
import {FILL_STYLE_FLAG} from './animation_constants';
|
||||
@ -57,7 +57,7 @@ export function balanceAnimationKeyframes(
|
||||
|
||||
// phase 2: normalize the final keyframe
|
||||
var finalKeyframe = keyframes[limit];
|
||||
ListWrapper.insert(finalKeyframe.styles.styles, 0, finalStateStyles);
|
||||
finalKeyframe.styles.styles.unshift(finalStateStyles);
|
||||
|
||||
var flatenedFinalKeyframeStyles = flattenStyles(finalKeyframe.styles.styles);
|
||||
var extraFinalKeyframeStyles: {[key: string]: string} = {};
|
||||
|
@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
import {Optional, Provider, SkipSelf} from '../../di';
|
||||
import {ListWrapper} from '../../facade/collection';
|
||||
import {getTypeNameForDebugging, isPresent} from '../../facade/lang';
|
||||
import {ChangeDetectorRef} from '../change_detector_ref';
|
||||
|
||||
@ -51,7 +50,7 @@ export class IterableDiffers {
|
||||
|
||||
static create(factories: IterableDifferFactory[], parent?: IterableDiffers): IterableDiffers {
|
||||
if (isPresent(parent)) {
|
||||
var copied = ListWrapper.clone(parent.factories);
|
||||
var copied = parent.factories.slice();
|
||||
factories = factories.concat(copied);
|
||||
return new IterableDiffers(factories);
|
||||
} else {
|
||||
|
@ -41,7 +41,7 @@ export class KeyValueDiffers {
|
||||
|
||||
static create(factories: KeyValueDifferFactory[], parent?: KeyValueDiffers): KeyValueDiffers {
|
||||
if (isPresent(parent)) {
|
||||
var copied = ListWrapper.clone(parent.factories);
|
||||
var copied = parent.factories.slice();
|
||||
factories = factories.concat(copied);
|
||||
return new KeyValueDiffers(factories);
|
||||
} else {
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {Injector} from '../di';
|
||||
import {ListWrapper, MapWrapper, Predicate} from '../facade/collection';
|
||||
import {MapWrapper, Predicate} from '../facade/collection';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {RenderDebugInfo} from '../render/api';
|
||||
|
||||
@ -92,8 +92,7 @@ export class DebugElement extends DebugNode {
|
||||
if (siblingIndex !== -1) {
|
||||
var previousChildren = this.childNodes.slice(0, siblingIndex + 1);
|
||||
var nextChildren = this.childNodes.slice(siblingIndex + 1);
|
||||
this.childNodes =
|
||||
ListWrapper.concat(ListWrapper.concat(previousChildren, newChildren), nextChildren);
|
||||
this.childNodes = previousChildren.concat(newChildren, nextChildren);
|
||||
for (var i = 0; i < newChildren.length; ++i) {
|
||||
var newChild = newChildren[i];
|
||||
if (isPresent(newChild.parent)) {
|
||||
|
@ -6,7 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {BaseError, WrappedError} from '../facade/errors';
|
||||
import {stringify} from '../facade/lang';
|
||||
import {Type} from '../type';
|
||||
@ -17,7 +16,7 @@ import {ReflectiveKey} from './reflective_key';
|
||||
function findFirstClosedCycle(keys: any[]): any[] {
|
||||
var res: any[] = [];
|
||||
for (var i = 0; i < keys.length; ++i) {
|
||||
if (ListWrapper.contains(res, keys[i])) {
|
||||
if (res.indexOf(keys[i]) > -1) {
|
||||
res.push(keys[i]);
|
||||
return res;
|
||||
}
|
||||
@ -28,8 +27,8 @@ function findFirstClosedCycle(keys: any[]): any[] {
|
||||
|
||||
function constructResolvingPath(keys: any[]): string {
|
||||
if (keys.length > 1) {
|
||||
var reversed = findFirstClosedCycle(ListWrapper.reversed(keys));
|
||||
var tokenStrs = reversed.map(k => stringify(k.token));
|
||||
const reversed = findFirstClosedCycle(keys.slice().reverse());
|
||||
const tokenStrs = reversed.map(k => stringify(k.token));
|
||||
return ' (' + tokenStrs.join(' -> ') + ')';
|
||||
}
|
||||
|
||||
@ -88,7 +87,7 @@ export class AbstractProviderError extends BaseError {
|
||||
export class NoProviderError extends AbstractProviderError {
|
||||
constructor(injector: ReflectiveInjector, key: ReflectiveKey) {
|
||||
super(injector, key, function(keys: any[]) {
|
||||
var first = stringify(ListWrapper.first(keys).token);
|
||||
const first = stringify(keys[0].token);
|
||||
return `No provider for ${first}!${constructResolvingPath(keys)}`;
|
||||
});
|
||||
}
|
||||
@ -167,7 +166,7 @@ export class InstantiationError extends WrappedError {
|
||||
}
|
||||
|
||||
get message(): string {
|
||||
var first = stringify(ListWrapper.first(this.keys).token);
|
||||
var first = stringify(this.keys[0].token);
|
||||
return `${this.originalError.message}: Error during instantiation of ${first}!${constructResolvingPath(this.keys)}.`;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {unimplemented} from '../facade/errors';
|
||||
import {Type} from '../type';
|
||||
|
||||
@ -17,8 +16,6 @@ import {AbstractProviderError, CyclicDependencyError, InstantiationError, NoProv
|
||||
import {ReflectiveKey} from './reflective_key';
|
||||
import {ReflectiveDependency, ResolvedReflectiveFactory, ResolvedReflectiveProvider, resolveReflectiveProviders} from './reflective_provider';
|
||||
|
||||
var __unused: Type<any>; // avoid unused import when Type union types are erased
|
||||
|
||||
// Threshold for the dynamic version
|
||||
const _MAX_CONSTRUCTION_COUNTER = 10;
|
||||
const UNDEFINED = new Object();
|
||||
@ -286,8 +283,7 @@ export class ReflectiveInjectorDynamicStrategy implements ReflectiveInjectorStra
|
||||
constructor(
|
||||
public protoStrategy: ReflectiveProtoInjectorDynamicStrategy,
|
||||
public injector: ReflectiveInjector_) {
|
||||
this.objs = new Array(protoStrategy.providers.length);
|
||||
ListWrapper.fill(this.objs, UNDEFINED);
|
||||
this.objs = new Array(protoStrategy.providers.length).fill(UNDEFINED);
|
||||
}
|
||||
|
||||
resetConstructionCounter(): void { this.injector._constructionCounter = 0; }
|
||||
@ -297,9 +293,9 @@ export class ReflectiveInjectorDynamicStrategy implements ReflectiveInjectorStra
|
||||
}
|
||||
|
||||
getObjByKeyId(keyId: number): any {
|
||||
var p = this.protoStrategy;
|
||||
const p = this.protoStrategy;
|
||||
|
||||
for (var i = 0; i < p.keyIds.length; i++) {
|
||||
for (let i = 0; i < p.keyIds.length; i++) {
|
||||
if (p.keyIds[i] === keyId) {
|
||||
if (this.objs[i] === UNDEFINED) {
|
||||
this.objs[i] = this.injector._new(p.providers[i]);
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ListWrapper, MapWrapper} from '../facade/collection';
|
||||
import {MapWrapper} from '../facade/collection';
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
import {reflector} from '../reflection/reflection';
|
||||
import {Type} from '../type';
|
||||
@ -170,7 +170,7 @@ export function mergeResolvedReflectiveProviders(
|
||||
var resolvedProvider: ResolvedReflectiveProvider;
|
||||
if (provider.multiProvider) {
|
||||
resolvedProvider = new ResolvedReflectiveProvider_(
|
||||
provider.key, ListWrapper.clone(provider.resolvedFactories), provider.multiProvider);
|
||||
provider.key, provider.resolvedFactories.slice(), provider.multiProvider);
|
||||
} else {
|
||||
resolvedProvider = provider;
|
||||
}
|
||||
|
@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
import {Injector} from '../di/injector';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {isPresent} from '../facade/lang';
|
||||
|
||||
import {ElementRef} from './element_ref';
|
||||
@ -69,8 +68,8 @@ export class AppElement {
|
||||
nestedViews = [];
|
||||
this.nestedViews = nestedViews;
|
||||
}
|
||||
ListWrapper.removeAt(nestedViews, previousIndex);
|
||||
ListWrapper.insert(nestedViews, currentIndex, view);
|
||||
nestedViews.splice(previousIndex, 1);
|
||||
nestedViews.splice(currentIndex, 0, view);
|
||||
var refRenderNode: any /** TODO #9100 */;
|
||||
if (currentIndex > 0) {
|
||||
var prevView = nestedViews[currentIndex - 1];
|
||||
@ -93,7 +92,7 @@ export class AppElement {
|
||||
nestedViews = [];
|
||||
this.nestedViews = nestedViews;
|
||||
}
|
||||
ListWrapper.insert(nestedViews, viewIndex, view);
|
||||
nestedViews.splice(viewIndex, 0, view);
|
||||
var refRenderNode: any /** TODO #9100 */;
|
||||
if (viewIndex > 0) {
|
||||
var prevView = nestedViews[viewIndex - 1];
|
||||
@ -108,7 +107,7 @@ export class AppElement {
|
||||
}
|
||||
|
||||
detachView(viewIndex: number): AppView<any> {
|
||||
var view = ListWrapper.removeAt(this.nestedViews, viewIndex);
|
||||
const view = this.nestedViews.splice(viewIndex, 1)[0];
|
||||
if (view.type === ViewType.COMPONENT) {
|
||||
throw new Error(`Component views can't be moved!`);
|
||||
}
|
||||
|
@ -113,17 +113,6 @@ export abstract class AppView<T> {
|
||||
}
|
||||
}
|
||||
|
||||
selectOrCreateHostElement(
|
||||
elementName: string, rootSelectorOrNode: string|any, debugInfo: RenderDebugInfo): any {
|
||||
var hostElement: any;
|
||||
if (isPresent(rootSelectorOrNode)) {
|
||||
hostElement = this.renderer.selectRootElement(rootSelectorOrNode, debugInfo);
|
||||
} else {
|
||||
hostElement = this.renderer.createElement(null, elementName, debugInfo);
|
||||
}
|
||||
return hostElement;
|
||||
}
|
||||
|
||||
injectorGet(token: any, nodeIndex: number, notFoundResult: any): any {
|
||||
return this.injectorGetInternal(token, nodeIndex, notFoundResult);
|
||||
}
|
||||
|
@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
import {Injector} from '../di/injector';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {unimplemented} from '../facade/errors';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {WtfScopeFn, wtfCreateScope, wtfLeave} from '../profile/profile';
|
||||
@ -186,7 +185,7 @@ export class ViewContainerRef_ implements ViewContainerRef {
|
||||
}
|
||||
|
||||
indexOf(viewRef: ViewRef): number {
|
||||
return ListWrapper.indexOf(this._element.nestedViews, (<ViewRef_<any>>viewRef).internalView);
|
||||
return this._element.nestedViews.indexOf((<ViewRef_<any>>viewRef).internalView);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@ -194,9 +193,9 @@ export class ViewContainerRef_ implements ViewContainerRef {
|
||||
|
||||
// TODO(i): rename to destroy
|
||||
remove(index: number = -1): void {
|
||||
var s = this._removeScope();
|
||||
const s = this._removeScope();
|
||||
if (index == -1) index = this.length - 1;
|
||||
var view = this._element.detachView(index);
|
||||
const view = this._element.detachView(index);
|
||||
view.destroy();
|
||||
// view is intentionally not returned to the client.
|
||||
wtfLeave(s);
|
||||
@ -207,14 +206,14 @@ export class ViewContainerRef_ implements ViewContainerRef {
|
||||
|
||||
// TODO(i): refactor insert+remove into move
|
||||
detach(index: number = -1): ViewRef {
|
||||
var s = this._detachScope();
|
||||
const s = this._detachScope();
|
||||
if (index == -1) index = this.length - 1;
|
||||
var view = this._element.detachView(index);
|
||||
const view = this._element.detachView(index);
|
||||
return wtfLeave(s, view.ref);
|
||||
}
|
||||
|
||||
clear() {
|
||||
for (var i = this.length - 1; i >= 0; i--) {
|
||||
for (let i = this.length - 1; i >= 0; i--) {
|
||||
this.remove(i);
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import {UNINITIALIZED} from '../change_detection/change_detection_util';
|
||||
import {Inject, Injectable} from '../di';
|
||||
import {isPresent, looseIdentical} from '../facade/lang';
|
||||
import {ViewEncapsulation} from '../metadata/view';
|
||||
import {RenderComponentType, Renderer, RootRenderer} from '../render/api';
|
||||
import {RenderComponentType, RenderDebugInfo, Renderer, RootRenderer} from '../render/api';
|
||||
import {Sanitizer} from '../security';
|
||||
|
||||
import {AppElement} from './element';
|
||||
@ -377,3 +377,153 @@ const CAMEL_CASE_REGEXP = /([A-Z])/g;
|
||||
function camelCaseToDashCase(input: string): string {
|
||||
return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase());
|
||||
}
|
||||
|
||||
export function createRenderElement(
|
||||
renderer: Renderer, parentElement: any, name: string, attrs: InlineArray<string>,
|
||||
debugInfo?: RenderDebugInfo): any {
|
||||
const el = renderer.createElement(parentElement, name, debugInfo);
|
||||
for (var i = 0; i < attrs.length; i += 2) {
|
||||
renderer.setElementAttribute(el, attrs.get(i), attrs.get(i + 1));
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
export function selectOrCreateRenderHostElement(
|
||||
renderer: Renderer, elementName: string, attrs: InlineArray<string>,
|
||||
rootSelectorOrNode: string | any, debugInfo?: RenderDebugInfo): any {
|
||||
var hostElement: any;
|
||||
if (isPresent(rootSelectorOrNode)) {
|
||||
hostElement = renderer.selectRootElement(rootSelectorOrNode, debugInfo);
|
||||
} else {
|
||||
hostElement = createRenderElement(renderer, null, elementName, attrs, debugInfo);
|
||||
}
|
||||
return hostElement;
|
||||
}
|
||||
|
||||
export interface InlineArray<T> {
|
||||
length: number;
|
||||
get(index: number): T;
|
||||
}
|
||||
|
||||
class InlineArray0 implements InlineArray<any> {
|
||||
length = 0;
|
||||
get(index: number): any { return undefined; }
|
||||
}
|
||||
|
||||
export class InlineArray2<T> implements InlineArray<T> {
|
||||
constructor(public length: number, private _v0?: T, private _v1?: T) {}
|
||||
get(index: number) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return this._v0;
|
||||
case 1:
|
||||
return this._v1;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class InlineArray4<T> implements InlineArray<T> {
|
||||
constructor(
|
||||
public length: number, private _v0?: T, private _v1?: T, private _v2?: T, private _v3?: T) {}
|
||||
get(index: number) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return this._v0;
|
||||
case 1:
|
||||
return this._v1;
|
||||
case 2:
|
||||
return this._v2;
|
||||
case 3:
|
||||
return this._v3;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class InlineArray8<T> implements InlineArray<T> {
|
||||
constructor(
|
||||
public length: number, private _v0?: T, private _v1?: T, private _v2?: T, private _v3?: T,
|
||||
private _v4?: T, private _v5?: T, private _v6?: T, private _v7?: T) {}
|
||||
get(index: number) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return this._v0;
|
||||
case 1:
|
||||
return this._v1;
|
||||
case 2:
|
||||
return this._v2;
|
||||
case 3:
|
||||
return this._v3;
|
||||
case 4:
|
||||
return this._v4;
|
||||
case 5:
|
||||
return this._v5;
|
||||
case 6:
|
||||
return this._v6;
|
||||
case 7:
|
||||
return this._v7;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class InlineArray16<T> implements InlineArray<T> {
|
||||
constructor(
|
||||
public length: number, private _v0?: T, private _v1?: T, private _v2?: T, private _v3?: T,
|
||||
private _v4?: T, private _v5?: T, private _v6?: T, private _v7?: T, private _v8?: T,
|
||||
private _v9?: T, private _v10?: T, private _v11?: T, private _v12?: T, private _v13?: T,
|
||||
private _v14?: T, private _v15?: T) {}
|
||||
get(index: number) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return this._v0;
|
||||
case 1:
|
||||
return this._v1;
|
||||
case 2:
|
||||
return this._v2;
|
||||
case 3:
|
||||
return this._v3;
|
||||
case 4:
|
||||
return this._v4;
|
||||
case 5:
|
||||
return this._v5;
|
||||
case 6:
|
||||
return this._v6;
|
||||
case 7:
|
||||
return this._v7;
|
||||
case 8:
|
||||
return this._v8;
|
||||
case 9:
|
||||
return this._v9;
|
||||
case 10:
|
||||
return this._v10;
|
||||
case 11:
|
||||
return this._v11;
|
||||
case 12:
|
||||
return this._v12;
|
||||
case 13:
|
||||
return this._v13;
|
||||
case 14:
|
||||
return this._v14;
|
||||
case 15:
|
||||
return this._v15;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class InlineArrayDynamic<T> implements InlineArray<T> {
|
||||
private _values: any[];
|
||||
// Note: We still take the length argument so this class can be created
|
||||
// in the same ways as the other classes!
|
||||
constructor(public length: number, ...values: any[]) { this._values = values; }
|
||||
|
||||
get(index: number) { return this._values[index]; }
|
||||
}
|
||||
|
||||
export const EMPTY_INLINE_ARRAY: InlineArray<any> = new InlineArray0();
|
||||
|
@ -9,7 +9,6 @@
|
||||
import {DefaultIterableDiffer, DefaultIterableDifferFactory} from '@angular/core/src/change_detection/differs/default_iterable_differ';
|
||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {ListWrapper} from '../../../src/facade/collection';
|
||||
import {TestIterable} from '../../change_detection/iterable';
|
||||
import {iterableChangesAsString} from '../../change_detection/util';
|
||||
|
||||
@ -110,7 +109,7 @@ export function main() {
|
||||
let l = [1, 2];
|
||||
differ.check(l);
|
||||
|
||||
ListWrapper.clear(l);
|
||||
l.length = 0;
|
||||
l.push(2);
|
||||
l.push(1);
|
||||
differ.check(l);
|
||||
@ -125,8 +124,8 @@ export function main() {
|
||||
let l = ['a', 'b', 'c'];
|
||||
differ.check(l);
|
||||
|
||||
ListWrapper.removeAt(l, 1);
|
||||
ListWrapper.insert(l, 0, 'b');
|
||||
l.splice(1, 1);
|
||||
l.splice(0, 0, 'b');
|
||||
differ.check(l);
|
||||
expect(differ.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['b[1->0]', 'a[0->1]', 'c'],
|
||||
@ -134,7 +133,7 @@ export function main() {
|
||||
moves: ['b[1->0]', 'a[0->1]']
|
||||
}));
|
||||
|
||||
ListWrapper.removeAt(l, 1);
|
||||
l.splice(1, 1);
|
||||
l.push('a');
|
||||
differ.check(l);
|
||||
expect(differ.toString()).toEqual(iterableChangesAsString({
|
||||
@ -170,7 +169,7 @@ export function main() {
|
||||
additions: ['c[null->2]', 'd[null->3]']
|
||||
}));
|
||||
|
||||
ListWrapper.removeAt(l, 2);
|
||||
l.splice(2, 1);
|
||||
differ.check(l);
|
||||
expect(differ.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['a', 'b', 'd[3->2]'],
|
||||
@ -179,7 +178,7 @@ export function main() {
|
||||
removals: ['c[2->null]']
|
||||
}));
|
||||
|
||||
ListWrapper.clear(l);
|
||||
l.length = 0;
|
||||
l.push('d');
|
||||
l.push('c');
|
||||
l.push('b');
|
||||
@ -214,10 +213,10 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should detect [NaN] moves', () => {
|
||||
let l = [NaN, NaN];
|
||||
let l: any[] = [NaN, NaN];
|
||||
differ.check(l);
|
||||
|
||||
ListWrapper.insert<any>(l, 0, 'foo');
|
||||
l.unshift('foo');
|
||||
differ.check(l);
|
||||
expect(differ.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['foo[null->0]', 'NaN[0->1]', 'NaN[1->2]'],
|
||||
@ -231,7 +230,7 @@ export function main() {
|
||||
let l = ['a', 'b', 'c'];
|
||||
differ.check(l);
|
||||
|
||||
ListWrapper.removeAt(l, 1);
|
||||
l.splice(1, 1);
|
||||
differ.check(l);
|
||||
expect(differ.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['a', 'c[2->1]'],
|
||||
@ -240,7 +239,7 @@ export function main() {
|
||||
removals: ['b[1->null]']
|
||||
}));
|
||||
|
||||
ListWrapper.insert(l, 1, 'b');
|
||||
l.splice(1, 0, 'b');
|
||||
differ.check(l);
|
||||
expect(differ.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['a', 'b[null->1]', 'c[1->2]'],
|
||||
@ -255,7 +254,7 @@ export function main() {
|
||||
let l = ['a', 'a', 'a', 'b', 'b'];
|
||||
differ.check(l);
|
||||
|
||||
ListWrapper.removeAt(l, 0);
|
||||
l.splice(0, 1);
|
||||
differ.check(l);
|
||||
expect(differ.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['a', 'a', 'b[3->2]', 'b[4->3]'],
|
||||
@ -269,7 +268,7 @@ export function main() {
|
||||
let l = ['a', 'a', 'b', 'b'];
|
||||
differ.check(l);
|
||||
|
||||
ListWrapper.insert(l, 0, 'b');
|
||||
l.splice(0, 0, 'b');
|
||||
differ.check(l);
|
||||
expect(differ.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['b[2->0]', 'a[0->1]', 'a[1->2]', 'b', 'b[null->4]'],
|
||||
@ -283,7 +282,7 @@ export function main() {
|
||||
let l = ['a', 'b', 'c'];
|
||||
differ.check(l);
|
||||
|
||||
ListWrapper.clear(l);
|
||||
l.length = 0;
|
||||
l.push('b');
|
||||
l.push('a');
|
||||
l.push('c');
|
||||
@ -557,7 +556,7 @@ export function main() {
|
||||
let l = buildItemList(['a', 'b', 'c']);
|
||||
differ.check(l);
|
||||
|
||||
ListWrapper.removeAt(l, 2);
|
||||
l.splice(2, 1);
|
||||
differ.check(l);
|
||||
expect(differ.toString()).toEqual(iterableChangesAsString({
|
||||
collection: ['{id: a}', '{id: b}'],
|
||||
|
@ -826,6 +826,73 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
expect(tc.nativeElement.id).toEqual('newId');
|
||||
});
|
||||
|
||||
it('should not use template variables for expressions in hostProperties', () => {
|
||||
@Directive({selector: '[host-properties]', host: {'[id]': 'id', '[title]': 'unknownProp'}})
|
||||
class DirectiveWithHostProps {
|
||||
id = 'one';
|
||||
}
|
||||
|
||||
const fixture =
|
||||
TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostProps]})
|
||||
.overrideComponent(
|
||||
MyComp,
|
||||
{set: {template: `<div *ngFor="let id of ['forId']" host-properties></div>`}})
|
||||
.createComponent(MyComp);
|
||||
fixture.detectChanges();
|
||||
|
||||
var tc = fixture.debugElement.children[0];
|
||||
expect(tc.properties['id']).toBe('one');
|
||||
expect(tc.properties['title']).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should not allow pipes in hostProperties', () => {
|
||||
@Directive({selector: '[host-properties]', host: {'[id]': 'id | uppercase'}})
|
||||
class DirectiveWithHostProps {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostProps]});
|
||||
const template = '<div host-properties></div>';
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
expect(() => TestBed.createComponent(MyComp))
|
||||
.toThrowError(/Host binding expression cannot contain pipes/);
|
||||
});
|
||||
|
||||
it('should not use template variables for expressions in hostListeners', () => {
|
||||
@Directive({selector: '[host-listener]', host: {'(click)': 'doIt(id, unknownProp)'}})
|
||||
class DirectiveWithHostListener {
|
||||
id = 'one';
|
||||
receivedArgs: any[];
|
||||
|
||||
doIt(...args: any[]) { this.receivedArgs = args; }
|
||||
}
|
||||
|
||||
const fixture =
|
||||
TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostListener]})
|
||||
.overrideComponent(
|
||||
MyComp,
|
||||
{set: {template: `<div *ngFor="let id of ['forId']" host-listener></div>`}})
|
||||
.createComponent(MyComp);
|
||||
fixture.detectChanges();
|
||||
var tc = fixture.debugElement.children[0];
|
||||
tc.triggerEventHandler('click', {});
|
||||
var dir: DirectiveWithHostListener = tc.injector.get(DirectiveWithHostListener);
|
||||
expect(dir.receivedArgs).toEqual(['one', undefined]);
|
||||
});
|
||||
|
||||
it('should not allow pipes in hostListeners', () => {
|
||||
@Directive({selector: '[host-listener]', host: {'(click)': 'doIt() | somePipe'}})
|
||||
class DirectiveWithHostListener {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostListener]});
|
||||
const template = '<div host-listener></div>';
|
||||
TestBed.overrideComponent(MyComp, {set: {template}});
|
||||
expect(() => TestBed.createComponent(MyComp))
|
||||
.toThrowError(/Cannot have a pipe in an action expression/);
|
||||
});
|
||||
|
||||
|
||||
|
||||
if (getDOM().supportsDOMEvents()) {
|
||||
it('should support preventing default on render events', () => {
|
||||
TestBed.configureTestingModule({
|
||||
|
@ -641,8 +641,7 @@ class NeedsQueryAndProject {
|
||||
|
||||
@Component({
|
||||
selector: 'needs-view-query',
|
||||
template: '<div text="1"><div text="2"></div></div>' +
|
||||
'<div text="3"></div><div text="4"></div>'
|
||||
template: '<div text="1"><div text="2"></div></div><div text="3"></div><div text="4"></div>'
|
||||
})
|
||||
class NeedsViewQuery {
|
||||
@ViewChildren(TextDirective) query: QueryList<TextDirective>;
|
||||
|
@ -6,9 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Directive, Input, NO_ERRORS_SCHEMA} from '@angular/core';
|
||||
import {Component, Directive, HostBinding, Input, NO_ERRORS_SCHEMA} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, getTestBed} from '@angular/core/testing';
|
||||
import {afterEach, beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
import {afterEach, beforeEach, describe, expect, iit, it} from '@angular/core/testing/testing_internal';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {DomSanitizer} from '@angular/platform-browser/src/security/dom_sanitization_service';
|
||||
|
||||
@ -133,22 +133,67 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
});
|
||||
|
||||
describe('sanitizing', () => {
|
||||
it('should escape unsafe attributes', () => {
|
||||
const template = `<a [href]="ctxProp">Link Title</a>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}});
|
||||
const fixture = TestBed.createComponent(SecuredComponent);
|
||||
|
||||
function checkEscapeOfHrefProperty(fixture: ComponentFixture<any>, isAttribute: boolean) {
|
||||
let e = fixture.debugElement.children[0].nativeElement;
|
||||
let ci = fixture.componentInstance;
|
||||
ci.ctxProp = 'hello';
|
||||
fixture.detectChanges();
|
||||
// In the browser, reading href returns an absolute URL. On the server side,
|
||||
// it just echoes back the property.
|
||||
expect(getDOM().getProperty(e, 'href')).toMatch(/.*\/?hello$/);
|
||||
let value =
|
||||
isAttribute ? getDOM().getAttribute(e, 'href') : getDOM().getProperty(e, 'href');
|
||||
expect(value).toMatch(/.*\/?hello$/);
|
||||
|
||||
ci.ctxProp = 'javascript:alert(1)';
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().getProperty(e, 'href')).toEqual('unsafe:javascript:alert(1)');
|
||||
value = isAttribute ? getDOM().getAttribute(e, 'href') : getDOM().getProperty(e, 'href');
|
||||
expect(value).toEqual('unsafe:javascript:alert(1)');
|
||||
}
|
||||
|
||||
it('should escape unsafe properties', () => {
|
||||
const template = `<a [href]="ctxProp">Link Title</a>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}});
|
||||
const fixture = TestBed.createComponent(SecuredComponent);
|
||||
|
||||
checkEscapeOfHrefProperty(fixture, false);
|
||||
});
|
||||
|
||||
it('should escape unsafe attributes', () => {
|
||||
const template = `<a [attr.href]="ctxProp">Link Title</a>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}});
|
||||
const fixture = TestBed.createComponent(SecuredComponent);
|
||||
|
||||
checkEscapeOfHrefProperty(fixture, true);
|
||||
});
|
||||
|
||||
it('should escape unsafe properties if they are used in host bindings', () => {
|
||||
@Directive({selector: '[dirHref]'})
|
||||
class HrefDirective {
|
||||
@HostBinding('href') @Input()
|
||||
dirHref: string;
|
||||
}
|
||||
|
||||
const template = `<a [dirHref]="ctxProp">Link Title</a>`;
|
||||
TestBed.configureTestingModule({declarations: [HrefDirective]});
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}});
|
||||
const fixture = TestBed.createComponent(SecuredComponent);
|
||||
|
||||
checkEscapeOfHrefProperty(fixture, false);
|
||||
});
|
||||
|
||||
it('should escape unsafe attributes if they are used in host bindings', () => {
|
||||
@Directive({selector: '[dirHref]'})
|
||||
class HrefDirective {
|
||||
@HostBinding('attr.href') @Input()
|
||||
dirHref: string;
|
||||
}
|
||||
|
||||
const template = `<a [dirHref]="ctxProp">Link Title</a>`;
|
||||
TestBed.configureTestingModule({declarations: [HrefDirective]});
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}});
|
||||
const fixture = TestBed.createComponent(SecuredComponent);
|
||||
|
||||
checkEscapeOfHrefProperty(fixture, true);
|
||||
});
|
||||
|
||||
it('should escape unsafe style values', () => {
|
||||
|
@ -85,78 +85,29 @@ export class StringMapWrapper {
|
||||
export interface Predicate<T> { (value: T, index?: number, array?: T[]): boolean; }
|
||||
|
||||
export class ListWrapper {
|
||||
// JS has no way to express a statically fixed size list, but dart does so we
|
||||
// keep both methods.
|
||||
static createFixedSize(size: number): any[] { return new Array(size); }
|
||||
static createGrowableSize(size: number): any[] { return new Array(size); }
|
||||
static clone<T>(array: T[]): T[] { return array.slice(0); }
|
||||
static forEachWithIndex<T>(array: T[], fn: (t: T, n: number) => void) {
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
fn(array[i], i);
|
||||
}
|
||||
}
|
||||
static first<T>(array: T[]): T {
|
||||
if (!array) return null;
|
||||
return array[0];
|
||||
}
|
||||
static last<T>(array: T[]): T {
|
||||
if (!array || array.length == 0) return null;
|
||||
return array[array.length - 1];
|
||||
}
|
||||
static indexOf<T>(array: T[], value: T, startIndex: number = 0): number {
|
||||
return array.indexOf(value, startIndex);
|
||||
}
|
||||
static contains<T>(list: T[], el: T): boolean { return list.indexOf(el) !== -1; }
|
||||
static reversed<T>(array: T[]): T[] {
|
||||
var a = ListWrapper.clone(array);
|
||||
return a.reverse();
|
||||
}
|
||||
static concat(a: any[], b: any[]): any[] { return a.concat(b); }
|
||||
static insert<T>(list: T[], index: number, value: T) { list.splice(index, 0, value); }
|
||||
static removeAt<T>(list: T[], index: number): T {
|
||||
var res = list[index];
|
||||
list.splice(index, 1);
|
||||
return res;
|
||||
}
|
||||
static removeAll<T>(list: T[], items: T[]) {
|
||||
for (var i = 0; i < items.length; ++i) {
|
||||
var index = list.indexOf(items[i]);
|
||||
for (let i = 0; i < items.length; ++i) {
|
||||
const index = list.indexOf(items[i]);
|
||||
list.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static remove<T>(list: T[], el: T): boolean {
|
||||
var index = list.indexOf(el);
|
||||
const index = list.indexOf(el);
|
||||
if (index > -1) {
|
||||
list.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static clear(list: any[]) { list.length = 0; }
|
||||
static isEmpty(list: any[]): boolean { return list.length == 0; }
|
||||
static fill(list: any[], value: any, start: number = 0, end: number = null) {
|
||||
list.fill(value, start, end === null ? list.length : end);
|
||||
}
|
||||
|
||||
static equals(a: any[], b: any[]): boolean {
|
||||
if (a.length != b.length) return false;
|
||||
for (var i = 0; i < a.length; ++i) {
|
||||
for (let i = 0; i < a.length; ++i) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static slice<T>(l: T[], from: number = 0, to: number = null): T[] {
|
||||
return l.slice(from, to === null ? undefined : to);
|
||||
}
|
||||
static splice<T>(l: T[], from: number, length: number): T[] { return l.splice(from, length); }
|
||||
static sort<T>(l: T[], compareFn?: (a: T, b: T) => number) {
|
||||
if (isPresent(compareFn)) {
|
||||
l.sort(compareFn);
|
||||
} else {
|
||||
l.sort();
|
||||
}
|
||||
}
|
||||
static toString<T>(l: T[]): string { return l.toString(); }
|
||||
static toJSON<T>(l: T[]): string { return JSON.stringify(l); }
|
||||
|
||||
static maximum<T>(list: T[], predicate: (t: T) => number): T {
|
||||
if (list.length == 0) {
|
||||
@ -166,7 +117,7 @@ export class ListWrapper {
|
||||
var maxValue = -Infinity;
|
||||
for (var index = 0; index < list.length; index++) {
|
||||
var candidate = list[index];
|
||||
if (isBlank(candidate)) {
|
||||
if (candidate == null) {
|
||||
continue;
|
||||
}
|
||||
var candidateValue = predicate(candidate);
|
||||
@ -183,12 +134,6 @@ export class ListWrapper {
|
||||
_flattenArray(list, target);
|
||||
return target;
|
||||
}
|
||||
|
||||
static addAll<T>(list: Array<T>, source: Array<T>): void {
|
||||
for (var i = 0; i < source.length; i++) {
|
||||
list.push(source[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _flattenArray(source: any[], target: any[]): any[] {
|
||||
|
@ -53,11 +53,10 @@ export function scheduleMicroTask(fn: Function) {
|
||||
|
||||
// Need to declare a new variable for global here since TypeScript
|
||||
// exports the original value of the symbol.
|
||||
var _global: BrowserNodeGlobal = globalScope;
|
||||
const _global: BrowserNodeGlobal = globalScope;
|
||||
|
||||
export {_global as global};
|
||||
|
||||
|
||||
export function getTypeNameForDebugging(type: any): string {
|
||||
return type['name'] || typeof type;
|
||||
}
|
||||
@ -70,11 +69,11 @@ _global.assert = function assert(condition) {
|
||||
};
|
||||
|
||||
export function isPresent(obj: any): boolean {
|
||||
return obj !== undefined && obj !== null;
|
||||
return obj != null;
|
||||
}
|
||||
|
||||
export function isBlank(obj: any): boolean {
|
||||
return obj === undefined || obj === null;
|
||||
return obj == null;
|
||||
}
|
||||
|
||||
const STRING_MAP_PROTO = Object.getPrototypeOf({});
|
||||
@ -86,8 +85,6 @@ export function isDate(obj: any): obj is Date {
|
||||
return obj instanceof Date && !isNaN(obj.valueOf());
|
||||
}
|
||||
|
||||
export function noop() {}
|
||||
|
||||
export function stringify(token: any): string {
|
||||
if (typeof token === 'string') {
|
||||
return token;
|
||||
@ -144,14 +141,6 @@ export function looseIdentical(a: any, b: any): boolean {
|
||||
return a === b || typeof a === 'number' && typeof b === 'number' && isNaN(a) && isNaN(b);
|
||||
}
|
||||
|
||||
export function normalizeBlank(obj: Object): any {
|
||||
return isBlank(obj) ? null : obj;
|
||||
}
|
||||
|
||||
export function normalizeBool(obj: boolean): boolean {
|
||||
return isBlank(obj) ? false : obj;
|
||||
}
|
||||
|
||||
export function isJsObject(o: any): boolean {
|
||||
return o !== null && (typeof o === 'function' || typeof o === 'object');
|
||||
}
|
||||
@ -182,17 +171,17 @@ export function setValueOnPath(global: any, path: string, value: any) {
|
||||
}
|
||||
|
||||
// When Symbol.iterator doesn't exist, retrieves the key used in es6-shim
|
||||
declare var Symbol: any;
|
||||
var _symbolIterator: any = null;
|
||||
declare let Symbol: any;
|
||||
let _symbolIterator: any = null;
|
||||
export function getSymbolIterator(): string|symbol {
|
||||
if (isBlank(_symbolIterator)) {
|
||||
if (isPresent((<any>globalScope).Symbol) && isPresent(Symbol.iterator)) {
|
||||
if (!_symbolIterator) {
|
||||
if ((<any>globalScope).Symbol && Symbol.iterator) {
|
||||
_symbolIterator = Symbol.iterator;
|
||||
} else {
|
||||
// es6-shim specific logic
|
||||
var keys = Object.getOwnPropertyNames(Map.prototype);
|
||||
for (var i = 0; i < keys.length; ++i) {
|
||||
var key = keys[i];
|
||||
const keys = Object.getOwnPropertyNames(Map.prototype);
|
||||
for (let i = 0; i < keys.length; ++i) {
|
||||
let key = keys[i];
|
||||
if (key !== 'entries' && key !== 'size' &&
|
||||
(Map as any).prototype[key] === Map.prototype['entries']) {
|
||||
_symbolIterator = key;
|
||||
|
@ -10,81 +10,6 @@ import {ListWrapper, MapWrapper, StringMapWrapper} from '../src/collection';
|
||||
|
||||
export function main() {
|
||||
describe('ListWrapper', () => {
|
||||
var l: number[];
|
||||
|
||||
describe('splice', () => {
|
||||
it('should remove sublist of given length and return it', () => {
|
||||
var list = [1, 2, 3, 4, 5, 6];
|
||||
expect(ListWrapper.splice(list, 1, 3)).toEqual([2, 3, 4]);
|
||||
expect(list).toEqual([1, 5, 6]);
|
||||
});
|
||||
|
||||
it('should support negative start', () => {
|
||||
var list = [1, 2, 3, 4, 5, 6];
|
||||
expect(ListWrapper.splice(list, -5, 3)).toEqual([2, 3, 4]);
|
||||
expect(list).toEqual([1, 5, 6]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fill', () => {
|
||||
beforeEach(() => { l = [1, 2, 3, 4]; });
|
||||
|
||||
it('should fill the whole list if neither start nor end are specified', () => {
|
||||
ListWrapper.fill(l, 9);
|
||||
expect(l).toEqual([9, 9, 9, 9]);
|
||||
});
|
||||
|
||||
it('should fill up to the end if end is not specified', () => {
|
||||
ListWrapper.fill(l, 9, 1);
|
||||
expect(l).toEqual([1, 9, 9, 9]);
|
||||
});
|
||||
|
||||
it('should support negative start', () => {
|
||||
ListWrapper.fill(l, 9, -1);
|
||||
expect(l).toEqual([1, 2, 3, 9]);
|
||||
});
|
||||
|
||||
it('should support negative end', () => {
|
||||
ListWrapper.fill(l, 9, -2, -1);
|
||||
expect(l).toEqual([1, 2, 9, 4]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('slice', () => {
|
||||
beforeEach(() => { l = [1, 2, 3, 4]; });
|
||||
|
||||
it('should return the whole list if neither start nor end are specified', () => {
|
||||
expect(ListWrapper.slice(l)).toEqual([1, 2, 3, 4]);
|
||||
});
|
||||
|
||||
it('should return up to the end if end is not specified', () => {
|
||||
expect(ListWrapper.slice(l, 1)).toEqual([2, 3, 4]);
|
||||
});
|
||||
|
||||
it('should support negative start', () => { expect(ListWrapper.slice(l, -1)).toEqual([4]); });
|
||||
|
||||
it('should support negative end', () => {
|
||||
expect(ListWrapper.slice(l, -3, -1)).toEqual([2, 3]);
|
||||
});
|
||||
|
||||
it('should return empty list if start is greater than end', () => {
|
||||
expect(ListWrapper.slice(l, 4, 2)).toEqual([]);
|
||||
expect(ListWrapper.slice(l, -2, -4)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('indexOf', () => {
|
||||
beforeEach(() => { l = [1, 2, 3, 4]; });
|
||||
|
||||
it('should find values that exist', () => { expect(ListWrapper.indexOf(l, 1)).toEqual(0); });
|
||||
|
||||
it('should not find values that do not exist',
|
||||
() => { expect(ListWrapper.indexOf(l, 9)).toEqual(-1); });
|
||||
|
||||
it('should respect the startIndex parameter',
|
||||
() => { expect(ListWrapper.indexOf(l, 1, 1)).toEqual(-1); });
|
||||
});
|
||||
|
||||
describe('maximum', () => {
|
||||
it('should return the maximal element', () => {
|
||||
expect(ListWrapper.maximum([1, 2, 3, 4], x => x)).toEqual(4);
|
||||
@ -102,17 +27,6 @@ export function main() {
|
||||
() => { expect(ListWrapper.maximum([], x => x)).toEqual(null); });
|
||||
});
|
||||
|
||||
describe('forEachWithIndex', () => {
|
||||
var l: any /** TODO #9100 */;
|
||||
|
||||
beforeEach(() => { l = ['a', 'b']; });
|
||||
|
||||
it('should iterate over an array passing values and indices', () => {
|
||||
var record: any[] /** TODO #9100 */ = [];
|
||||
ListWrapper.forEachWithIndex(l, (value, index) => record.push([value, index]));
|
||||
expect(record).toEqual([['a', 0], ['b', 1]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('StringMapWrapper', () => {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user