Compare commits

..

23 Commits

Author SHA1 Message Date
69ad99dca6 chore(release): cut the 2.2.0-beta.0 release and add changelog 2016-10-20 14:36:46 -07:00
da5fc696bb fix(router): do not update primary route if only secondary outlet is given (#11797) 2016-10-20 10:59:08 -07:00
b44b6ef8f5 fix(router): module loader should start compiling modules when stubbedModules are set (#11742) 2016-10-20 10:58:53 -07:00
0f21a5823b cleanup(router): add a test verifying than NavigationEnd is not emitted after NavigationCancel 2016-10-20 10:56:12 -07:00
5ae6915600 fix(router): fix lazy loading triggered by redirects from wildcard routes
Closes #12183
2016-10-20 10:56:12 -07:00
8b9ab44eee feat(router): add support for ng1/ng2 migration (#12160) 2016-10-20 10:44:44 -07:00
b0a03fcab3 refactor(compiler): introduce directive wrappers to generate less code
- for now only wraps the `@Input` properties and calls
  to `ngOnInit`, `ngDoCheck` and `ngOnChanges` of directives.
- also groups eval sources by NgModule.

Part of #11683
2016-10-20 10:41:43 -07:00
c951822c35 refactor(compiler): don’t use the OfflineCompiler in extract_i18n 2016-10-20 10:41:43 -07:00
acda82c1ed refactor(compiler): remove private exports
All of `@angular/compiler` is private, so we can export
everything we need directly.
2016-10-20 10:41:43 -07:00
a8815d6b08 chore(ci): re-enable browserstack tests in ci 2016-10-20 10:01:51 -07:00
d6791ff0e0 feat(ngUpgrade): add support for AoT compiled upgrade applications
This commit introduces a new API to the ngUpgrade module, which is compatible
with AoT compilation. Primarily, it removes the dependency on reflection
over the Angular 2 metadata by introducing an API where this information
is explicitly defined, in the source code, in a way that is not lost through
AoT compilation.

This commit is a collaboration between @mhevery (who provided the original
design of the API); @gkalpak & @petebacondarwin (who implemented the
API and migrated the specs from the original ngUpgrade tests) and @alexeagle
(who provided input and review).

This commit is an starting point, there is still work to be done:

* add more documentation
* validate the API via internal projects
* align the ngUpgrade compilation of A1 directives closer to the real A1
  compiler
* add more unit tests
* consider support for async `templateUrl` A1 upgraded components

Closes #12239
2016-10-19 15:27:49 -07:00
a2d35641e3 chore(tslint.json): semicolon rule expects an array 2016-10-19 22:38:14 +01:00
76dd026447 refactor: remove some facades (#12335) 2016-10-19 13:42:39 -07:00
0ecd9b2df0 chore(ci): make browserstack tests optional until they are fixed 2016-10-19 10:41:00 -07:00
0e9503b500 feat(forms) range values need to be numbers instead of strings (#11792) 2016-10-19 10:12:13 -07:00
f77ab6a2d2 feat(datePipe): support narrow forms for month and weekdays (#12297)
Closes #12294
2016-10-19 10:05:13 -07:00
97bc97153b feat(forms): add ng-pending CSS class during async validation (#11243)
Closes #10336
2016-10-19 09:56:31 -07:00
445e5922ec feat(forms): make 'parent' a public property of 'AbstractControl' (#11855) 2016-10-19 09:55:50 -07:00
b9fc090143 feat(forms): Added emitEvent to AbstractControl methods (#11949)
* feat(forms): Added emitEvent to AbstractControl methods

* style(forms): unified named parameter
2016-10-19 09:54:54 -07:00
592f40aa9c feat(forms): add hasError and getError to AbstractControlDirective (#11985)
Allows cleaner expressions in template-driven forms.

Before:

    <label>Username</label><input name="username" ngModel required #username="ngModel">
    <div *ngIf="username.dirty && username.control.hasError('required')">Username is required</div>

After:

    <label>Username</label><input name="username" ngModel required #username="ngModel">
    <div *ngIf="username.dirty && username.hasError('required')">Username is required</div>

Fixes #7255
2016-10-19 09:49:02 -07:00
24facdea2d feat(benchmark): add large form benchmark
This benchmark tracks the generated file size for large forms
as well as the time to create and destroy many form fields.
2016-10-19 09:39:16 -07:00
aa2d3372a5 fix(benchmarks): fix method name in targetable spec 2016-10-19 09:39:16 -07:00
bf60418fdc feat(forms): Validator.pattern accepts a RegExp (#12323) 2016-10-19 09:37:54 -07:00
310 changed files with 9622 additions and 10522 deletions

View File

@ -1,54 +1,12 @@
<a name="2.1.2"></a> <a name="2.2.0-beta.0"></a>
## [2.1.2](https://github.com/angular/angular/compare/2.1.1...2.1.2) (2016-10-27) # [2.2.0-beta.0](https://github.com/angular/angular/compare/2.1.0...2.2.0-beta.0) (2016-10-20)
### 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 ### 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:** 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)) * **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 default values ([bd1dcb5](https://github.com/angular/angular/commit/bd1dcb5)) * **core:** fix decorator defalut 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) * **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)) * **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)) * **router:** correctly export filter operator in es5 ([#12286](https://github.com/angular/angular/issues/12286)) ([27d7677](https://github.com/angular/angular/commit/27d7677))
@ -57,15 +15,6 @@ Note: The 2.2.0-beta.1 release also contains all the changes present in the 2.1.
* **router:** module loader should start compiling modules when stubbedModules are set ([#11742](https://github.com/angular/angular/issues/11742)) ([b44b6ef](https://github.com/angular/angular/commit/b44b6ef)) * **router:** module loader should start compiling modules when stubbedModules are set ([#11742](https://github.com/angular/angular/issues/11742)) ([b44b6ef](https://github.com/angular/angular/commit/b44b6ef))
### Performance Improvements
* **common:** optimize NgSwitch default case ([fdf4309](https://github.com/angular/angular/commit/fdf4309))
<a name="2.2.0-beta.0"></a>
# [2.2.0-beta.0](https://github.com/angular/angular/compare/2.1.0...2.2.0-beta.0) (2016-10-20)
### Features ### Features
* **common:** support narrow forms for month and weekdays in DatePipe ([#12297](https://github.com/angular/angular/issues/12297)) ([f77ab6a](https://github.com/angular/angular/commit/f77ab6a)), closes [#12294](https://github.com/angular/angular/issues/12294) * **common:** support narrow forms for month and weekdays in DatePipe ([#12297](https://github.com/angular/angular/issues/12297)) ([f77ab6a](https://github.com/angular/angular/commit/f77ab6a)), closes [#12294](https://github.com/angular/angular/issues/12294)
@ -77,7 +26,10 @@ Note: The 2.2.0-beta.1 release also contains all the changes present in the 2.1.
* **upgrade:** add support for AoT compiled upgrade applications ([d6791ff](https://github.com/angular/angular/commit/d6791ff)), closes [#12239](https://github.com/angular/angular/issues/12239) * **upgrade:** add support for AoT compiled upgrade applications ([d6791ff](https://github.com/angular/angular/commit/d6791ff)), closes [#12239](https://github.com/angular/angular/issues/12239)
* **router:** add support for ng1/ng2 migration ([#12160](https://github.com/angular/angular/issues/12160)) ([8b9ab44](https://github.com/angular/angular/commit/8b9ab44)) * **router:** add support for ng1/ng2 migration ([#12160](https://github.com/angular/angular/issues/12160)) ([8b9ab44](https://github.com/angular/angular/commit/8b9ab44))
Note: The 2.2.0-beta.0 release also contains all the changes present in the 2.1.1 release.
### Performance Improvements
* **common:** optimize NgSwitch default case ([fdf4309](https://github.com/angular/angular/commit/fdf4309))

View File

@ -67,7 +67,6 @@ if [[ ${BUILD_ALL} == true ]]; then
ln -s ../../../../node_modules/reflect-metadata/Reflect.js . ln -s ../../../../node_modules/reflect-metadata/Reflect.js .
ln -s ../../../../node_modules/rxjs . ln -s ../../../../node_modules/rxjs .
ln -s ../../../../node_modules/angular/angular.js . ln -s ../../../../node_modules/angular/angular.js .
ln -s ../../../../node_modules/hammerjs/hammer.js .
cd - cd -
echo "====== Copying files needed for benchmarks =====" echo "====== Copying files needed for benchmarks ====="

View File

@ -124,31 +124,38 @@ gulp.task('public-api:update', ['build.sh'], (done) => {
.on('close', done); .on('close', done);
}); });
// Checks tests for presence of ddescribe, fdescribe, fit, iit and fails the build if one of the
// focused tests is found.
// Currently xdescribe and xit are _not_ reported as errors since there are a couple of excluded
// tests in our code base.
gulp.task('check-tests', function() {
const ddescribeIit = require('gulp-ddescribe-iit');
return gulp
.src([
'modules/**/*.spec.ts',
'modules/**/*_spec.ts',
])
.pipe(ddescribeIit({allowDisabledTests: true}));
});
// Check the coding standards and programming errors // Check the coding standards and programming errors
gulp.task('lint', ['format:enforce', 'tools:build'], () => { gulp.task('lint', ['check-tests', 'format:enforce', 'tools:build'], () => {
const tslint = require('gulp-tslint'); const tslint = require('gulp-tslint');
// Built-in rules are at // Built-in rules are at
// https://palantir.github.io/tslint/rules/ // https://github.com/palantir/tslint#supported-rules
const tslintConfig = require('./tslint.json'); const tslintConfig = require('./tslint.json');
return gulp return gulp
.src([ .src([
// todo(vicb): add .js files when supported // todo(vicb): add .js files when supported
// see https://github.com/palantir/tslint/pull/1515 // see https://github.com/palantir/tslint/pull/1515
'./modules/**/*.ts', 'modules/@angular/**/*.ts',
'./tools/**/*.ts', 'modules/benchpress/**/*.ts',
'./*.ts', './*.ts',
// Ignore TypeScript mocks because it's not managed by us
'!./tools/@angular/tsc-wrapped/test/typescript.mocks.ts',
// Ignore generated files due to lack of copyright header
// todo(alfaproject): make generated files lintable
'!**/*.d.ts',
'!**/*.ngfactory.ts',
]) ])
.pipe(tslint({ .pipe(tslint({
tslint: require('tslint').default, tslint: require('tslint').default,
configuration: tslintConfig, configuration: tslintConfig,
rulesDirectory: 'dist/tools/tslint',
formatter: 'prose', formatter: 'prose',
})) }))
.pipe(tslint.report({emitError: true})); .pipe(tslint.report({emitError: true}));

View File

@ -23,7 +23,7 @@ module.exports = function(config) {
'node_modules/core-js/client/core.js', 'node_modules/core-js/client/core.js',
// include Angular v1 for upgrade module testing // include Angular v1 for upgrade module testing
'node_modules/angular/angular.min.js', 'node_modules/angular/angular.js',
'node_modules/zone.js/dist/zone.js', 'node_modules/zone.js/dist/long-stack-trace-zone.js', 'node_modules/zone.js/dist/zone.js', 'node_modules/zone.js/dist/long-stack-trace-zone.js',
'node_modules/zone.js/dist/proxy.js', 'node_modules/zone.js/dist/sync-test.js', 'node_modules/zone.js/dist/proxy.js', 'node_modules/zone.js/dist/sync-test.js',

View File

@ -8,10 +8,13 @@
import {Inject, Injectable, OpaqueToken} from '@angular/core'; import {Inject, Injectable, OpaqueToken} from '@angular/core';
import {ListWrapper} from '../facade/collection';
import {MeasureValues} from '../measure_values'; import {MeasureValues} from '../measure_values';
import {Statistic} from '../statistic'; import {Statistic} from '../statistic';
import {Validator} from '../validator'; import {Validator} from '../validator';
/** /**
* A validator that checks the regression slope of a specific metric. * A validator that checks the regression slope of a specific metric.
* Waits for the regression slope to be >=0. * Waits for the regression slope to be >=0.
@ -37,17 +40,17 @@ export class RegressionSlopeValidator extends Validator {
validate(completeSample: MeasureValues[]): MeasureValues[] { validate(completeSample: MeasureValues[]): MeasureValues[] {
if (completeSample.length >= this._sampleSize) { if (completeSample.length >= this._sampleSize) {
const latestSample = var latestSample = ListWrapper.slice(
completeSample.slice(completeSample.length - this._sampleSize, completeSample.length); completeSample, completeSample.length - this._sampleSize, completeSample.length);
const xValues: number[] = []; var xValues: number[] = [];
const yValues: number[] = []; var yValues: number[] = [];
for (let i = 0; i < latestSample.length; i++) { for (var i = 0; i < latestSample.length; i++) {
// For now, we only use the array index as x value. // For now, we only use the array index as x value.
// TODO(tbosch): think about whether we should use time here instead // TODO(tbosch): think about whether we should use time here instead
xValues.push(i); xValues.push(i);
yValues.push(latestSample[i].values[this._metric]); yValues.push(latestSample[i].values[this._metric]);
} }
const regressionSlope = Statistic.calculateRegressionSlope( var regressionSlope = Statistic.calculateRegressionSlope(
xValues, Statistic.calculateMean(xValues), yValues, Statistic.calculateMean(yValues)); xValues, Statistic.calculateMean(xValues), yValues, Statistic.calculateMean(yValues));
return regressionSlope >= 0 ? latestSample : null; return regressionSlope >= 0 ? latestSample : null;
} else { } else {

View File

@ -8,9 +8,12 @@
import {Inject, Injectable, OpaqueToken} from '@angular/core'; import {Inject, Injectable, OpaqueToken} from '@angular/core';
import {ListWrapper} from '../facade/collection';
import {MeasureValues} from '../measure_values'; import {MeasureValues} from '../measure_values';
import {Validator} from '../validator'; import {Validator} from '../validator';
/** /**
* A validator that waits for the sample to have a certain size. * A validator that waits for the sample to have a certain size.
*/ */
@ -25,7 +28,8 @@ export class SizeValidator extends Validator {
validate(completeSample: MeasureValues[]): MeasureValues[] { validate(completeSample: MeasureValues[]): MeasureValues[] {
if (completeSample.length >= this._sampleSize) { if (completeSample.length >= this._sampleSize) {
return completeSample.slice(completeSample.length - this._sampleSize, completeSample.length); return ListWrapper.slice(
completeSample, completeSample.length - this._sampleSize, completeSample.length);
} else { } else {
return null; return null;
} }

View File

@ -62,7 +62,7 @@ export class IOsDriverExtension extends WebDriverExtension {
var startTime = record['startTime']; var startTime = record['startTime'];
var endTime = record['endTime']; var endTime = record['endTime'];
if (type === 'FunctionCall' && (data == null || data['scriptName'] !== 'InjectedScript')) { if (type === 'FunctionCall' && (isBlank(data) || data['scriptName'] !== 'InjectedScript')) {
events.push(createStartEvent('script', startTime)); events.push(createStartEvent('script', startTime));
endEvent = createEndEvent('script', endTime); endEvent = createEndEvent('script', endTime);
} else if (type === 'Time') { } else if (type === 'Time') {

View File

@ -28,7 +28,7 @@ export function main() {
if (!descriptions) { if (!descriptions) {
descriptions = []; descriptions = [];
} }
if (sampleId == null) { if (isBlank(sampleId)) {
sampleId = 'null'; sampleId = 'null';
} }
var providers: Provider[] = [ var providers: Provider[] = [

View File

@ -9,6 +9,7 @@
import {describe, expect, it} from '@angular/core/testing/testing_internal'; import {describe, expect, it} from '@angular/core/testing/testing_internal';
import {MeasureValues, ReflectiveInjector, RegressionSlopeValidator} from '../../index'; import {MeasureValues, ReflectiveInjector, RegressionSlopeValidator} from '../../index';
import {ListWrapper} from '../../src/facade/collection';
export function main() { export function main() {
describe('regression slope validator', () => { describe('regression slope validator', () => {
@ -43,15 +44,17 @@ export function main() {
it('should return the last sampleSize runs when the regression slope is ==0', () => { it('should return the last sampleSize runs when the regression slope is ==0', () => {
createValidator({size: 2, metric: 'script'}); createValidator({size: 2, metric: 'script'});
var sample = [mv(0, 0, {'script': 1}), mv(1, 1, {'script': 1}), mv(2, 2, {'script': 1})]; var sample = [mv(0, 0, {'script': 1}), mv(1, 1, {'script': 1}), mv(2, 2, {'script': 1})];
expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2)); expect(validator.validate(ListWrapper.slice(sample, 0, 2)))
expect(validator.validate(sample)).toEqual(sample.slice(1, 3)); .toEqual(ListWrapper.slice(sample, 0, 2));
expect(validator.validate(sample)).toEqual(ListWrapper.slice(sample, 1, 3));
}); });
it('should return the last sampleSize runs when the regression slope is >0', () => { it('should return the last sampleSize runs when the regression slope is >0', () => {
createValidator({size: 2, metric: 'script'}); createValidator({size: 2, metric: 'script'});
var sample = [mv(0, 0, {'script': 1}), mv(1, 1, {'script': 2}), mv(2, 2, {'script': 3})]; var sample = [mv(0, 0, {'script': 1}), mv(1, 1, {'script': 2}), mv(2, 2, {'script': 3})];
expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2)); expect(validator.validate(ListWrapper.slice(sample, 0, 2)))
expect(validator.validate(sample)).toEqual(sample.slice(1, 3)); .toEqual(ListWrapper.slice(sample, 0, 2));
expect(validator.validate(sample)).toEqual(ListWrapper.slice(sample, 1, 3));
}); });
}); });

View File

@ -9,6 +9,7 @@
import {describe, expect, it} from '@angular/core/testing/testing_internal'; import {describe, expect, it} from '@angular/core/testing/testing_internal';
import {MeasureValues, ReflectiveInjector, SizeValidator} from '../../index'; import {MeasureValues, ReflectiveInjector, SizeValidator} from '../../index';
import {ListWrapper} from '../../src/facade/collection';
export function main() { export function main() {
describe('size validator', () => { describe('size validator', () => {
@ -36,8 +37,9 @@ export function main() {
it('should return the last sampleSize runs when it has at least the given size', () => { it('should return the last sampleSize runs when it has at least the given size', () => {
createValidator(2); createValidator(2);
var sample = [mv(0, 0, {'a': 1}), mv(1, 1, {'b': 2}), mv(2, 2, {'c': 3})]; var sample = [mv(0, 0, {'a': 1}), mv(1, 1, {'b': 2}), mv(2, 2, {'c': 3})];
expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2)); expect(validator.validate(ListWrapper.slice(sample, 0, 2)))
expect(validator.validate(sample)).toEqual(sample.slice(1, 3)); .toEqual(ListWrapper.slice(sample, 0, 2));
expect(validator.validate(sample)).toEqual(ListWrapper.slice(sample, 1, 3));
}); });
}); });

View File

@ -11,6 +11,8 @@ import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableD
import {isListLikeIterable} from '../facade/collection'; import {isListLikeIterable} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
/** /**
* @ngModule CommonModule * @ngModule CommonModule
* *
@ -29,11 +31,11 @@ import {isPresent} from '../facade/lang';
* *
* @description * @description
* *
* The CSS classes are updated as follows, depending on the type of the expression evaluation: * The CSS classes are updated as follow depending on the type of the expression evaluation:
* - `string` - the CSS classes listed in the string (space delimited) are added, * - `string` - the CSS classes listed in a string (space delimited) are added,
* - `Array` - the CSS classes declared as Array elements are added, * - `Array` - the CSS classes (Array elements) are added,
* - `Object` - keys are CSS classes that get added when the expression given in the value * - `Object` - keys are CSS class names that get added when the expression given in the value
* evaluates to a truthy value, otherwise they are removed. * evaluates to a truthy value, otherwise class are removed.
* *
* @stable * @stable
*/ */
@ -48,6 +50,7 @@ export class NgClass implements DoCheck {
private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers, private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
private _ngEl: ElementRef, private _renderer: Renderer) {} private _ngEl: ElementRef, private _renderer: Renderer) {}
@Input('class') @Input('class')
set klass(v: string) { set klass(v: string) {
this._applyInitialClasses(true); this._applyInitialClasses(true);

View File

@ -33,21 +33,21 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
* - `'shortTime'`: equivalent to `'jm'` (e.g. `12:05 PM` for `en-US`) * - `'shortTime'`: equivalent to `'jm'` (e.g. `12:05 PM` for `en-US`)
* *
* *
* | Component | Symbol | Short Form | Long Form | Numeric | 2-digit | * | Component | Symbol | Narrow | Short Form | Long Form | Numeric | 2-digit |
* |-----------|:------:|--------------|-------------------|-----------|-----------| * |-----------|:------:|--------|--------------|-------------------|-----------|-----------|
* | era | G | G (AD) | GGGG (Anno Domini)| - | - | * | era | G | G (A) | GGG (AD) | GGGG (Anno Domini)| - | - |
* | year | y | - | - | y (2015) | yy (15) | * | year | y | - | - | - | y (2015) | yy (15) |
* | month | M | MMM (Sep) | MMMM (September) | M (9) | MM (09) | * | month | M | L (S) | MMM (Sep) | MMMM (September) | M (9) | MM (09) |
* | day | d | - | - | d (3) | dd (03) | * | day | d | - | - | - | d (3) | dd (03) |
* | weekday | E | EEE (Sun) | EEEE (Sunday) | - | - | * | weekday | E | E (S) | EEE (Sun) | EEEE (Sunday) | - | - |
* | hour | j | - | - | j (13) | jj (13) | * | hour | j | - | - | - | j (13) | jj (13) |
* | hour12 | h | - | - | h (1 PM) | hh (01 PM)| * | hour12 | h | - | - | - | h (1 PM) | hh (01 PM)|
* | hour24 | H | - | - | H (13) | HH (13) | * | hour24 | H | - | - | - | H (13) | HH (13) |
* | minute | m | - | - | m (5) | mm (05) | * | minute | m | - | - | - | m (5) | mm (05) |
* | second | s | - | - | s (9) | ss (09) | * | second | s | - | - | - | s (9) | ss (09) |
* | timezone | z | - | z (Pacific Standard Time)| - | - | * | timezone | z | - | - | z (Pacific Standard Time)| - | - |
* | timezone | Z | Z (GMT-8:00) | - | - | - | * | timezone | Z | - | Z (GMT-8:00) | - | - | - |
* | timezone | a | a (PM) | - | - | - | * | timezone | a | - | a (PM) | - | - | - |
* *
* In javascript, only the components specified will be respected (not the ordering, * In javascript, only the components specified will be respected (not the ordering,
* punctuations, ...) and details of the formatting will be dependent on the locale. * punctuations, ...) and details of the formatting will be dependent on the locale.

View File

@ -17,7 +17,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
* @howToUse `expression | lowercase` * @howToUse `expression | lowercase`
* @description * @description
* *
* Converts value into a lowercase string using `String.prototype.toLowerCase()`. * Converts value into lowercase string using `String.prototype.toLowerCase()`.
* *
* ### Example * ### Example
* *

View File

@ -16,7 +16,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
* @howToUse `expression | uppercase` * @howToUse `expression | uppercase`
* @description * @description
* *
* Converts value into an uppercase string using `String.prototype.toUpperCase()`. * Converts value into lowercase string using `String.prototype.toUpperCase()`.
* *
* ### Example * ### Example
* *

View File

@ -8,12 +8,13 @@
import {DatePipe} from '@angular/common'; import {DatePipe} from '@angular/common';
import {PipeResolver} from '@angular/compiler/src/pipe_resolver'; import {PipeResolver} from '@angular/compiler/src/pipe_resolver';
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
import {browserDetection} from '@angular/platform-browser/testing/browser_util'; import {browserDetection} from '@angular/platform-browser/testing/browser_util';
export function main() { export function main() {
describe('DatePipe', () => { describe('DatePipe', () => {
let date: Date; var date: Date;
let pipe: DatePipe; var pipe: DatePipe;
// TODO: reactivate the disabled expectations once emulators are fixed in SauceLabs // TODO: reactivate the disabled expectations once emulators are fixed in SauceLabs
// In some old versions of Chrome in Android emulators, time formatting returns dates in the // In some old versions of Chrome in Android emulators, time formatting returns dates in the
@ -33,9 +34,7 @@ export function main() {
describe('supports', () => { describe('supports', () => {
it('should support date', () => { expect(() => pipe.transform(date)).not.toThrow(); }); it('should support date', () => { expect(() => pipe.transform(date)).not.toThrow(); });
it('should support int', () => { expect(() => pipe.transform(123456789)).not.toThrow(); }); it('should support int', () => { expect(() => pipe.transform(123456789)).not.toThrow(); });
it('should support numeric strings', it('should support numeric strings',
() => { expect(() => pipe.transform('123456789')).not.toThrow(); }); () => { expect(() => pipe.transform('123456789')).not.toThrow(); });
@ -60,7 +59,7 @@ export function main() {
expect(pipe.transform(date, 'MMM')).toEqual('Jun'); expect(pipe.transform(date, 'MMM')).toEqual('Jun');
expect(pipe.transform(date, 'MMMM')).toEqual('June'); expect(pipe.transform(date, 'MMMM')).toEqual('June');
expect(pipe.transform(date, 'd')).toEqual('15'); expect(pipe.transform(date, 'd')).toEqual('15');
expect(pipe.transform(date, 'E')).toEqual('Mon'); expect(pipe.transform(date, 'EEE')).toEqual('Mon');
expect(pipe.transform(date, 'EEEE')).toEqual('Monday'); expect(pipe.transform(date, 'EEEE')).toEqual('Monday');
if (!browserDetection.isOldChrome) { if (!browserDetection.isOldChrome) {
expect(pipe.transform(date, 'h')).toEqual('9'); expect(pipe.transform(date, 'h')).toEqual('9');
@ -73,6 +72,9 @@ export function main() {
if (!browserDetection.isOldChrome) { if (!browserDetection.isOldChrome) {
expect(pipe.transform(date, 'HH')).toEqual('09'); expect(pipe.transform(date, 'HH')).toEqual('09');
} }
expect(pipe.transform(date, 'E')).toEqual('M');
expect(pipe.transform(date, 'L')).toEqual('J');
expect(pipe.transform(date, 'm')).toEqual('3'); expect(pipe.transform(date, 'm')).toEqual('3');
expect(pipe.transform(date, 's')).toEqual('1'); expect(pipe.transform(date, 's')).toEqual('1');
expect(pipe.transform(date, 'mm')).toEqual('03'); expect(pipe.transform(date, 'mm')).toEqual('03');
@ -82,13 +84,13 @@ export function main() {
}); });
it('should format common multi component patterns', () => { it('should format common multi component patterns', () => {
expect(pipe.transform(date, 'E, M/d/y')).toEqual('Mon, 6/15/2015'); expect(pipe.transform(date, 'EEE, M/d/y')).toEqual('Mon, 6/15/2015');
expect(pipe.transform(date, 'E, M/d')).toEqual('Mon, 6/15'); expect(pipe.transform(date, 'EEE, M/d')).toEqual('Mon, 6/15');
expect(pipe.transform(date, 'MMM d')).toEqual('Jun 15'); expect(pipe.transform(date, 'MMM d')).toEqual('Jun 15');
expect(pipe.transform(date, 'dd/MM/yyyy')).toEqual('15/06/2015'); expect(pipe.transform(date, 'dd/MM/yyyy')).toEqual('15/06/2015');
expect(pipe.transform(date, 'MM/dd/yyyy')).toEqual('06/15/2015'); expect(pipe.transform(date, 'MM/dd/yyyy')).toEqual('06/15/2015');
expect(pipe.transform(date, 'yMEd')).toEqual('20156Mon15'); expect(pipe.transform(date, 'yMEEEd')).toEqual('20156Mon15');
expect(pipe.transform(date, 'MEd')).toEqual('6Mon15'); expect(pipe.transform(date, 'MEEEd')).toEqual('6Mon15');
expect(pipe.transform(date, 'MMMd')).toEqual('Jun15'); expect(pipe.transform(date, 'MMMd')).toEqual('Jun15');
expect(pipe.transform(date, 'yMMMMEEEEd')).toEqual('Monday, June 15, 2015'); expect(pipe.transform(date, 'yMMMMEEEEd')).toEqual('Monday, June 15, 2015');
// IE and Edge can't format a date to minutes and seconds without hours // IE and Edge can't format a date to minutes and seconds without hours

View File

@ -27,7 +27,7 @@ Then you can add an import statement in the `bootstrap` allowing you to bootstra
generated code: generated code:
```typescript ```typescript
main.module.ts main_module.ts
------------- -------------
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {Component, NgModule, ApplicationRef} from '@angular/core'; import {Component, NgModule, ApplicationRef} from '@angular/core';
@ -49,7 +49,7 @@ export class MainModule {
bootstrap.ts bootstrap.ts
------------- -------------
import {MainModuleNgFactory} from './main.module.ngfactory'; import {MainModuleNgFactory} from './main_module.ngfactory';
import {platformBrowser} from '@angular/platform-browser'; import {platformBrowser} from '@angular/platform-browser';
platformBrowser().bootstrapModuleFactory(MainModuleNgFactory); platformBrowser().bootstrapModuleFactory(MainModuleNgFactory);

View File

@ -7,7 +7,6 @@
*/ */
export {CodeGenerator} from './src/codegen'; export {CodeGenerator} from './src/codegen';
export {Extractor} from './src/extractor';
export {NodeReflectorHostContext, ReflectorHost, ReflectorHostContext} from './src/reflector_host'; export {NodeReflectorHostContext, ReflectorHost, ReflectorHostContext} from './src/reflector_host';
export {StaticReflector, StaticReflectorHost, StaticSymbol} from './src/static_reflector'; export {StaticReflector, StaticReflectorHost, StaticSymbol} from './src/static_reflector';

View File

@ -1,18 +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 {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';
}

View File

@ -12,10 +12,6 @@
<source>Welcome</source> <source>Welcome</source>
<target>tervetuloa</target> <target>tervetuloa</target>
</trans-unit> </trans-unit>
<trans-unit id="63a85808f03b8181e36a952e0fa38202c2304862" datatype="html">
<source>other-3rdP-component</source>
<target>other-3rdP-component</target>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@ -9,5 +9,4 @@
<translationbundle> <translationbundle>
<translation id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4">käännä teksti</translation> <translation id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4">käännä teksti</translation>
<translation id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">tervetuloa</translation> <translation id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">tervetuloa</translation>
<translation id="63a85808f03b8181e36a952e0fa38202c2304862">other-3rdP-component</translation>
</translationbundle> </translationbundle>

View File

@ -11,12 +11,9 @@ import {FormsModule} from '@angular/forms';
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {MdButtonModule} from '@angular2-material/button'; import {MdButtonModule} from '@angular2-material/button';
import {ThirdpartyModule} from '../third_party_src/module';
import {MultipleComponentsMyComp, NextComp} from './a/multiple_components'; import {MultipleComponentsMyComp, NextComp} from './a/multiple_components';
import {AnimateCmp} from './animate'; import {AnimateCmp} from './animate';
import {BasicComp} from './basic'; import {BasicComp} from './basic';
import {ComponentUsingThirdParty} from './comp_using_3rdp';
import {CompWithAnalyzeEntryComponentsProvider, CompWithEntryComponents} from './entry_components'; import {CompWithAnalyzeEntryComponentsProvider, CompWithEntryComponents} from './entry_components';
import {CompConsumingEvents, CompUsingPipes, CompWithProviders, CompWithReferences, DirPublishingEvents, ModuleUsingCustomElements} from './features'; import {CompConsumingEvents, CompUsingPipes, CompWithProviders, CompWithReferences, DirPublishingEvents, ModuleUsingCustomElements} from './features';
import {CompUsingRootModuleDirectiveAndPipe, SomeDirectiveInRootModule, SomePipeInRootModule, SomeService, someLibModuleWithProviders} from './module_fixtures'; import {CompUsingRootModuleDirectiveAndPipe, SomeDirectiveInRootModule, SomePipeInRootModule, SomeService, someLibModuleWithProviders} from './module_fixtures';
@ -25,47 +22,35 @@ import {CompForChildQuery, CompWithChildQuery, CompWithDirectiveChild, Directive
@NgModule({ @NgModule({
declarations: [ declarations: [
AnimateCmp,
BasicComp,
CompConsumingEvents,
CompForChildQuery,
CompUsingPipes,
CompUsingRootModuleDirectiveAndPipe,
CompWithAnalyzeEntryComponentsProvider,
CompWithChildQuery,
CompWithDirectiveChild,
CompWithEntryComponents,
CompWithNgContent,
CompWithProviders,
CompWithReferences,
DirectiveForQuery,
DirPublishingEvents,
MultipleComponentsMyComp,
NextComp,
ProjectingComp,
SomeDirectiveInRootModule, SomeDirectiveInRootModule,
SomePipeInRootModule, SomePipeInRootModule,
ComponentUsingThirdParty, AnimateCmp,
BasicComp,
CompForChildQuery,
CompWithEntryComponents,
CompWithAnalyzeEntryComponentsProvider,
ProjectingComp,
CompWithChildQuery,
CompWithDirectiveChild,
CompWithNgContent,
CompUsingRootModuleDirectiveAndPipe,
CompWithProviders,
CompWithReferences,
CompUsingPipes,
CompConsumingEvents,
DirPublishingEvents,
MultipleComponentsMyComp,
DirectiveForQuery,
NextComp,
], ],
imports: [ imports: [
BrowserModule, BrowserModule, FormsModule, someLibModuleWithProviders(), ModuleUsingCustomElements,
FormsModule, MdButtonModule
MdButtonModule,
ModuleUsingCustomElements,
someLibModuleWithProviders(),
ThirdpartyModule,
], ],
providers: [SomeService], providers: [SomeService],
entryComponents: [ entryComponents: [
AnimateCmp, AnimateCmp, BasicComp, CompWithEntryComponents, CompWithAnalyzeEntryComponentsProvider,
BasicComp, ProjectingComp, CompWithChildQuery, CompUsingRootModuleDirectiveAndPipe, CompWithReferences
CompUsingRootModuleDirectiveAndPipe,
CompWithAnalyzeEntryComponentsProvider,
CompWithChildQuery,
CompWithEntryComponents,
CompWithReferences,
ProjectingComp,
ComponentUsingThirdParty,
] ]
}) })
export class MainModule { export class MainModule {

View File

@ -43,13 +43,13 @@ describe('template codegen output', () => {
}); });
it('should be able to create the basic component', () => { it('should be able to create the basic component', () => {
const compFixture = createComponent(BasicComp); var compFixture = createComponent(BasicComp);
expect(compFixture.componentInstance).toBeTruthy(); expect(compFixture.componentInstance).toBeTruthy();
}); });
it('should support ngIf', () => { it('should support ngIf', () => {
const compFixture = createComponent(BasicComp); var compFixture = createComponent(BasicComp);
const debugElement = compFixture.debugElement; var debugElement = compFixture.debugElement;
expect(debugElement.children.length).toBe(3); expect(debugElement.children.length).toBe(3);
compFixture.componentInstance.ctxBool = true; compFixture.componentInstance.ctxBool = true;
@ -59,8 +59,8 @@ describe('template codegen output', () => {
}); });
it('should support ngFor', () => { it('should support ngFor', () => {
const compFixture = createComponent(BasicComp); var compFixture = createComponent(BasicComp);
const debugElement = compFixture.debugElement; var debugElement = compFixture.debugElement;
expect(debugElement.children.length).toBe(3); expect(debugElement.children.length).toBe(3);
// test NgFor // test NgFor
@ -83,9 +83,11 @@ describe('template codegen output', () => {
}); });
it('should support i18n for content tags', () => { it('should support i18n for content tags', () => {
const containerElement = createComponent(BasicComp).nativeElement; const compFixture = createComponent(BasicComp);
const pElement = containerElement.children.find((c: any) => c.name == 'p'); const debugElement = compFixture.debugElement;
const pText = pElement.children.map((c: any) => c.data).join('').trim(); 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();
expect(pText).toBe('tervetuloa'); expect(pText).toBe('tervetuloa');
}); });
}); });

View File

@ -34,7 +34,6 @@ const EXPECTED_XMB = `<?xml version="1.0" encoding="UTF-8" ?>
<!ELEMENT ex (#PCDATA)> <!ELEMENT ex (#PCDATA)>
]> ]>
<messagebundle> <messagebundle>
<msg id="63a85808f03b8181e36a952e0fa38202c2304862">other-3rdP-component</msg>
<msg id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" desc="desc" meaning="meaning">translate me</msg> <msg id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" desc="desc" meaning="meaning">translate me</msg>
<msg id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">Welcome</msg> <msg id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">Welcome</msg>
</messagebundle> </messagebundle>
@ -44,10 +43,6 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="ng2.template"> <file source-language="en" datatype="plaintext" original="ng2.template">
<body> <body>
<trans-unit id="63a85808f03b8181e36a952e0fa38202c2304862" datatype="html">
<source>other-3rdP-component</source>
<target/>
</trans-unit>
<trans-unit id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" datatype="html"> <trans-unit id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" datatype="html">
<source>translate me</source> <source>translate me</source>
<target/> <target/>

View File

@ -7,7 +7,6 @@
*/ */
import './init'; import './init';
import {ComponentUsingThirdParty} from '../src/comp_using_3rdp';
import {MainModule} from '../src/module'; import {MainModule} from '../src/module';
import {CompUsingLibModuleDirectiveAndPipe, CompUsingRootModuleDirectiveAndPipe, SOME_TOKEN, ServiceUsingLibModule, SomeLibModule, SomeService} from '../src/module_fixtures'; import {CompUsingLibModuleDirectiveAndPipe, CompUsingRootModuleDirectiveAndPipe, SOME_TOKEN, ServiceUsingLibModule, SomeLibModule, SomeService} from '../src/module_fixtures';
@ -16,9 +15,9 @@ import {createComponent, createModule} from './util';
describe('NgModule', () => { describe('NgModule', () => {
it('should support providers', () => { it('should support providers', () => {
const moduleRef = createModule(); const moduleRef = createModule();
expect(moduleRef.instance instanceof MainModule).toEqual(true); expect(moduleRef.instance instanceof MainModule).toBe(true);
expect(moduleRef.injector.get(MainModule) instanceof MainModule).toEqual(true); expect(moduleRef.injector.get(MainModule) instanceof MainModule).toBe(true);
expect(moduleRef.injector.get(SomeService) instanceof SomeService).toEqual(true); expect(moduleRef.injector.get(SomeService) instanceof SomeService).toBe(true);
}); });
it('should support entryComponents components', () => { it('should support entryComponents components', () => {
@ -27,7 +26,7 @@ describe('NgModule', () => {
CompUsingRootModuleDirectiveAndPipe); CompUsingRootModuleDirectiveAndPipe);
expect(cf.componentType).toBe(CompUsingRootModuleDirectiveAndPipe); expect(cf.componentType).toBe(CompUsingRootModuleDirectiveAndPipe);
const compRef = cf.create(moduleRef.injector); const compRef = cf.create(moduleRef.injector);
expect(compRef.instance instanceof CompUsingRootModuleDirectiveAndPipe).toEqual(true); expect(compRef.instance instanceof CompUsingRootModuleDirectiveAndPipe).toBe(true);
}); });
it('should support entryComponents via the ANALYZE_FOR_ENTRY_COMPONENTS provider and function providers in components', it('should support entryComponents via the ANALYZE_FOR_ENTRY_COMPONENTS provider and function providers in components',
@ -43,30 +42,12 @@ 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', () => { it('should support module directives and pipes', () => {
const compFixture = createComponent(CompUsingRootModuleDirectiveAndPipe); const compFixture = createComponent(CompUsingRootModuleDirectiveAndPipe);
compFixture.detectChanges(); compFixture.detectChanges();
const debugElement = compFixture.debugElement; const debugElement = compFixture.debugElement;
expect(debugElement.children[0].properties['title']).toEqual('transformed someValue'); expect(debugElement.children[0].properties['title']).toBe('transformed someValue');
}); });
it('should support module directives and pipes on lib modules', () => { it('should support module directives and pipes on lib modules', () => {
@ -74,10 +55,10 @@ describe('NgModule', () => {
compFixture.detectChanges(); compFixture.detectChanges();
const debugElement = compFixture.debugElement; const debugElement = compFixture.debugElement;
expect(debugElement.children[0].properties['title']).toEqual('transformed someValue'); expect(debugElement.children[0].properties['title']).toBe('transformed someValue');
expect(debugElement.injector.get(SomeLibModule) instanceof SomeLibModule).toEqual(true); expect(debugElement.injector.get(SomeLibModule) instanceof SomeLibModule).toBe(true);
expect(debugElement.injector.get(ServiceUsingLibModule) instanceof ServiceUsingLibModule) expect(debugElement.injector.get(ServiceUsingLibModule) instanceof ServiceUsingLibModule)
.toEqual(true); .toBe(true);
}); });
}); });

View File

@ -1,8 +0,0 @@
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.

View File

@ -1,16 +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 {Component} from '@angular/core';
@Component({
selector: 'third-party-comp',
template: '<div>3rdP-component</div>',
})
export class ThirdPartyComponent {
}

View File

@ -1,17 +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 {Directive, Input} from '@angular/core';
@Directive({
selector: '[thirdParty]',
host: {'[title]': 'thirdParty'},
})
export class ThirdPartyDirective {
@Input() thirdParty: string;
}

View File

@ -1,28 +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 {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 {
}

View File

@ -1,16 +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 {Component} from '@angular/core';
@Component({
selector: 'another-third-party-comp',
template: '<div i18n>other-3rdP-component</div>',
})
export class AnotherThirdpartyComponent {
}

View File

@ -1,17 +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 {NgModule} from '@angular/core';
import {AnotherThirdpartyComponent} from './other_comp';
@NgModule({
declarations: [AnotherThirdpartyComponent],
exports: [AnotherThirdpartyComponent],
})
export class AnotherThirdPartyModule {
}

View File

@ -1,20 +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": ".",
"outDir": "../node_modules/third_party"
}
}

View File

@ -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"
]
}

View File

@ -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"
]
}

View File

@ -11,7 +11,7 @@
* Intended to be used in a build step. * Intended to be used in a build step.
*/ */
import * as compiler from '@angular/compiler'; import * as compiler from '@angular/compiler';
import {ViewEncapsulation} from '@angular/core'; import {Directive, NgModule, ViewEncapsulation} from '@angular/core';
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped'; import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
@ -35,16 +35,73 @@ 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 { export class CodeGenerator {
private moduleCollector: CodeGeneratorModuleCollector;
constructor( constructor(
private options: AngularCompilerOptions, private program: ts.Program, private options: AngularCompilerOptions, private program: ts.Program,
public host: ts.CompilerHost, private staticReflector: StaticReflector, public host: ts.CompilerHost, private staticReflector: StaticReflector,
private compiler: compiler.OfflineCompiler, private reflectorHost: StaticReflectorHost) {} private compiler: compiler.OfflineCompiler, private reflectorHost: StaticReflectorHost) {
this.moduleCollector =
new CodeGeneratorModuleCollector(staticReflector, reflectorHost, program, options);
}
// Write codegen in a directory structure matching the sources. // Write codegen in a directory structure matching the sources.
private calculateEmitPath(filePath: string): string { private calculateEmitPath(filePath: string): string {
let root = this.options.basePath; let root = this.options.basePath;
for (const eachRootDir of this.options.rootDirs || []) { for (let eachRootDir of this.options.rootDirs || []) {
if (this.options.trace) { if (this.options.trace) {
console.log(`Check if ${filePath} is under rootDirs element ${eachRootDir}`); console.log(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
} }
@ -54,28 +111,31 @@ export class CodeGenerator {
} }
// transplant the codegen path to be inside the `genDir` // transplant the codegen path to be inside the `genDir`
let relativePath: string = path.relative(root, filePath); var relativePath: string = path.relative(root, filePath);
while (relativePath.startsWith('..' + path.sep)) { while (relativePath.startsWith('..' + path.sep)) {
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything // Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
// into `genDir`. // into `genDir`.
relativePath = relativePath.substr(3); relativePath = relativePath.substr(3);
} }
return path.join(this.options.genDir, relativePath); return path.join(this.options.genDir, relativePath);
} }
codegen(options: {transitiveModules: boolean}): Promise<any> { codegen(): Promise<any> {
const staticSymbols = const {fileMetas, ngModules} = this.moduleCollector.getModuleSymbols(this.program);
extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options); const analyzedNgModules = this.compiler.analyzeModules(ngModules);
return Promise.all(fileMetas.map(
return this.compiler.compileModules(staticSymbols, options).then(generatedModules => { (fileMeta) =>
generatedModules.forEach(generatedModule => { this.compiler
const sourceFile = this.program.getSourceFile(generatedModule.fileUrl); .compile(
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl); fileMeta.fileUrl, analyzedNgModules, fileMeta.directives, fileMeta.ngModules)
this.host.writeFile( .then((generatedModules) => {
emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]); 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]);
});
})));
} }
static create( static create(
@ -133,9 +193,7 @@ export class CodeGenerator {
// TODO(vicb): do not pass cliOptions.i18nFormat here // TODO(vicb): do not pass cliOptions.i18nFormat here
const offlineCompiler = new compiler.OfflineCompiler( const offlineCompiler = new compiler.OfflineCompiler(
resolver, normalizer, tmplParser, new compiler.StyleCompiler(urlResolver), resolver, normalizer, tmplParser, new compiler.StyleCompiler(urlResolver),
new compiler.ViewCompiler(config, elementSchemaRegistry), new compiler.ViewCompiler(config), new compiler.DirectiveWrapperCompiler(config),
new compiler.DirectiveWrapperCompiler(
config, expressionParser, elementSchemaRegistry, console),
new compiler.NgModuleCompiler(), new compiler.TypeScriptEmitter(reflectorHost), new compiler.NgModuleCompiler(), new compiler.TypeScriptEmitter(reflectorHost),
cliOptions.locale, cliOptions.i18nFormat); cliOptions.locale, cliOptions.i18nFormat);
@ -144,40 +202,8 @@ export class CodeGenerator {
} }
} }
export function extractProgramSymbols( export interface FileMetadata {
program: ts.Program, staticReflector: StaticReflector, reflectorHost: StaticReflectorHost, fileUrl: string;
options: AngularCompilerOptions): StaticSymbol[] { directives: StaticSymbol[];
// Compare with false since the default should be true ngModules: StaticSymbol[];
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;
}

View File

@ -10,32 +10,27 @@
/** /**
* Extract i18n messages from source code * Extract i18n messages from source code
*
* TODO(vicb): factorize code with the CodeGenerator
*/ */
// Must be imported first, because angular2 decorators throws on load. // Must be imported first, because angular2 decorators throws on load.
import 'reflect-metadata'; import 'reflect-metadata';
import * as compiler from '@angular/compiler'; import * as compiler from '@angular/compiler';
import * as tsc from '@angular/tsc-wrapped'; import {Component, NgModule, ViewEncapsulation} from '@angular/core';
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import * as tsc from '@angular/tsc-wrapped';
import {Extractor} from './extractor'; import {Console} from './private_import_core';
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector, StaticSymbol} from './static_reflector';
function extract( function extract(
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions, ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions,
program: ts.Program, host: ts.CompilerHost) { program: ts.Program, host: ts.CompilerHost) {
const resourceLoader: compiler.ResourceLoader = { const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser());
get: (s: string) => { const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host, htmlParser);
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(); const bundlePromise: Promise<compiler.MessageBundle> = extractor.extract();
return (bundlePromise).then(messageBundle => { return (bundlePromise).then(messageBundle => {
@ -51,7 +46,6 @@ function extract(
case 'xliff': case 'xliff':
case 'xlf': case 'xlf':
default: default:
const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser());
ext = 'xlf'; ext = 'xlf';
serializer = new compiler.Xliff(htmlParser, compiler.DEFAULT_INTERPOLATION_CONFIG); serializer = new compiler.Xliff(htmlParser, compiler.DEFAULT_INTERPOLATION_CONFIG);
break; break;
@ -62,6 +56,139 @@ 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 // Entry point
if (require.main === module) { if (require.main === module) {
const args = require('minimist')(process.argv.slice(2)); const args = require('minimist')(process.argv.slice(2));

View File

@ -1,110 +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
*/
/**
* 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);
}
}

View File

@ -19,9 +19,7 @@ import {CodeGenerator} from './codegen';
function codegen( function codegen(
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program, ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program,
host: ts.CompilerHost) { host: ts.CompilerHost) {
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen({ return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen();
transitiveModules: true
});
} }
// CLI entry point // CLI entry point

View File

@ -250,12 +250,6 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
} else { } else {
const sf = this.program.getSourceFile(filePath); const sf = this.program.getSourceFile(filePath);
if (!sf) { 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.`); throw new Error(`Source file ${filePath} not present in program.`);
} }
return this.metadataCollector.getMetadata(sf); return this.metadataCollector.getMetadata(sf);

View File

@ -25,7 +25,7 @@ export interface StaticReflectorHost {
* @param modulePath is a string identifier for a module as an absolute path. * @param modulePath is a string identifier for a module as an absolute path.
* @returns the metadata for the given module. * @returns the metadata for the given module.
*/ */
getMetadataFor(modulePath: string): {[key: string]: any}|{[key: string]: any}[]; getMetadataFor(modulePath: string): {[key: string]: any};
/** /**
* Resolve a symbol from an import statement form, to the file where it is declared. * Resolve a symbol from an import statement form, to the file where it is declared.
@ -72,12 +72,13 @@ export class StaticReflector implements ReflectorReader {
constructor(private host: StaticReflectorHost) { this.initializeConversionMap(); } constructor(private host: StaticReflectorHost) { this.initializeConversionMap(); }
importUri(typeOrFunc: StaticSymbol): string { importUri(typeOrFunc: StaticSymbol): string {
const staticSymbol = this.host.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, ''); var staticSymbol = this.host.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, '');
return staticSymbol ? staticSymbol.filePath : null; return staticSymbol ? staticSymbol.filePath : null;
} }
resolveIdentifier(name: string, moduleUrl: string, runtime: any): any { resolveIdentifier(name: string, moduleUrl: string, runtime: any): any {
return this.host.findDeclaration(moduleUrl, name, ''); const result = this.host.findDeclaration(moduleUrl, name, '');
return result;
} }
resolveEnum(enumIdentifier: any, name: string): any { resolveEnum(enumIdentifier: any, name: string): any {
@ -237,9 +238,9 @@ export class StaticReflector implements ReflectorReader {
/** @internal */ /** @internal */
public simplify(context: StaticSymbol, value: any): any { public simplify(context: StaticSymbol, value: any): any {
const _this = this; let _this = this;
let scope = BindingScope.empty; let scope = BindingScope.empty;
const calling = new Map<StaticSymbol, boolean>(); let calling = new Map<StaticSymbol, boolean>();
function simplifyInContext(context: StaticSymbol, value: any, depth: number): any { function simplifyInContext(context: StaticSymbol, value: any, depth: number): any {
function resolveReference(context: StaticSymbol, expression: any): StaticSymbol { function resolveReference(context: StaticSymbol, expression: any): StaticSymbol {
@ -254,15 +255,16 @@ export class StaticReflector implements ReflectorReader {
} }
function resolveReferenceValue(staticSymbol: StaticSymbol): any { function resolveReferenceValue(staticSymbol: StaticSymbol): any {
const moduleMetadata = _this.getModuleMetadata(staticSymbol.filePath); let result: any = staticSymbol;
const declarationValue = let moduleMetadata = _this.getModuleMetadata(staticSymbol.filePath);
let declarationValue =
moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null; moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null;
return declarationValue; return declarationValue;
} }
function isOpaqueToken(context: StaticSymbol, value: any): boolean { function isOpaqueToken(context: StaticSymbol, value: any): boolean {
if (value && value.__symbolic === 'new' && value.expression) { if (value && value.__symbolic === 'new' && value.expression) {
const target = value.expression; let target = value.expression;
if (target.__symbolic == 'reference') { if (target.__symbolic == 'reference') {
return sameSymbol(resolveReference(context, target), _this.opaqueToken); return sameSymbol(resolveReference(context, target), _this.opaqueToken);
} }
@ -533,7 +535,7 @@ export class StaticReflector implements ReflectorReader {
} }
} }
const result = simplifyInContext(context, value, 0); let result = simplifyInContext(context, value, 0);
if (shouldIgnore(result)) { if (shouldIgnore(result)) {
return undefined; return undefined;
} }
@ -548,7 +550,8 @@ export class StaticReflector implements ReflectorReader {
if (!moduleMetadata) { if (!moduleMetadata) {
moduleMetadata = this.host.getMetadataFor(module); moduleMetadata = this.host.getMetadataFor(module);
if (Array.isArray(moduleMetadata)) { if (Array.isArray(moduleMetadata)) {
moduleMetadata = moduleMetadata.find(md => md['version'] === SUPPORTED_SCHEMA_VERSION) || moduleMetadata = (<Array<any>>moduleMetadata)
.find(element => element.version === SUPPORTED_SCHEMA_VERSION) ||
moduleMetadata[0]; moduleMetadata[0];
} }
if (!moduleMetadata) { if (!moduleMetadata) {
@ -565,8 +568,12 @@ export class StaticReflector implements ReflectorReader {
} }
private getTypeMetadata(type: StaticSymbol): {[key: string]: any} { private getTypeMetadata(type: StaticSymbol): {[key: string]: any} {
const moduleMetadata = this.getModuleMetadata(type.filePath); let moduleMetadata = this.getModuleMetadata(type.filePath);
return moduleMetadata['metadata'][type.name] || {__symbolic: 'class'}; let result = moduleMetadata['metadata'][type.name];
if (!result) {
result = {__symbolic: 'class'};
}
return result;
} }
} }
@ -606,7 +613,7 @@ function produceErrorMessage(error: any): string {
function mapStringMap(input: {[key: string]: any}, transform: (value: any, key: string) => any): function mapStringMap(input: {[key: string]: any}, transform: (value: any, key: string) => any):
{[key: string]: any} { {[key: string]: any} {
if (!input) return {}; if (!input) return {};
const result: {[key: string]: any} = {}; var result: {[key: string]: any} = {};
Object.keys(input).forEach((key) => { Object.keys(input).forEach((key) => {
let value = transform(input[key], key); let value = transform(input[key], key);
if (!shouldIgnore(value)) { if (!shouldIgnore(value)) {
@ -631,7 +638,8 @@ abstract class BindingScope {
public static empty: BindingScope = {resolve: name => BindingScope.missing}; public static empty: BindingScope = {resolve: name => BindingScope.missing};
public static build(): BindingScopeBuilder { public static build(): BindingScopeBuilder {
const current = new Map<string, any>(); let current = new Map<string, any>();
let parent: BindingScope = undefined;
return { return {
define: function(name, value) { define: function(name, value) {
current.set(name, value); current.set(name, value);

View File

@ -8,6 +8,7 @@
import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler-cli/src/static_reflector'; 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 {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 {MetadataCollector} from '@angular/tsc-wrapped';
import * as ts from 'typescript'; import * as ts from 'typescript';
@ -473,7 +474,7 @@ class MockReflectorHost implements StaticReflectorHost {
function resolvePath(pathParts: string[]): string { function resolvePath(pathParts: string[]): string {
let result: string[] = []; let result: string[] = [];
pathParts.forEach((part, index) => { ListWrapper.forEachWithIndex(pathParts, (part, index) => {
switch (part) { switch (part) {
case '': case '':
case '.': case '.':

View File

@ -34,8 +34,9 @@ export {DirectiveResolver} from './src/directive_resolver';
export {PipeResolver} from './src/pipe_resolver'; export {PipeResolver} from './src/pipe_resolver';
export {NgModuleResolver} from './src/ng_module_resolver'; export {NgModuleResolver} from './src/ng_module_resolver';
export {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './src/ml_parser/interpolation_config'; export {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './src/ml_parser/interpolation_config';
export * from './src/schema/element_schema_registry'; export {ElementSchemaRegistry} from './src/schema/element_schema_registry';
export * from './src/i18n/index'; export * from './src/i18n/index';
export * from './src/template_parser/template_ast';
export * from './src/directive_normalizer'; export * from './src/directive_normalizer';
export * from './src/expression_parser/lexer'; export * from './src/expression_parser/lexer';
export * from './src/expression_parser/parser'; export * from './src/expression_parser/parser';

View File

@ -7,7 +7,7 @@
*/ */
import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata} from '../compile_metadata'; import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata} from '../compile_metadata';
import {StringMapWrapper} from '../facade/collection'; import {ListWrapper, StringMapWrapper} from '../facade/collection';
import {isBlank, isPresent} from '../facade/lang'; import {isBlank, isPresent} from '../facade/lang';
import {ParseError} from '../parse_util'; import {ParseError} from '../parse_util';
import {ANY_STATE, FILL_STYLE_FLAG} from '../private_import_core'; import {ANY_STATE, FILL_STYLE_FLAG} from '../private_import_core';
@ -180,7 +180,8 @@ function _normalizeStyleMetadata(
var normalizedStyles: {[key: string]: string | number}[] = []; var normalizedStyles: {[key: string]: string | number}[] = [];
entry.styles.forEach(styleEntry => { entry.styles.forEach(styleEntry => {
if (typeof styleEntry === 'string') { if (typeof styleEntry === 'string') {
normalizedStyles.push(..._resolveStylesFromState(<string>styleEntry, stateStyles, errors)); ListWrapper.addAll(
normalizedStyles, _resolveStylesFromState(<string>styleEntry, stateStyles, errors));
} else { } else {
normalizedStyles.push(<{[key: string]: string | number}>styleEntry); normalizedStyles.push(<{[key: string]: string | number}>styleEntry);
} }
@ -345,12 +346,12 @@ function _parseAnimationKeyframes(
}); });
if (doSortKeyframes) { if (doSortKeyframes) {
rawKeyframes.sort((a, b) => a[0] <= b[0] ? -1 : 1); ListWrapper.sort(rawKeyframes, (a, b) => a[0] <= b[0] ? -1 : 1);
} }
var firstKeyframe = rawKeyframes[0]; var firstKeyframe = rawKeyframes[0];
if (firstKeyframe[0] != _INITIAL_KEYFRAME) { if (firstKeyframe[0] != _INITIAL_KEYFRAME) {
rawKeyframes.splice(0, 0, firstKeyframe = [_INITIAL_KEYFRAME, {}]); ListWrapper.insert(rawKeyframes, 0, firstKeyframe = [_INITIAL_KEYFRAME, {}]);
} }
var firstKeyframeStyles = firstKeyframe[1]; var firstKeyframeStyles = firstKeyframe[1];
@ -420,7 +421,7 @@ function _parseTransitionAnimation(
steps.push(new AnimationStepAst(startingStyles, [], 0, 0, '')); steps.push(new AnimationStepAst(startingStyles, [], 0, 0, ''));
} else { } else {
var innerStep = <AnimationStepAst>innerAst; var innerStep = <AnimationStepAst>innerAst;
innerStep.startingStyles.styles.push(...previousStyles); ListWrapper.addAll(innerStep.startingStyles.styles, previousStyles);
} }
previousStyles = null; previousStyles = null;
} }

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ListWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
export class StylesCollectionEntry { export class StylesCollectionEntry {
@ -36,7 +37,7 @@ export class StylesCollection {
} }
} }
entries.splice(insertionIndex, 0, tuple); ListWrapper.insert(entries, insertionIndex, tuple);
} }
getByIndex(property: string, index: number): StylesCollectionEntry { getByIndex(property: string, index: number): StylesCollectionEntry {

View File

@ -9,7 +9,7 @@
import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core'; import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core';
import {ListWrapper, MapWrapper} from './facade/collection'; import {ListWrapper, MapWrapper} from './facade/collection';
import {isPresent} from './facade/lang'; import {isPresent, normalizeBlank, normalizeBool} from './facade/lang';
import {LifecycleHooks} from './private_import_core'; import {LifecycleHooks} from './private_import_core';
import {CssSelector} from './selector'; import {CssSelector} from './selector';
import {sanitizeIdentifier, splitAtColon} from './util'; import {sanitizeIdentifier, splitAtColon} from './util';
@ -23,6 +23,7 @@ function unimplemented(): any {
// group 2: "event" from "(event)" // group 2: "event" from "(event)"
// group 3: "@trigger" from "@trigger" // group 3: "@trigger" from "@trigger"
const HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/; const HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/;
const UNDEFINED = new Object();
export abstract class CompileMetadataWithIdentifier { export abstract class CompileMetadataWithIdentifier {
get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); } get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); }
@ -105,27 +106,33 @@ export class CompileDiDependencyMetadata {
isSkipSelf: boolean; isSkipSelf: boolean;
isOptional: boolean; isOptional: boolean;
isValue: boolean; isValue: boolean;
query: CompileQueryMetadata;
viewQuery: CompileQueryMetadata;
token: CompileTokenMetadata; token: CompileTokenMetadata;
value: any; value: any;
constructor({isAttribute, isSelf, isHost, isSkipSelf, isOptional, isValue, token, value}: { constructor(
isAttribute?: boolean, {isAttribute, isSelf, isHost, isSkipSelf, isOptional, isValue, query, viewQuery, token,
isSelf?: boolean, value}: {
isHost?: boolean, isAttribute?: boolean,
isSkipSelf?: boolean, isSelf?: boolean,
isOptional?: boolean, isHost?: boolean,
isValue?: boolean, isSkipSelf?: boolean,
query?: CompileQueryMetadata, isOptional?: boolean,
viewQuery?: CompileQueryMetadata, isValue?: boolean,
token?: CompileTokenMetadata, query?: CompileQueryMetadata,
value?: any viewQuery?: CompileQueryMetadata,
} = {}) { token?: CompileTokenMetadata,
this.isAttribute = !!isAttribute; value?: any
this.isSelf = !!isSelf; } = {}) {
this.isHost = !!isHost; this.isAttribute = normalizeBool(isAttribute);
this.isSkipSelf = !!isSkipSelf; this.isSelf = normalizeBool(isSelf);
this.isOptional = !!isOptional; this.isHost = normalizeBool(isHost);
this.isValue = !!isValue; this.isSkipSelf = normalizeBool(isSkipSelf);
this.isOptional = normalizeBool(isOptional);
this.isValue = normalizeBool(isValue);
this.query = query;
this.viewQuery = viewQuery;
this.token = token; this.token = token;
this.value = value; this.value = value;
} }
@ -154,8 +161,8 @@ export class CompileProviderMetadata {
this.useValue = useValue; this.useValue = useValue;
this.useExisting = useExisting; this.useExisting = useExisting;
this.useFactory = useFactory; this.useFactory = useFactory;
this.deps = deps || null; this.deps = normalizeBlank(deps);
this.multi = !!multi; this.multi = normalizeBool(multi);
} }
} }
@ -185,7 +192,7 @@ export class CompileTokenMetadata implements CompileMetadataWithIdentifier {
{value?: any, identifier?: CompileIdentifierMetadata, identifierIsInstance?: boolean}) { {value?: any, identifier?: CompileIdentifierMetadata, identifierIsInstance?: boolean}) {
this.value = value; this.value = value;
this.identifier = identifier; this.identifier = identifier;
this.identifierIsInstance = !!identifierIsInstance; this.identifierIsInstance = normalizeBool(identifierIsInstance);
} }
get reference(): any { get reference(): any {
@ -220,7 +227,7 @@ export class CompileTypeMetadata extends CompileIdentifierMetadata {
lifecycleHooks?: LifecycleHooks[]; lifecycleHooks?: LifecycleHooks[];
} = {}) { } = {}) {
super({reference: reference, name: name, moduleUrl: moduleUrl, prefix: prefix, value: value}); super({reference: reference, name: name, moduleUrl: moduleUrl, prefix: prefix, value: value});
this.isHost = !!isHost; this.isHost = normalizeBool(isHost);
this.diDeps = _normalizeArray(diDeps); this.diDeps = _normalizeArray(diDeps);
this.lifecycleHooks = _normalizeArray(lifecycleHooks); this.lifecycleHooks = _normalizeArray(lifecycleHooks);
} }
@ -241,8 +248,8 @@ export class CompileQueryMetadata {
read?: CompileTokenMetadata read?: CompileTokenMetadata
} = {}) { } = {}) {
this.selectors = selectors; this.selectors = selectors;
this.descendants = !!descendants; this.descendants = normalizeBool(descendants);
this.first = !!first; this.first = normalizeBool(first);
this.propertyName = propertyName; this.propertyName = propertyName;
this.read = read; this.read = read;
} }
@ -296,9 +303,9 @@ export class CompileTemplateMetadata {
this.styles = _normalizeArray(styles); this.styles = _normalizeArray(styles);
this.styleUrls = _normalizeArray(styleUrls); this.styleUrls = _normalizeArray(styleUrls);
this.externalStylesheets = _normalizeArray(externalStylesheets); this.externalStylesheets = _normalizeArray(externalStylesheets);
this.animations = animations ? ListWrapper.flatten(animations) : []; this.animations = isPresent(animations) ? ListWrapper.flatten(animations) : [];
this.ngContentSelectors = ngContentSelectors || []; this.ngContentSelectors = ngContentSelectors || [];
if (interpolation && interpolation.length != 2) { if (isPresent(interpolation) && interpolation.length != 2) {
throw new Error(`'interpolation' should have a start and an end symbol.`); throw new Error(`'interpolation' should have a start and an end symbol.`);
} }
this.interpolation = interpolation; this.interpolation = interpolation;
@ -368,7 +375,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
return new CompileDirectiveMetadata({ return new CompileDirectiveMetadata({
type, type,
isComponent: !!isComponent, selector, exportAs, changeDetection, isComponent: normalizeBool(isComponent), selector, exportAs, changeDetection,
inputs: inputsMap, inputs: inputsMap,
outputs: outputsMap, outputs: outputsMap,
hostListeners, hostListeners,
@ -496,13 +503,13 @@ export class CompilePipeMetadata implements CompileMetadataWithIdentifier {
} = {}) { } = {}) {
this.type = type; this.type = type;
this.name = name; this.name = name;
this.pure = !!pure; this.pure = normalizeBool(pure);
} }
get identifier(): CompileIdentifierMetadata { return this.type; } get identifier(): CompileIdentifierMetadata { return this.type; }
} }
/** /**
* Metadata regarding compilation of a module. * Metadata regarding compilation of a directive.
*/ */
export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier { export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
type: CompileTypeMetadata; type: CompileTypeMetadata;
@ -562,7 +569,6 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
export class TransitiveCompileNgModuleMetadata { export class TransitiveCompileNgModuleMetadata {
directivesSet = new Set<Type<any>>(); directivesSet = new Set<Type<any>>();
pipesSet = new Set<Type<any>>(); pipesSet = new Set<Type<any>>();
constructor( constructor(
public modules: CompileNgModuleMetadata[], public providers: CompileProviderMetadata[], public modules: CompileNgModuleMetadata[], public providers: CompileProviderMetadata[],
public entryComponents: CompileTypeMetadata[], public directives: CompileDirectiveMetadata[], public entryComponents: CompileTypeMetadata[], public directives: CompileDirectiveMetadata[],
@ -575,13 +581,11 @@ export class TransitiveCompileNgModuleMetadata {
export function removeIdentifierDuplicates<T extends CompileMetadataWithIdentifier>(items: T[]): export function removeIdentifierDuplicates<T extends CompileMetadataWithIdentifier>(items: T[]):
T[] { T[] {
const map = new Map<any, T>(); const map = new Map<any, T>();
items.forEach((item) => { items.forEach((item) => {
if (!map.get(item.identifier.reference)) { if (!map.get(item.identifier.reference)) {
map.set(item.identifier.reference, item); map.set(item.identifier.reference, item);
} }
}); });
return MapWrapper.values(map); return MapWrapper.values(map);
} }

View File

@ -1,48 +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 {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}`);
}

View File

@ -1,60 +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 {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));
}

View File

@ -1,146 +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 {SecurityContext} from '@angular/core';
import {isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast';
import {EMPTY_STATE as EMPTY_ANIMATION_STATE} from '../private_import_core';
import {BoundElementPropertyAst, BoundEventAst, 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);
}
export function triggerAnimation(
view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst,
eventListener: o.Expression, renderElement: o.Expression, renderValue: o.Expression,
lastRenderValue: o.Expression) {
const detachStmts: o.Statement[] = [];
const updateStmts: o.Statement[] = [];
const animationName = boundProp.name;
const animationFnExpr =
componentView.prop('componentType').prop('animations').key(o.literal(animationName));
// it's important to normalize the void value as `void` explicitly
// so that the styles data can be obtained from the stringmap
const emptyStateValue = o.literal(EMPTY_ANIMATION_STATE);
const unitializedValue = o.importExpr(resolveIdentifier(Identifiers.UNINITIALIZED));
const animationTransitionVar = o.variable('animationTransition_' + animationName);
updateStmts.push(
animationTransitionVar
.set(animationFnExpr.callFn([
view, renderElement,
lastRenderValue.equals(unitializedValue).conditional(emptyStateValue, lastRenderValue),
renderValue.equals(unitializedValue).conditional(emptyStateValue, renderValue)
]))
.toDeclStmt());
detachStmts.push(
animationTransitionVar
.set(animationFnExpr.callFn([view, renderElement, lastRenderValue, emptyStateValue]))
.toDeclStmt());
const registerStmts = [
animationTransitionVar
.callMethod(
'onStart',
[eventListener.callMethod(
o.BuiltinMethod.Bind,
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'start'))])])
.toStmt(),
animationTransitionVar
.callMethod(
'onDone',
[eventListener.callMethod(
o.BuiltinMethod.Bind,
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'done'))])])
.toStmt(),
];
updateStmts.push(...registerStmts);
detachStmts.push(...registerStmts);
return {updateStmts, detachStmts};
}

View File

@ -9,37 +9,24 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {CompileDirectiveMetadata, CompileIdentifierMetadata} from './compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata} from './compile_metadata';
import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util';
import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter';
import {triggerAnimation, writeToRenderer} from './compiler_util/render_util';
import {CompilerConfig} from './config'; import {CompilerConfig} from './config';
import {Parser} from './expression_parser/parser';
import {Identifiers, resolveIdentifier} from './identifiers'; 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 * as o from './output/output_ast';
import {ParseError, ParseErrorLevel, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util'; import {LifecycleHooks, isDefaultChangeDetectionStrategy} from './private_import_core';
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 { export class DirectiveWrapperCompileResult {
constructor(public statements: o.Statement[], public dirWrapperClassVar: string) {} constructor(public statements: o.Statement[], public dirWrapperClassVar: string) {}
} }
const CONTEXT_FIELD_NAME = 'context'; const CONTEXT_FIELD_NAME = 'context';
const CHANGES_FIELD_NAME = '_changes'; const CHANGES_FIELD_NAME = 'changes';
const CHANGED_FIELD_NAME = '_changed'; const CHANGED_FIELD_NAME = 'changed';
const EVENT_HANDLER_FIELD_NAME = '_eventHandler';
const CURR_VALUE_VAR = o.variable('currValue'); const CURR_VALUE_VAR = o.variable('currValue');
const THROW_ON_CHANGE_VAR = o.variable('throwOnChange'); const THROW_ON_CHANGE_VAR = o.variable('throwOnChange');
const FORCE_UPDATE_VAR = o.variable('forceUpdate'); const FORCE_UPDATE_VAR = o.variable('forceUpdate');
const VIEW_VAR = o.variable('view'); const VIEW_VAR = o.variable('view');
const COMPONENT_VIEW_VAR = o.variable('componentView');
const RENDER_EL_VAR = o.variable('el'); const RENDER_EL_VAR = o.variable('el');
const EVENT_NAME_VAR = o.variable('eventName');
const RESET_CHANGES_STMT = o.THIS_EXPR.prop(CHANGES_FIELD_NAME).set(o.literalMap([])).toStmt(); const RESET_CHANGES_STMT = o.THIS_EXPR.prop(CHANGES_FIELD_NAME).set(o.literalMap([])).toStmt();
@ -55,107 +42,62 @@ const RESET_CHANGES_STMT = o.THIS_EXPR.prop(CHANGES_FIELD_NAME).set(o.literalMap
export class DirectiveWrapperCompiler { export class DirectiveWrapperCompiler {
static dirWrapperClassName(id: CompileIdentifierMetadata) { return `Wrapper_${id.name}`; } static dirWrapperClassName(id: CompileIdentifierMetadata) { return `Wrapper_${id.name}`; }
constructor( constructor(private compilerConfig: CompilerConfig) {}
private compilerConfig: CompilerConfig, private _exprParser: Parser,
private _schemaRegistry: ElementSchemaRegistry, private _console: Console) {}
compile(dirMeta: CompileDirectiveMetadata): DirectiveWrapperCompileResult { compile(dirMeta: CompileDirectiveMetadata): DirectiveWrapperCompileResult {
const hostParseResult = parseHostBindings(dirMeta, this._exprParser, this._schemaRegistry);
reportParseErrors(hostParseResult.errors, this._console);
const builder = new DirectiveWrapperBuilder(this.compilerConfig, dirMeta);
Object.keys(dirMeta.inputs).forEach((inputFieldName) => {
addCheckInputMethod(inputFieldName, builder);
});
addNgDoCheckMethod(builder);
addCheckHostMethod(hostParseResult.hostProps, builder);
addHandleEventMethod(hostParseResult.hostListeners, builder);
addSubscribeMethod(dirMeta, builder);
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[] = [];
detachStmts: o.Statement[] = [];
destroyStmts: o.Statement[] = [];
genChanges: boolean;
ngOnChanges: boolean;
ngOnInit: boolean;
ngDoCheck: boolean;
ngOnDestroy: 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;
this.ngOnDestroy = dirLifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1;
if (this.ngOnDestroy) {
this.destroyStmts.push(
o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngOnDestroy', []).toStmt());
}
}
build(): o.ClassStmt {
const dirDepParamNames: string[] = []; const dirDepParamNames: string[] = [];
for (let i = 0; i < this.dirMeta.type.diDeps.length; i++) { for (let i = 0; i < dirMeta.type.diDeps.length; i++) {
dirDepParamNames.push(`p${i}`); dirDepParamNames.push(`p${i}`);
} }
const dirLifecycleHooks = dirMeta.type.lifecycleHooks;
const methods = [ let lifecycleHooks: GenConfig = {
new o.ClassMethod( genChanges: dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1 ||
'ngOnDetach', this.compilerConfig.logBindingUpdate,
[ ngOnChanges: dirLifecycleHooks.indexOf(LifecycleHooks.OnChanges) !== -1,
new o.FnParam( ngOnInit: dirLifecycleHooks.indexOf(LifecycleHooks.OnInit) !== -1,
VIEW_VAR.name, ngDoCheck: dirLifecycleHooks.indexOf(LifecycleHooks.DoCheck) !== -1
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])), };
new o.FnParam(
COMPONENT_VIEW_VAR.name,
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
],
this.detachStmts),
new o.ClassMethod('ngOnDestroy', [], this.destroyStmts),
];
const fields: o.ClassField[] = [ const fields: o.ClassField[] = [
new o.ClassField(EVENT_HANDLER_FIELD_NAME, o.FUNCTION_TYPE, [o.StmtModifier.Private]), 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),
new o.ClassField(CHANGED_FIELD_NAME, o.BOOL_TYPE, [o.StmtModifier.Private]),
]; ];
const ctorStmts: o.Statement[] = const ctorStmts: o.Statement[] =
[o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(false)).toStmt()]; [o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(false)).toStmt()];
if (this.genChanges) { if (lifecycleHooks.genChanges) {
fields.push(new o.ClassField( fields.push(new o.ClassField(CHANGES_FIELD_NAME, new o.MapType(o.DYNAMIC_TYPE)));
CHANGES_FIELD_NAME, new o.MapType(o.DYNAMIC_TYPE), [o.StmtModifier.Private]));
ctorStmts.push(RESET_CHANGES_STMT); 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( ctorStmts.push(
o.THIS_EXPR.prop(CONTEXT_FIELD_NAME) o.THIS_EXPR.prop(CONTEXT_FIELD_NAME)
.set(o.importExpr(this.dirMeta.type) .set(o.importExpr(dirMeta.type)
.instantiate(dirDepParamNames.map((paramName) => o.variable(paramName)))) .instantiate(dirDepParamNames.map((paramName) => o.variable(paramName))))
.toStmt()); .toStmt());
const ctor = new o.ClassMethod(
null, dirDepParamNames.map((paramName) => new o.FnParam(paramName, o.DYNAMIC_TYPE)),
ctorStmts);
return createClassStmt({ const wrapperClassName = DirectiveWrapperCompiler.dirWrapperClassName(dirMeta.type);
name: DirectiveWrapperCompiler.dirWrapperClassName(this.dirMeta.type), const classStmt = new o.ClassStmt(wrapperClassName, null, fields, [], ctor, methods);
ctorParams: dirDepParamNames.map((paramName) => new o.FnParam(paramName, o.DYNAMIC_TYPE)), return new DirectiveWrapperCompileResult([classStmt], wrapperClassName);
builders: [{fields, ctorStmts, methods}, this]
});
} }
} }
function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) { function detectChangesInternalMethod(
lifecycleHooks: GenConfig, logBindingUpdate: boolean): o.ClassMethod {
const changedVar = o.variable('changed'); const changedVar = o.variable('changed');
const stmts: o.Statement[] = [ const stmts: o.Statement[] = [
changedVar.set(o.THIS_EXPR.prop(CHANGED_FIELD_NAME)).toDeclStmt(), changedVar.set(o.THIS_EXPR.prop(CHANGED_FIELD_NAME)).toDeclStmt(),
@ -163,14 +105,14 @@ function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) {
]; ];
const lifecycleStmts: o.Statement[] = []; const lifecycleStmts: o.Statement[] = [];
if (builder.genChanges) { if (lifecycleHooks.genChanges) {
const onChangesStmts: o.Statement[] = []; const onChangesStmts: o.Statement[] = [];
if (builder.ngOnChanges) { if (lifecycleHooks.ngOnChanges) {
onChangesStmts.push(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME) onChangesStmts.push(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME)
.callMethod('ngOnChanges', [o.THIS_EXPR.prop(CHANGES_FIELD_NAME)]) .callMethod('ngOnChanges', [o.THIS_EXPR.prop(CHANGES_FIELD_NAME)])
.toStmt()); .toStmt());
} }
if (builder.compilerConfig.logBindingUpdate) { if (logBindingUpdate) {
onChangesStmts.push( onChangesStmts.push(
o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfoForChanges)) o.importExpr(resolveIdentifier(Identifiers.setBindingDebugInfoForChanges))
.callFn( .callFn(
@ -181,12 +123,12 @@ function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) {
lifecycleStmts.push(new o.IfStmt(changedVar, onChangesStmts)); lifecycleStmts.push(new o.IfStmt(changedVar, onChangesStmts));
} }
if (builder.ngOnInit) { if (lifecycleHooks.ngOnInit) {
lifecycleStmts.push(new o.IfStmt( lifecycleStmts.push(new o.IfStmt(
VIEW_VAR.prop('numberOfChecks').identical(new o.LiteralExpr(0)), VIEW_VAR.prop('numberOfChecks').identical(new o.LiteralExpr(0)),
[o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngOnInit', []).toStmt()])); [o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngOnInit', []).toStmt()]));
} }
if (builder.ngDoCheck) { if (lifecycleHooks.ngDoCheck) {
lifecycleStmts.push(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngDoCheck', []).toStmt()); lifecycleStmts.push(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngDoCheck', []).toStmt());
} }
if (lifecycleStmts.length > 0) { if (lifecycleStmts.length > 0) {
@ -194,265 +136,51 @@ function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) {
} }
stmts.push(new o.ReturnStatement(changedVar)); stmts.push(new o.ReturnStatement(changedVar));
builder.methods.push(new o.ClassMethod( return new o.ClassMethod(
'ngDoCheck', 'detectChangesInternal',
[ [
new o.FnParam( new o.FnParam(
VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])), VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE), new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE), new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
], ],
stmts, o.BOOL_TYPE)); stmts, o.BOOL_TYPE);
} }
function addCheckInputMethod(input: string, builder: DirectiveWrapperBuilder) { function checkInputMethod(
const field = createCheckBindingField(builder); input: string, fieldExpr: o.ReadPropExpr, lifecycleHooks: GenConfig): o.ClassMethod {
var onChangeStatements: o.Statement[] = [ var onChangeStatements: o.Statement[] = [
o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(true)).toStmt(), 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(), o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).prop(input).set(CURR_VALUE_VAR).toStmt(),
]; ];
if (builder.genChanges) { if (lifecycleHooks.genChanges) {
onChangeStatements.push(o.THIS_EXPR.prop(CHANGES_FIELD_NAME) onChangeStatements.push(o.THIS_EXPR.prop(CHANGES_FIELD_NAME)
.key(o.literal(input)) .key(o.literal(input))
.set(o.importExpr(resolveIdentifier(Identifiers.SimpleChange)) .set(o.importExpr(resolveIdentifier(Identifiers.SimpleChange))
.instantiate([field.expression, CURR_VALUE_VAR])) .instantiate([fieldExpr, CURR_VALUE_VAR]))
.toStmt()); .toStmt());
} }
onChangeStatements.push(fieldExpr.set(CURR_VALUE_VAR).toStmt());
var methodBody: o.Statement[] = createCheckBindingStmt( var methodBody: o.Statement[] = [
{currValExpr: CURR_VALUE_VAR, forceUpdate: FORCE_UPDATE_VAR, stmts: []}, field.expression, new o.IfStmt(
THROW_ON_CHANGE_VAR, onChangeStatements); FORCE_UPDATE_VAR.or(o.importExpr(resolveIdentifier(Identifiers.checkBinding))
builder.methods.push(new o.ClassMethod( .callFn([THROW_ON_CHANGE_VAR, fieldExpr, CURR_VALUE_VAR])),
onChangeStatements),
];
return new o.ClassMethod(
`check_${input}`, `check_${input}`,
[ [
new o.FnParam(CURR_VALUE_VAR.name, o.DYNAMIC_TYPE), new o.FnParam(CURR_VALUE_VAR.name, o.DYNAMIC_TYPE),
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE), new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
new o.FnParam(FORCE_UPDATE_VAR.name, o.BOOL_TYPE), new o.FnParam(FORCE_UPDATE_VAR.name, o.BOOL_TYPE),
], ],
methodBody)); methodBody);
} }
function addCheckHostMethod( interface GenConfig {
hostProps: BoundElementPropertyAst[], builder: DirectiveWrapperBuilder) { genChanges: boolean;
const stmts: o.Statement[] = []; ngOnChanges: boolean;
const methodParams: o.FnParam[] = [ ngOnInit: boolean;
new o.FnParam( ngDoCheck: boolean;
VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])), }
new o.FnParam(
COMPONENT_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, hostPropIdx) => {
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))));
}
let checkBindingStmts: o.Statement[];
if (hostProp.isAnimation) {
const {updateStmts, detachStmts} = triggerAnimation(
VIEW_VAR, COMPONENT_VIEW_VAR, hostProp,
o.THIS_EXPR.prop(EVENT_HANDLER_FIELD_NAME)
.or(o.importExpr(resolveIdentifier(Identifiers.noop))),
RENDER_EL_VAR, evalResult.currValExpr, field.expression);
checkBindingStmts = updateStmts;
builder.detachStmts.push(...detachStmts);
} else {
checkBindingStmts = writeToRenderer(
VIEW_VAR, hostProp, RENDER_EL_VAR, evalResult.currValExpr,
builder.compilerConfig.logBindingUpdate, securityContextExpr);
}
stmts.push(...createCheckBindingStmt(
evalResult, field.expression, THROW_ON_CHANGE_VAR, checkBindingStmts));
});
builder.methods.push(new o.ClassMethod('checkHost', methodParams, stmts));
}
function addHandleEventMethod(hostListeners: BoundEventAst[], builder: DirectiveWrapperBuilder) {
const resultVar = o.variable(`result`);
const actionStmts: o.Statement[] = [resultVar.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE)];
hostListeners.forEach((hostListener, eventIdx) => {
const evalResult = convertActionBinding(
builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostListener.handler,
`sub_${eventIdx}`);
const trueStmts = evalResult.stmts;
if (evalResult.preventDefault) {
trueStmts.push(resultVar.set(evalResult.preventDefault.and(resultVar)).toStmt());
}
// TODO(tbosch): convert this into a `switch` once our OutputAst supports it.
actionStmts.push(
new o.IfStmt(EVENT_NAME_VAR.equals(o.literal(hostListener.fullName)), trueStmts));
});
actionStmts.push(new o.ReturnStatement(resultVar));
builder.methods.push(new o.ClassMethod(
'handleEvent',
[
new o.FnParam(EVENT_NAME_VAR.name, o.STRING_TYPE),
new o.FnParam(EventHandlerVars.event.name, o.DYNAMIC_TYPE)
],
actionStmts, o.BOOL_TYPE));
}
function addSubscribeMethod(dirMeta: CompileDirectiveMetadata, builder: DirectiveWrapperBuilder) {
const methodParams: o.FnParam[] = [
new o.FnParam(
VIEW_VAR.name, o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
new o.FnParam(EVENT_HANDLER_FIELD_NAME, o.DYNAMIC_TYPE)
];
const stmts: o.Statement[] = [
o.THIS_EXPR.prop(EVENT_HANDLER_FIELD_NAME).set(o.variable(EVENT_HANDLER_FIELD_NAME)).toStmt()
];
Object.keys(dirMeta.outputs).forEach((emitterPropName, emitterIdx) => {
const eventName = dirMeta.outputs[emitterPropName];
const paramName = `emit${emitterIdx}`;
methodParams.push(new o.FnParam(paramName, o.BOOL_TYPE));
const subscriptionFieldName = `subscription${emitterIdx}`;
builder.fields.push(new o.ClassField(subscriptionFieldName, o.DYNAMIC_TYPE));
stmts.push(new o.IfStmt(o.variable(paramName), [
o.THIS_EXPR.prop(subscriptionFieldName)
.set(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME)
.prop(emitterPropName)
.callMethod(
o.BuiltinMethod.SubscribeObservable,
[o.variable(EVENT_HANDLER_FIELD_NAME)
.callMethod(o.BuiltinMethod.Bind, [VIEW_VAR, o.literal(eventName)])]))
.toStmt()
]));
builder.destroyStmts.push(
o.THIS_EXPR.prop(subscriptionFieldName)
.and(o.THIS_EXPR.prop(subscriptionFieldName).callMethod('unsubscribe', []))
.toStmt());
});
builder.methods.push(new o.ClassMethod('subscribe', 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')}`);
}
}
export class DirectiveWrapperExpressions {
static create(dir: CompileIdentifierMetadata, depsExpr: o.Expression[]): o.Expression {
return o.importExpr(dir).instantiate(depsExpr, o.importType(dir));
}
static context(dirWrapper: o.Expression): o.ReadPropExpr {
return dirWrapper.prop(CONTEXT_FIELD_NAME);
}
static ngDoCheck(
dirWrapper: o.Expression, view: o.Expression, renderElement: o.Expression,
throwOnChange: o.Expression): o.Expression {
return dirWrapper.callMethod('ngDoCheck', [view, renderElement, throwOnChange]);
}
static checkHost(
hostProps: BoundElementPropertyAst[], dirWrapper: o.Expression, view: o.Expression,
componentView: o.Expression, renderElement: o.Expression, throwOnChange: o.Expression,
runtimeSecurityContexts: o.Expression[]): o.Statement[] {
if (hostProps.length) {
return [dirWrapper
.callMethod(
'checkHost', [view, componentView, renderElement, throwOnChange].concat(
runtimeSecurityContexts))
.toStmt()];
} else {
return [];
}
}
static ngOnDetach(
hostProps: BoundElementPropertyAst[], dirWrapper: o.Expression, view: o.Expression,
componentView: o.Expression, renderEl: o.Expression): o.Statement[] {
if (hostProps.some(prop => prop.isAnimation)) {
return [dirWrapper
.callMethod(
'ngOnDetach',
[
view,
componentView,
renderEl,
])
.toStmt()];
} else {
return [];
}
}
static ngOnDestroy(dir: CompileDirectiveMetadata, dirWrapper: o.Expression): o.Statement[] {
if (dir.type.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1 ||
Object.keys(dir.outputs).length > 0) {
return [dirWrapper.callMethod('ngOnDestroy', []).toStmt()];
} else {
return [];
}
}
static subscribe(
dirMeta: CompileDirectiveMetadata, hostProps: BoundElementPropertyAst[], usedEvents: string[],
dirWrapper: o.Expression, view: o.Expression, eventListener: o.Expression): o.Statement[] {
let needsSubscribe = false;
let eventFlags: o.Expression[] = [];
Object.keys(dirMeta.outputs).forEach((propName) => {
const eventName = dirMeta.outputs[propName];
const eventUsed = usedEvents.indexOf(eventName) > -1;
needsSubscribe = needsSubscribe || eventUsed;
eventFlags.push(o.literal(eventUsed));
});
hostProps.forEach((hostProp) => {
if (hostProp.isAnimation && usedEvents.length > 0) {
needsSubscribe = true;
}
});
if (needsSubscribe) {
return [
dirWrapper.callMethod('subscribe', [view, eventListener].concat(eventFlags)).toStmt()
];
} else {
return [];
}
}
static handleEvent(
hostEvents: BoundEventAst[], dirWrapper: o.Expression, eventName: o.Expression,
event: o.Expression): o.Expression {
return dirWrapper.callMethod('handleEvent', [eventName, event]);
}
}

View File

@ -60,11 +60,10 @@ export class Parser {
parseSimpleBinding( parseSimpleBinding(
input: string, location: string, input: string, location: string,
interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource { interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): ASTWithSource {
const ast = this._parseBindingAst(input, location, interpolationConfig); var ast = this._parseBindingAst(input, location, interpolationConfig);
const errors = SimpleExpressionChecker.check(ast); if (!SimpleExpressionChecker.check(ast)) {
if (errors.length > 0) {
this._reportError( this._reportError(
`Host binding expression cannot contain ${errors.join(' ')}`, input, location); 'Host binding expression can only contain field access and constants', input, location);
} }
return new ASTWithSource(ast, input, location, this.errors); return new ASTWithSource(ast, input, location, this.errors);
} }
@ -752,51 +751,51 @@ export class _ParseAST {
} }
class SimpleExpressionChecker implements AstVisitor { class SimpleExpressionChecker implements AstVisitor {
static check(ast: AST): string[] { static check(ast: AST): boolean {
var s = new SimpleExpressionChecker(); var s = new SimpleExpressionChecker();
ast.visit(s); ast.visit(s);
return s.errors; return s.simple;
} }
errors: string[] = []; simple = true;
visitImplicitReceiver(ast: ImplicitReceiver, context: any) {} visitImplicitReceiver(ast: ImplicitReceiver, context: any) {}
visitInterpolation(ast: Interpolation, context: any) {} visitInterpolation(ast: Interpolation, context: any) { this.simple = false; }
visitLiteralPrimitive(ast: LiteralPrimitive, context: any) {} visitLiteralPrimitive(ast: LiteralPrimitive, context: any) {}
visitPropertyRead(ast: PropertyRead, context: any) {} visitPropertyRead(ast: PropertyRead, context: any) {}
visitPropertyWrite(ast: PropertyWrite, context: any) {} visitPropertyWrite(ast: PropertyWrite, context: any) { this.simple = false; }
visitSafePropertyRead(ast: SafePropertyRead, context: any) {} visitSafePropertyRead(ast: SafePropertyRead, context: any) { this.simple = false; }
visitMethodCall(ast: MethodCall, context: any) {} visitMethodCall(ast: MethodCall, context: any) { this.simple = false; }
visitSafeMethodCall(ast: SafeMethodCall, context: any) {} visitSafeMethodCall(ast: SafeMethodCall, context: any) { this.simple = false; }
visitFunctionCall(ast: FunctionCall, context: any) {} visitFunctionCall(ast: FunctionCall, context: any) { this.simple = false; }
visitLiteralArray(ast: LiteralArray, context: any) { this.visitAll(ast.expressions); } visitLiteralArray(ast: LiteralArray, context: any) { this.visitAll(ast.expressions); }
visitLiteralMap(ast: LiteralMap, context: any) { this.visitAll(ast.values); } visitLiteralMap(ast: LiteralMap, context: any) { this.visitAll(ast.values); }
visitBinary(ast: Binary, context: any) {} visitBinary(ast: Binary, context: any) { this.simple = false; }
visitPrefixNot(ast: PrefixNot, context: any) {} visitPrefixNot(ast: PrefixNot, context: any) { this.simple = false; }
visitConditional(ast: Conditional, context: any) {} visitConditional(ast: Conditional, context: any) { this.simple = false; }
visitPipe(ast: BindingPipe, context: any) { this.errors.push('pipes'); } visitPipe(ast: BindingPipe, context: any) { this.simple = false; }
visitKeyedRead(ast: KeyedRead, context: any) {} visitKeyedRead(ast: KeyedRead, context: any) { this.simple = false; }
visitKeyedWrite(ast: KeyedWrite, context: any) {} visitKeyedWrite(ast: KeyedWrite, context: any) { this.simple = false; }
visitAll(asts: any[]): any[] { return asts.map(node => node.visit(this)); } visitAll(asts: any[]): any[] { return asts.map(node => node.visit(this)); }
visitChain(ast: Chain, context: any) {} visitChain(ast: Chain, context: any) { this.simple = false; }
visitQuote(ast: Quote, context: any) {} visitQuote(ast: Quote, context: any) { this.simple = false; }
} }

View File

@ -10,6 +10,7 @@ import {ANALYZE_FOR_ENTRY_COMPONENTS, AnimationTransitionEvent, ChangeDetectionS
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata'; 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 {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 APP_VIEW_MODULE_URL = assetUrl('core', 'linker/view');
var VIEW_UTILS_MODULE_URL = assetUrl('core', 'linker/view_utils'); var VIEW_UTILS_MODULE_URL = assetUrl('core', 'linker/view_utils');
@ -162,6 +163,11 @@ export class Identifiers {
moduleUrl: VIEW_UTILS_MODULE_URL, moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.checkBinding runtime: view_utils.checkBinding
}; };
static flattenNestedViewRenderNodes: IdentifierSpec = {
name: 'flattenNestedViewRenderNodes',
moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.flattenNestedViewRenderNodes
};
static devModeEqual: static devModeEqual:
IdentifierSpec = {name: 'devModeEqual', moduleUrl: CD_MODULE_URL, runtime: devModeEqual}; IdentifierSpec = {name: 'devModeEqual', moduleUrl: CD_MODULE_URL, runtime: devModeEqual};
static interpolate: IdentifierSpec = { static interpolate: IdentifierSpec = {
@ -184,17 +190,8 @@ export class Identifiers {
moduleUrl: VIEW_UTILS_MODULE_URL, moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.EMPTY_MAP runtime: view_utils.EMPTY_MAP
}; };
static createRenderElement: IdentifierSpec = {
name: 'createRenderElement', static pureProxies = [
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, null,
{name: 'pureProxy1', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy1}, {name: 'pureProxy1', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy1},
{name: 'pureProxy2', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy2}, {name: 'pureProxy2', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.pureProxy2},
@ -287,42 +284,6 @@ export class Identifiers {
moduleUrl: assetUrl('core', 'animation/animation_transition'), moduleUrl: assetUrl('core', 'animation/animation_transition'),
runtime: AnimationTransition 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
};
static subscribeToRenderElement: IdentifierSpec = {
name: 'subscribeToRenderElement',
moduleUrl: VIEW_UTILS_MODULE_URL,
runtime: view_utils.subscribeToRenderElement
};
static noop:
IdentifierSpec = {name: 'noop', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: view_utils.noop};
}
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) { export function resolveIdentifier(identifier: IdentifierSpec) {

View File

@ -160,7 +160,7 @@ export class CompileMetadataResolver {
moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta); moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta);
if (dirMeta.entryComponents) { if (dirMeta.entryComponents) {
entryComponentMetadata = entryComponentMetadata =
flattenAndDedupeArray(dirMeta.entryComponents) flattenArray(dirMeta.entryComponents)
.map((type) => this.getTypeMetadata(type, staticTypeModuleUrl(type))) .map((type) => this.getTypeMetadata(type, staticTypeModuleUrl(type)))
.concat(entryComponentMetadata); .concat(entryComponentMetadata);
} }
@ -228,7 +228,7 @@ export class CompileMetadataResolver {
const schemas: SchemaMetadata[] = []; const schemas: SchemaMetadata[] = [];
if (meta.imports) { if (meta.imports) {
flattenAndDedupeArray(meta.imports).forEach((importedType) => { flattenArray(meta.imports).forEach((importedType) => {
let importedModuleType: Type<any>; let importedModuleType: Type<any>;
if (isValidType(importedType)) { if (isValidType(importedType)) {
importedModuleType = importedType; importedModuleType = importedType;
@ -257,7 +257,7 @@ export class CompileMetadataResolver {
} }
if (meta.exports) { if (meta.exports) {
flattenAndDedupeArray(meta.exports).forEach((exportedType) => { flattenArray(meta.exports).forEach((exportedType) => {
if (!isValidType(exportedType)) { if (!isValidType(exportedType)) {
throw new Error( throw new Error(
`Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`); `Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`);
@ -283,7 +283,7 @@ export class CompileMetadataResolver {
const transitiveModule = const transitiveModule =
this._getTransitiveNgModuleMetadata(importedModules, exportedModules); this._getTransitiveNgModuleMetadata(importedModules, exportedModules);
if (meta.declarations) { if (meta.declarations) {
flattenAndDedupeArray(meta.declarations).forEach((declaredType) => { flattenArray(meta.declarations).forEach((declaredType) => {
if (!isValidType(declaredType)) { if (!isValidType(declaredType)) {
throw new Error( throw new Error(
`Unexpected value '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`); `Unexpected value '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`);
@ -313,12 +313,12 @@ export class CompileMetadataResolver {
if (meta.entryComponents) { if (meta.entryComponents) {
entryComponents.push( entryComponents.push(
...flattenAndDedupeArray(meta.entryComponents) ...flattenArray(meta.entryComponents)
.map(type => this.getTypeMetadata(type, staticTypeModuleUrl(type)))); .map(type => this.getTypeMetadata(type, staticTypeModuleUrl(type))));
} }
if (meta.bootstrap) { if (meta.bootstrap) {
const typeMetadata = flattenAndDedupeArray(meta.bootstrap).map(type => { const typeMetadata = flattenArray(meta.bootstrap).map(type => {
if (!isValidType(type)) { if (!isValidType(type)) {
throw new Error( throw new Error(
`Unexpected value '${stringify(type)}' used in the bootstrap property of module '${stringify(moduleType)}'`); `Unexpected value '${stringify(type)}' used in the bootstrap property of module '${stringify(moduleType)}'`);
@ -331,7 +331,7 @@ export class CompileMetadataResolver {
entryComponents.push(...bootstrapComponents); entryComponents.push(...bootstrapComponents);
if (meta.schemas) { if (meta.schemas) {
schemas.push(...flattenAndDedupeArray(meta.schemas)); schemas.push(...flattenArray(meta.schemas));
} }
transitiveModule.entryComponents.push(...entryComponents); transitiveModule.entryComponents.push(...entryComponents);
@ -378,15 +378,15 @@ export class CompileMetadataResolver {
} }
private _getTypeDescriptor(type: Type<any>): string { private _getTypeDescriptor(type: Type<any>): string {
if (this._directiveResolver.resolve(type, false)) { if (this._directiveResolver.resolve(type, false) !== null) {
return 'directive'; return 'directive';
} }
if (this._pipeResolver.resolve(type, false)) { if (this._pipeResolver.resolve(type, false) !== null) {
return 'pipe'; return 'pipe';
} }
if (this._ngModuleResolver.resolve(type, false)) { if (this._ngModuleResolver.resolve(type, false) !== null) {
return 'module'; return 'module';
} }
@ -506,7 +506,9 @@ export class CompileMetadataResolver {
let isSelf = false; let isSelf = false;
let isSkipSelf = false; let isSkipSelf = false;
let isOptional = false; let isOptional = false;
let token: any = null; let query: Query = null;
let viewQuery: Query = null;
var token: any = null;
if (Array.isArray(param)) { if (Array.isArray(param)) {
param.forEach((paramEntry) => { param.forEach((paramEntry) => {
if (paramEntry instanceof Host) { if (paramEntry instanceof Host) {
@ -520,6 +522,12 @@ export class CompileMetadataResolver {
} else if (paramEntry instanceof Attribute) { } else if (paramEntry instanceof Attribute) {
isAttribute = true; isAttribute = true;
token = paramEntry.attributeName; token = paramEntry.attributeName;
} else if (paramEntry instanceof Query) {
if (paramEntry.isViewQuery) {
viewQuery = paramEntry;
} else {
query = paramEntry;
}
} else if (paramEntry instanceof Inject) { } else if (paramEntry instanceof Inject) {
token = paramEntry.token; token = paramEntry.token;
} else if (isValidType(paramEntry) && isBlank(token)) { } else if (isValidType(paramEntry) && isBlank(token)) {
@ -540,6 +548,8 @@ export class CompileMetadataResolver {
isSelf, isSelf,
isSkipSelf, isSkipSelf,
isOptional, isOptional,
query: query ? this.getQueryMetadata(query, null, typeOrFunc) : null,
viewQuery: viewQuery ? this.getQueryMetadata(viewQuery, null, typeOrFunc) : null,
token: this.getTokenMetadata(token) token: this.getTokenMetadata(token)
}); });
@ -595,20 +605,19 @@ export class CompileMetadataResolver {
} else if (isValidType(provider)) { } else if (isValidType(provider)) {
compileProvider = this.getTypeMetadata(provider, staticTypeModuleUrl(provider)); compileProvider = this.getTypeMetadata(provider, staticTypeModuleUrl(provider));
} else { } else {
const providersInfo = let providersInfo = (<string[]>providers.reduce(
(<string[]>providers.reduce( (soFar: string[], seenProvider: any, seenProviderIdx: number) => {
(soFar: string[], seenProvider: any, seenProviderIdx: number) => { if (seenProviderIdx < providerIdx) {
if (seenProviderIdx < providerIdx) { soFar.push(`${stringify(seenProvider)}`);
soFar.push(`${stringify(seenProvider)}`); } else if (seenProviderIdx == providerIdx) {
} else if (seenProviderIdx == providerIdx) { soFar.push(`?${stringify(seenProvider)}?`);
soFar.push(`?${stringify(seenProvider)}?`); } else if (seenProviderIdx == providerIdx + 1) {
} else if (seenProviderIdx == providerIdx + 1) { soFar.push('...');
soFar.push('...'); }
} return soFar;
return soFar; },
}, []))
[])) .join(', ');
.join(', ');
throw new Error( throw new Error(
`Invalid ${debugInfo ? debugInfo : 'provider'} - only instances of Provider and Type are allowed, got: [${providersInfo}]`); `Invalid ${debugInfo ? debugInfo : 'provider'} - only instances of Provider and Type are allowed, got: [${providersInfo}]`);
@ -726,6 +735,7 @@ function getTransitiveModules(
return targetModules; return targetModules;
} }
function flattenArray(tree: any[], out: Array<any> = []): Array<any> { function flattenArray(tree: any[], out: Array<any> = []): Array<any> {
if (tree) { if (tree) {
for (let i = 0; i < tree.length; i++) { for (let i = 0; i < tree.length; i++) {
@ -740,17 +750,6 @@ function flattenArray(tree: any[], out: Array<any> = []): Array<any> {
return out; return out;
} }
function dedupeArray(array: any[]): Array<any> {
if (array) {
return Array.from(new Set(array));
}
return [];
}
function flattenAndDedupeArray(tree: any[]): Array<any> {
return dedupeArray(flattenArray(tree));
}
function isValidType(value: any): boolean { function isValidType(value: any): boolean {
return cpl.isStaticSymbol(value) || (value instanceof Type); return cpl.isStaticSymbol(value) || (value instanceof Type);
} }

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ListWrapper} from '../facade/collection';
import {isBlank, isPresent} from '../facade/lang'; import {isBlank, isPresent} from '../facade/lang';
import {ParseError, ParseSourceSpan} from '../parse_util'; import {ParseError, ParseSourceSpan} from '../parse_util';
@ -230,7 +231,7 @@ class _TreeBuilder {
private _closeVoidElement(): void { private _closeVoidElement(): void {
if (this._elementStack.length > 0) { if (this._elementStack.length > 0) {
const el = this._elementStack[this._elementStack.length - 1]; const el = ListWrapper.last(this._elementStack);
if (this.getTagDefinition(el.name).isVoid) { if (this.getTagDefinition(el.name).isVoid) {
this._elementStack.pop(); this._elementStack.pop();
@ -274,7 +275,7 @@ class _TreeBuilder {
private _pushElement(el: html.Element) { private _pushElement(el: html.Element) {
if (this._elementStack.length > 0) { if (this._elementStack.length > 0) {
const parentEl = this._elementStack[this._elementStack.length - 1]; const parentEl = ListWrapper.last(this._elementStack);
if (this.getTagDefinition(parentEl.name).isClosedByChild(el.name)) { if (this.getTagDefinition(parentEl.name).isClosedByChild(el.name)) {
this._elementStack.pop(); this._elementStack.pop();
} }
@ -315,7 +316,7 @@ class _TreeBuilder {
for (let stackIndex = this._elementStack.length - 1; stackIndex >= 0; stackIndex--) { for (let stackIndex = this._elementStack.length - 1; stackIndex >= 0; stackIndex--) {
const el = this._elementStack[stackIndex]; const el = this._elementStack[stackIndex];
if (el.name == fullName) { if (el.name == fullName) {
this._elementStack.splice(stackIndex, this._elementStack.length - stackIndex); ListWrapper.splice(this._elementStack, stackIndex, this._elementStack.length - stackIndex);
return true; return true;
} }
@ -342,7 +343,7 @@ class _TreeBuilder {
} }
private _getParentElement(): html.Element { private _getParentElement(): html.Element {
return this._elementStack.length > 0 ? this._elementStack[this._elementStack.length - 1] : null; return this._elementStack.length > 0 ? ListWrapper.last(this._elementStack) : null;
} }
/** /**
@ -360,7 +361,7 @@ class _TreeBuilder {
container = this._elementStack[i]; container = this._elementStack[i];
} }
return {parent: this._elementStack[this._elementStack.length - 1], container}; return {parent: ListWrapper.last(this._elementStack), container};
} }
private _addToParent(node: html.Node) { private _addToParent(node: html.Node) {

View File

@ -9,16 +9,15 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata} from './compile_metadata'; import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata} from './compile_metadata';
import {createDiTokenExpression} from './compiler_util/identifier_util';
import {isPresent} from './facade/lang'; import {isPresent} from './facade/lang';
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers'; import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers';
import {ClassBuilder, createClassStmt} from './output/class_builder';
import * as o from './output/output_ast'; import * as o from './output/output_ast';
import {convertValueToOutputAst} from './output/value_util'; import {convertValueToOutputAst} from './output/value_util';
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util'; import {ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
import {LifecycleHooks} from './private_import_core'; import {LifecycleHooks} from './private_import_core';
import {NgModuleProviderAnalyzer} from './provider_analyzer'; import {NgModuleProviderAnalyzer} from './provider_analyzer';
import {ProviderAst} from './template_parser/template_ast'; import {ProviderAst} from './template_parser/template_ast';
import {createDiTokenExpression} from './util';
export class ComponentFactoryDependency { export class ComponentFactoryDependency {
constructor( constructor(
@ -83,15 +82,13 @@ export class NgModuleCompiler {
} }
} }
class _InjectorBuilder implements ClassBuilder { class _InjectorBuilder {
fields: o.ClassField[] = [];
getters: o.ClassGetter[] = [];
methods: o.ClassMethod[] = [];
ctorStmts: o.Statement[] = [];
private _tokens: CompileTokenMetadata[] = []; private _tokens: CompileTokenMetadata[] = [];
private _instances = new Map<any, o.Expression>(); private _instances = new Map<any, o.Expression>();
private _fields: o.ClassField[] = [];
private _createStmts: o.Statement[] = []; private _createStmts: o.Statement[] = [];
private _destroyStmts: o.Statement[] = []; private _destroyStmts: o.Statement[] = [];
private _getters: o.ClassGetter[] = [];
constructor( constructor(
private _ngModuleMeta: CompileNgModuleMetadata, private _ngModuleMeta: CompileNgModuleMetadata,
@ -139,23 +136,26 @@ class _InjectorBuilder implements ClassBuilder {
), ),
]; ];
var parentArgs = [ var ctor = new o.ClassMethod(
o.variable(InjectorProps.parent.name), null,
o.literalArr( [new o.FnParam(
this._entryComponentFactories.map((componentFactory) => o.importExpr(componentFactory))), InjectorProps.parent.name, o.importType(resolveIdentifier(Identifiers.Injector)))],
o.literalArr(this._bootstrapComponentFactories.map( [o.SUPER_EXPR
(componentFactory) => o.importExpr(componentFactory))) .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 injClassName = `${this._ngModuleMeta.type.name}Injector`; var injClassName = `${this._ngModuleMeta.type.name}Injector`;
return createClassStmt({ return new o.ClassStmt(
name: injClassName, injClassName, o.importExpr(
ctorParams: [new o.FnParam( resolveIdentifier(Identifiers.NgModuleInjector),
InjectorProps.parent.name, o.importType(resolveIdentifier(Identifiers.Injector)))], [o.importType(this._ngModuleMeta.type)]),
parent: o.importExpr( this._fields, this._getters, ctor, methods);
resolveIdentifier(Identifiers.NgModuleInjector), [o.importType(this._ngModuleMeta.type)]),
parentArgs: parentArgs,
builders: [{methods}, this]
});
} }
private _getProviderValue(provider: CompileProviderMetadata): o.Expression { private _getProviderValue(provider: CompileProviderMetadata): o.Expression {
@ -194,11 +194,11 @@ class _InjectorBuilder implements ClassBuilder {
type = o.DYNAMIC_TYPE; type = o.DYNAMIC_TYPE;
} }
if (isEager) { 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()); this._createStmts.push(o.THIS_EXPR.prop(propName).set(resolvedProviderValueExpr).toStmt());
} else { } else {
var internalField = `_${propName}`; 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! // Note: Equals is important for JS so that it also checks the undefined case!
var getterStmts = [ var getterStmts = [
new o.IfStmt( new o.IfStmt(
@ -206,7 +206,7 @@ class _InjectorBuilder implements ClassBuilder {
[o.THIS_EXPR.prop(internalField).set(resolvedProviderValueExpr).toStmt()]), [o.THIS_EXPR.prop(internalField).set(resolvedProviderValueExpr).toStmt()]),
new o.ReturnStatement(o.THIS_EXPR.prop(internalField)) 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); return o.THIS_EXPR.prop(propName);
} }

View File

@ -13,7 +13,6 @@ import {AnimationParser} from './animation/animation_parser';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata';
import {DirectiveNormalizer} from './directive_normalizer'; import {DirectiveNormalizer} from './directive_normalizer';
import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from './directive_wrapper_compiler'; import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from './directive_wrapper_compiler';
import {ListWrapper, MapWrapper} from './facade/collection';
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers'; import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers';
import {CompileMetadataResolver} from './metadata_resolver'; import {CompileMetadataResolver} from './metadata_resolver';
import {NgModuleCompiler} from './ng_module_compiler'; import {NgModuleCompiler} from './ng_module_compiler';
@ -24,88 +23,28 @@ import {TemplateParser} from './template_parser/template_parser';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewCompileResult, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler'; import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewCompileResult, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler';
export class SourceModule { export class SourceModule {
constructor(public fileUrl: string, public moduleUrl: string, public source: string) {} constructor(public moduleUrl: string, public source: string) {}
} }
// Returns all the source files and a mapping from modules to directives export class NgModulesSummary {
export function analyzeNgModules( constructor(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean}, public ngModuleByDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
metadataResolver: CompileMetadataResolver): { public ngModules: CompileNgModuleMetadata[]) {}
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>, }
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>
} {
const {
ngModules: programNgModules,
pipesAndDirectives: programPipesOrDirectives,
} = _extractModulesAndPipesOrDirectives(programStaticSymbols, metadataResolver);
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>(); export function analyzeModules(
ngModules: StaticSymbol[], metadataResolver: CompileMetadataResolver) {
programNgModules.forEach(modMeta => { const ngModuleByDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
if (options.transitiveModules) { const modules: CompileNgModuleMetadata[] = [];
// 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) => { ngModuleMeta.declaredDirectives.forEach((dirMeta: CompileDirectiveMetadata) => {
const fileUrl = dirMeta.type.reference.filePath; ngModuleByDirective.set(dirMeta.type.reference, ngModuleMeta);
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 { export class OfflineCompiler {
@ -120,28 +59,19 @@ export class OfflineCompiler {
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter, private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
private _localeId: string, private _translationFormat: string) {} private _localeId: string, private _translationFormat: string) {}
analyzeModules(ngModules: StaticSymbol[]): NgModulesSummary {
return analyzeModules(ngModules, this._metadataResolver);
}
clearCache() { clearCache() {
this._directiveNormalizer.clearCache(); this._directiveNormalizer.clearCache();
this._metadataResolver.clearCache(); this._metadataResolver.clearCache();
} }
compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}): compile(
Promise<SourceModule[]> { moduleUrl: string, ngModulesSummary: NgModulesSummary, directives: StaticSymbol[],
const {ngModuleByPipeOrDirective, files} = ngModules: StaticSymbol[]): Promise<SourceModule[]> {
analyzeNgModules(staticSymbols, options, this._metadataResolver); const fileSuffix = _splitTypescriptSuffix(moduleUrl)[1];
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 statements: o.Statement[] = [];
const exportedVars: string[] = []; const exportedVars: string[] = [];
const outputSourceModules: SourceModule[] = []; const outputSourceModules: SourceModule[] = [];
@ -161,10 +91,9 @@ export class OfflineCompiler {
if (!compMeta.isComponent) { if (!compMeta.isComponent) {
return Promise.resolve(null); return Promise.resolve(null);
} }
const ngModule = ngModuleByPipeOrDirective.get(dirType); const ngModule = ngModulesSummary.ngModuleByDirective.get(dirType);
if (!ngModule) { if (!ngModule) {
throw new Error( throw new Error(`Cannot determine the module for component ${compMeta.type.name}!`);
`Internal Error: cannot determine the module for component ${compMeta.type.name}!`);
} }
return Promise return Promise
@ -177,8 +106,7 @@ export class OfflineCompiler {
// compile styles // compile styles
const stylesCompileResults = this._styleCompiler.compileComponent(compMeta); const stylesCompileResults = this._styleCompiler.compileComponent(compMeta);
stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => { stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => {
outputSourceModules.push( outputSourceModules.push(this._codgenStyles(compiledStyleSheet, fileSuffix));
this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix));
}); });
// compile components // compile components
@ -191,9 +119,8 @@ export class OfflineCompiler {
})) }))
.then(() => { .then(() => {
if (statements.length > 0) { if (statements.length > 0) {
const srcModule = this._codegenSourceModule( outputSourceModules.unshift(this._codegenSourceModule(
srcFileUrl, _ngfactoryModuleUrl(srcFileUrl), statements, exportedVars); _ngfactoryModuleUrl(moduleUrl), statements, exportedVars));
outputSourceModules.unshift(srcModule);
} }
return outputSourceModules; return outputSourceModules;
}); });
@ -282,21 +209,18 @@ export class OfflineCompiler {
return viewResult.viewFactoryVar; return viewResult.viewFactoryVar;
} }
private _codgenStyles( private _codgenStyles(stylesCompileResult: CompiledStylesheet, fileSuffix: string): SourceModule {
fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): SourceModule {
_resolveStyleStatements(stylesCompileResult, fileSuffix); _resolveStyleStatements(stylesCompileResult, fileSuffix);
return this._codegenSourceModule( return this._codegenSourceModule(
fileUrl, _stylesModuleUrl( _stylesModuleUrl(
stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix), stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix),
stylesCompileResult.statements, [stylesCompileResult.stylesVar]); stylesCompileResult.statements, [stylesCompileResult.stylesVar]);
} }
private _codegenSourceModule( private _codegenSourceModule(
fileUrl: string, moduleUrl: string, statements: o.Statement[], moduleUrl: string, statements: o.Statement[], exportedVars: string[]): SourceModule {
exportedVars: string[]): SourceModule {
return new SourceModule( return new SourceModule(
fileUrl, moduleUrl, moduleUrl, this._outputEmitter.emitStatements(moduleUrl, statements, exportedVars));
this._outputEmitter.emitStatements(moduleUrl, statements, exportedVars));
} }
} }
@ -358,28 +282,3 @@ function _splitTypescriptSuffix(path: string): string[] {
return [path, '']; 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};
}

View File

@ -1,60 +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 * 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[];
}

View File

@ -7,6 +7,7 @@
*/ */
import {ListWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import * as o from './output_ast'; import * as o from './output_ast';
@ -152,13 +153,13 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor {
if (isPresent(expr.builtin)) { if (isPresent(expr.builtin)) {
switch (expr.builtin) { switch (expr.builtin) {
case o.BuiltinMethod.ConcatArray: case o.BuiltinMethod.ConcatArray:
result = receiver.concat(...args); result = ListWrapper.concat(receiver, args[0]);
break; break;
case o.BuiltinMethod.SubscribeObservable: case o.BuiltinMethod.SubscribeObservable:
result = receiver.subscribe({next: args[0]}); result = receiver.subscribe({next: args[0]});
break; break;
case o.BuiltinMethod.Bind: case o.BuiltinMethod.Bind:
result = receiver.bind(...args); result = receiver.bind(args[0]);
break; break;
default: default:
throw new Error(`Unknown builtin method ${expr.builtin}`); throw new Error(`Unknown builtin method ${expr.builtin}`);

View File

@ -35,7 +35,7 @@ export enum ParseErrorLevel {
FATAL FATAL
} }
export class ParseError { export abstract class ParseError {
constructor( constructor(
public span: ParseSourceSpan, public msg: string, public span: ParseSourceSpan, public msg: string,
public level: ParseErrorLevel = ParseErrorLevel.FATAL) {} public level: ParseErrorLevel = ParseErrorLevel.FATAL) {}

View File

@ -8,8 +8,8 @@
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata} from './compile_metadata'; import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata} from './compile_metadata';
import {MapWrapper} from './facade/collection'; import {ListWrapper, MapWrapper} from './facade/collection';
import {isBlank, isPresent} from './facade/lang'; import {isBlank, isPresent, normalizeBlank} from './facade/lang';
import {Identifiers, resolveIdentifierToken} from './identifiers'; import {Identifiers, resolveIdentifierToken} from './identifiers';
import {ParseError, ParseSourceSpan} from './parse_util'; import {ParseError, ParseSourceSpan} from './parse_util';
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst} from './template_parser/template_ast'; import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst} from './template_parser/template_ast';
@ -91,9 +91,9 @@ export class ProviderElementContext {
get transformedDirectiveAsts(): DirectiveAst[] { get transformedDirectiveAsts(): DirectiveAst[] {
var sortedProviderTypes = this.transformProviders.map(provider => provider.token.identifier); var sortedProviderTypes = this.transformProviders.map(provider => provider.token.identifier);
var sortedDirectives = this._directiveAsts.slice(); var sortedDirectives = ListWrapper.clone(this._directiveAsts);
sortedDirectives.sort( ListWrapper.sort(
(dir1, dir2) => sortedProviderTypes.indexOf(dir1.directive.type) - sortedDirectives, (dir1, dir2) => sortedProviderTypes.indexOf(dir1.directive.type) -
sortedProviderTypes.indexOf(dir2.directive.type)); sortedProviderTypes.indexOf(dir2.directive.type));
return sortedDirectives; return sortedDirectives;
} }
@ -117,7 +117,7 @@ export class ProviderElementContext {
while (currentEl !== null) { while (currentEl !== null) {
queries = currentEl._contentQueries.get(token.reference); queries = currentEl._contentQueries.get(token.reference);
if (isPresent(queries)) { if (isPresent(queries)) {
result.push(...queries.filter((query) => query.descendants || distance <= 1)); ListWrapper.addAll(result, queries.filter((query) => query.descendants || distance <= 1));
} }
if (currentEl._directiveAsts.length > 0) { if (currentEl._directiveAsts.length > 0) {
distance++; distance++;
@ -126,7 +126,7 @@ export class ProviderElementContext {
} }
queries = this.viewContext.viewQueries.get(token.reference); queries = this.viewContext.viewQueries.get(token.reference);
if (isPresent(queries)) { if (isPresent(queries)) {
result.push(...queries); ListWrapper.addAll(result, queries);
} }
return result; return result;
} }
@ -194,8 +194,10 @@ export class ProviderElementContext {
eager: boolean = null): CompileDiDependencyMetadata { eager: boolean = null): CompileDiDependencyMetadata {
if (dep.isAttribute) { if (dep.isAttribute) {
var attrValue = this._attrs[dep.token.value]; var attrValue = this._attrs[dep.token.value];
return new CompileDiDependencyMetadata( return new CompileDiDependencyMetadata({isValue: true, value: normalizeBlank(attrValue)});
{isValue: true, value: attrValue == null ? null : attrValue}); }
if (isPresent(dep.query) || isPresent(dep.viewQuery)) {
return dep;
} }
if (isPresent(dep.token)) { if (isPresent(dep.token)) {
@ -487,7 +489,7 @@ function _resolveProviders(
targetProvidersByToken.set(provider.token.reference, resolvedProvider); targetProvidersByToken.set(provider.token.reference, resolvedProvider);
} else { } else {
if (!provider.multi) { if (!provider.multi) {
resolvedProvider.providers.length = 0; ListWrapper.clear(resolvedProvider.providers);
} }
resolvedProvider.providers.push(provider); resolvedProvider.providers.push(provider);
} }
@ -500,6 +502,11 @@ function _getViewQueries(component: CompileDirectiveMetadata): Map<any, CompileQ
if (isPresent(component.viewQueries)) { if (isPresent(component.viewQueries)) {
component.viewQueries.forEach((query) => _addQueryToTokenMap(viewQueries, query)); component.viewQueries.forEach((query) => _addQueryToTokenMap(viewQueries, query));
} }
component.type.diDeps.forEach((dep) => {
if (isPresent(dep.viewQuery)) {
_addQueryToTokenMap(viewQueries, dep.viewQuery);
}
});
return viewQueries; return viewQueries;
} }
@ -510,6 +517,11 @@ function _getContentQueries(directives: CompileDirectiveMetadata[]):
if (isPresent(directive.queries)) { if (isPresent(directive.queries)) {
directive.queries.forEach((query) => _addQueryToTokenMap(contentQueries, query)); directive.queries.forEach((query) => _addQueryToTokenMap(contentQueries, query));
} }
directive.type.diDeps.forEach((dep) => {
if (isPresent(dep.query)) {
_addQueryToTokenMap(contentQueries, dep.query);
}
});
}); });
return contentQueries; return contentQueries;
} }

View File

@ -328,12 +328,7 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry {
* 'NONE' security context, i.e. that they are safe inert string values. Only specific well known * 'NONE' security context, i.e. that they are safe inert string values. Only specific well known
* attack vectors are assigned their appropriate context. * attack vectors are assigned their appropriate context.
*/ */
securityContext(tagName: string, propName: string, isAttribute: boolean): SecurityContext { securityContext(tagName: string, propName: string): 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 // Make sure comparisons are case insensitive, so that case differences between attribute and
// property names do not have a security impact. // property names do not have a security impact.
tagName = tagName.toLowerCase(); tagName = tagName.toLowerCase();
@ -371,6 +366,4 @@ export class DomElementSchemaRegistry extends ElementSchemaRegistry {
return {error: false}; return {error: false};
} }
} }
allKnownElementNames(): string[] { return Object.keys(this._schema); }
} }

View File

@ -6,14 +6,12 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {SchemaMetadata, SecurityContext} from '@angular/core'; import {SchemaMetadata} from '@angular/core';
export abstract class ElementSchemaRegistry { export abstract class ElementSchemaRegistry {
abstract hasProperty(tagName: string, propName: string, schemaMetas: SchemaMetadata[]): boolean; abstract hasProperty(tagName: string, propName: string, schemaMetas: SchemaMetadata[]): boolean;
abstract hasElement(tagName: string, schemaMetas: SchemaMetadata[]): boolean; abstract hasElement(tagName: string, schemaMetas: SchemaMetadata[]): boolean;
abstract securityContext(elementName: string, propName: string, isAttribute: boolean): abstract securityContext(tagName: string, propName: string): any;
SecurityContext;
abstract allKnownElementNames(): string[];
abstract getMappedPropName(propName: string): string; abstract getMappedPropName(propName: string): string;
abstract getDefaultComponentElementName(): string; abstract getDefaultComponentElementName(): string;
abstract validateProperty(name: string): {error: boolean, msg?: string}; abstract validateProperty(name: string): {error: boolean, msg?: string};

View File

@ -135,12 +135,12 @@ export class SelectorMatcher {
return notMatcher; return notMatcher;
} }
private _elementMap = new Map<string, SelectorContext[]>(); private _elementMap: {[k: string]: SelectorContext[]} = {};
private _elementPartialMap = new Map<string, SelectorMatcher>(); private _elementPartialMap: {[k: string]: SelectorMatcher} = {};
private _classMap = new Map<string, SelectorContext[]>(); private _classMap: {[k: string]: SelectorContext[]} = {};
private _classPartialMap = new Map<string, SelectorMatcher>(); private _classPartialMap: {[k: string]: SelectorMatcher} = {};
private _attrValueMap = new Map<string, Map<string, SelectorContext[]>>(); private _attrValueMap: {[k: string]: {[k: string]: SelectorContext[]}} = {};
private _attrValuePartialMap = new Map<string, Map<string, SelectorMatcher>>(); private _attrValuePartialMap: {[k: string]: {[k: string]: SelectorMatcher}} = {};
private _listContexts: SelectorListContext[] = []; private _listContexts: SelectorListContext[] = [];
addSelectables(cssSelectors: CssSelector[], callbackCtxt?: any) { addSelectables(cssSelectors: CssSelector[], callbackCtxt?: any) {
@ -195,18 +195,18 @@ export class SelectorMatcher {
const value = attrs[i + 1]; const value = attrs[i + 1];
if (isTerminal) { if (isTerminal) {
const terminalMap = matcher._attrValueMap; const terminalMap = matcher._attrValueMap;
let terminalValuesMap = terminalMap.get(name); let terminalValuesMap = terminalMap[name];
if (!terminalValuesMap) { if (!terminalValuesMap) {
terminalValuesMap = new Map<string, SelectorContext[]>(); terminalValuesMap = {};
terminalMap.set(name, terminalValuesMap); terminalMap[name] = terminalValuesMap;
} }
this._addTerminal(terminalValuesMap, value, selectable); this._addTerminal(terminalValuesMap, value, selectable);
} else { } else {
let partialMap = matcher._attrValuePartialMap; let partialMap = matcher._attrValuePartialMap;
let partialValuesMap = partialMap.get(name); let partialValuesMap = partialMap[name];
if (!partialValuesMap) { if (!partialValuesMap) {
partialValuesMap = new Map<string, SelectorMatcher>(); partialValuesMap = {};
partialMap.set(name, partialValuesMap); partialMap[name] = partialValuesMap;
} }
matcher = this._addPartial(partialValuesMap, value); matcher = this._addPartial(partialValuesMap, value);
} }
@ -215,20 +215,20 @@ export class SelectorMatcher {
} }
private _addTerminal( private _addTerminal(
map: Map<string, SelectorContext[]>, name: string, selectable: SelectorContext) { map: {[k: string]: SelectorContext[]}, name: string, selectable: SelectorContext) {
let terminalList = map.get(name); let terminalList = map[name];
if (!terminalList) { if (!terminalList) {
terminalList = []; terminalList = [];
map.set(name, terminalList); map[name] = terminalList;
} }
terminalList.push(selectable); terminalList.push(selectable);
} }
private _addPartial(map: Map<string, SelectorMatcher>, name: string): SelectorMatcher { private _addPartial(map: {[k: string]: SelectorMatcher}, name: string): SelectorMatcher {
let matcher = map.get(name); let matcher = map[name];
if (!matcher) { if (!matcher) {
matcher = new SelectorMatcher(); matcher = new SelectorMatcher();
map.set(name, matcher); map[name] = matcher;
} }
return matcher; return matcher;
} }
@ -270,7 +270,7 @@ export class SelectorMatcher {
const name = attrs[i]; const name = attrs[i];
const value = attrs[i + 1]; const value = attrs[i + 1];
const terminalValuesMap = this._attrValueMap.get(name); const terminalValuesMap = this._attrValueMap[name];
if (value) { if (value) {
result = result =
this._matchTerminal(terminalValuesMap, '', cssSelector, matchedCallback) || result; this._matchTerminal(terminalValuesMap, '', cssSelector, matchedCallback) || result;
@ -278,7 +278,7 @@ export class SelectorMatcher {
result = result =
this._matchTerminal(terminalValuesMap, value, cssSelector, matchedCallback) || result; this._matchTerminal(terminalValuesMap, value, cssSelector, matchedCallback) || result;
const partialValuesMap = this._attrValuePartialMap.get(name); const partialValuesMap = this._attrValuePartialMap[name];
if (value) { if (value) {
result = this._matchPartial(partialValuesMap, '', cssSelector, matchedCallback) || result; result = this._matchPartial(partialValuesMap, '', cssSelector, matchedCallback) || result;
} }
@ -291,14 +291,14 @@ export class SelectorMatcher {
/** @internal */ /** @internal */
_matchTerminal( _matchTerminal(
map: Map<string, SelectorContext[]>, name: string, cssSelector: CssSelector, map: {[k: string]: SelectorContext[]}, name: string, cssSelector: CssSelector,
matchedCallback: (c: CssSelector, a: any) => void): boolean { matchedCallback: (c: CssSelector, a: any) => void): boolean {
if (!map || typeof name !== 'string') { if (!map || typeof name !== 'string') {
return false; return false;
} }
let selectables = map.get(name); let selectables = map[name];
const starSelectables = map.get('*'); const starSelectables = map['*'];
if (starSelectables) { if (starSelectables) {
selectables = selectables.concat(starSelectables); selectables = selectables.concat(starSelectables);
} }
@ -316,13 +316,13 @@ export class SelectorMatcher {
/** @internal */ /** @internal */
_matchPartial( _matchPartial(
map: Map<string, SelectorMatcher>, name: string, cssSelector: CssSelector, map: {[k: string]: SelectorMatcher}, name: string, cssSelector: CssSelector,
matchedCallback: (c: CssSelector, a: any) => void): boolean { matchedCallback: (c: CssSelector, a: any) => void): boolean {
if (!map || typeof name !== 'string') { if (!map || typeof name !== 'string') {
return false; return false;
} }
const nestedSelector = map.get(name); const nestedSelector = map[name];
if (!nestedSelector) { if (!nestedSelector) {
return false; return false;
} }

View File

@ -1,439 +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 {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();
}

View File

@ -63,8 +63,8 @@ export class AttrAst implements TemplateAst {
export class BoundElementPropertyAst implements TemplateAst { export class BoundElementPropertyAst implements TemplateAst {
constructor( constructor(
public name: string, public type: PropertyBindingType, public name: string, public type: PropertyBindingType,
public securityContext: SecurityContext, public needsRuntimeSecurityContext: boolean, public securityContext: SecurityContext, public value: AST, public unit: string,
public value: AST, public unit: string, public sourceSpan: ParseSourceSpan) {} public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any { visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitElementProperty(this, context); return visitor.visitElementProperty(this, context);
} }
@ -76,23 +76,19 @@ export class BoundElementPropertyAst implements TemplateAst {
* `(@trigger.phase)="callback($event)"`). * `(@trigger.phase)="callback($event)"`).
*/ */
export class BoundEventAst implements TemplateAst { export class BoundEventAst implements TemplateAst {
static calcFullName(name: string, target: string, phase: string): string {
if (target) {
return `${target}:${name}`;
} else if (phase) {
return `@${name}.${phase}`;
} else {
return name;
}
}
constructor( constructor(
public name: string, public target: string, public phase: string, public handler: AST, public name: string, public target: string, public phase: string, public handler: AST,
public sourceSpan: ParseSourceSpan) {} public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any { visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitEvent(this, context); return visitor.visitEvent(this, context);
} }
get fullName() { return BoundEventAst.calcFullName(this.name, this.target, this.phase); } get fullName() {
if (this.target) {
return `${this.target}:${this.name}`;
} else {
return this.name;
}
}
get isAnimation(): boolean { return !!this.phase; } get isAnimation(): boolean { return !!this.phase; }
} }

View File

@ -25,8 +25,8 @@ import {ProviderElementContext, ProviderViewContext} from '../provider_analyzer'
import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {CssSelector, SelectorMatcher} from '../selector'; import {CssSelector, SelectorMatcher} from '../selector';
import {isStyleUrlResolvable} from '../style_url_resolver'; 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 {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'; import {PreparsedElementType, preparseElement} from './template_preparser';
@ -56,11 +56,17 @@ const IDENT_BANANA_BOX_IDX = 8;
const IDENT_PROPERTY_IDX = 9; const IDENT_PROPERTY_IDX = 9;
const IDENT_EVENT_IDX = 10; const IDENT_EVENT_IDX = 10;
const ANIMATE_PROP_PREFIX = 'animate-';
const TEMPLATE_ELEMENT = 'template'; const TEMPLATE_ELEMENT = 'template';
const TEMPLATE_ATTR = 'template'; const TEMPLATE_ATTR = 'template';
const TEMPLATE_ATTR_PREFIX = '*'; const TEMPLATE_ATTR_PREFIX = '*';
const CLASS_ATTR = 'class'; 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]; const TEXT_CSS_SELECTOR = CssSelector.parse('*')[0];
/** /**
@ -129,20 +135,11 @@ export class TemplateParser {
const uniqPipes = removeIdentifierDuplicates(pipes); const uniqPipes = removeIdentifierDuplicates(pipes);
const providerViewContext = const providerViewContext =
new ProviderViewContext(component, htmlAstWithErrors.rootNodes[0].sourceSpan); 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( const parseVisitor = new TemplateParseVisitor(
providerViewContext, uniqDirectives, bindingParser, this._schemaRegistry, schemas, providerViewContext, uniqDirectives, uniqPipes, schemas, this._exprParser,
errors); this._schemaRegistry);
result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT); result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT);
errors.push(...providerViewContext.errors); errors.push(...parseVisitor.errors, ...providerViewContext.errors);
} else { } else {
result = []; result = [];
} }
@ -200,18 +197,130 @@ export class TemplateParser {
class TemplateParseVisitor implements html.Visitor { class TemplateParseVisitor implements html.Visitor {
selectorMatcher = new SelectorMatcher(); selectorMatcher = new SelectorMatcher();
errors: TemplateParseError[] = [];
directivesIndex = new Map<CompileDirectiveMetadata, number>(); directivesIndex = new Map<CompileDirectiveMetadata, number>();
ngContentCount: number = 0; ngContentCount: number = 0;
pipesByName: Map<string, CompilePipeMetadata> = new Map();
private _interpolationConfig: InterpolationConfig;
constructor( constructor(
public providerViewContext: ProviderViewContext, directives: CompileDirectiveMetadata[], public providerViewContext: ProviderViewContext, directives: CompileDirectiveMetadata[],
private _bindingParser: BindingParser, private _schemaRegistry: ElementSchemaRegistry, pipes: CompilePipeMetadata[], private _schemas: SchemaMetadata[], private _exprParser: Parser,
private _schemas: SchemaMetadata[], private _targetErrors: TemplateParseError[]) { private _schemaRegistry: ElementSchemaRegistry) {
const tempMeta = providerViewContext.component.template;
if (tempMeta && tempMeta.interpolation) {
this._interpolationConfig = {
start: tempMeta.interpolation[0],
end: tempMeta.interpolation[1]
};
}
directives.forEach((directive: CompileDirectiveMetadata, index: number) => { directives.forEach((directive: CompileDirectiveMetadata, index: number) => {
const selector = CssSelector.parse(directive.selector); const selector = CssSelector.parse(directive.selector);
this.selectorMatcher.addSelectables(selector, directive); this.selectorMatcher.addSelectables(selector, directive);
this.directivesIndex.set(directive, index); 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; } visitExpansion(expansion: html.Expansion, context: any): any { return null; }
@ -220,7 +329,7 @@ class TemplateParseVisitor implements html.Visitor {
visitText(text: html.Text, parent: ElementContext): any { visitText(text: html.Text, parent: ElementContext): any {
const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR); const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
const expr = this._bindingParser.parseInterpolation(text.value, text.sourceSpan); const expr = this._parseInterpolation(text.value, text.sourceSpan);
if (isPresent(expr)) { if (isPresent(expr)) {
return new BoundTextAst(expr, ngContentIndex, text.sourceSpan); return new BoundTextAst(expr, ngContentIndex, text.sourceSpan);
} else { } else {
@ -252,12 +361,13 @@ class TemplateParseVisitor implements html.Visitor {
} }
const matchableAttrs: string[][] = []; const matchableAttrs: string[][] = [];
const elementOrDirectiveProps: BoundProperty[] = []; const elementOrDirectiveProps: BoundElementOrDirectiveProperty[] = [];
const elementOrDirectiveRefs: ElementOrDirectiveRef[] = []; const elementOrDirectiveRefs: ElementOrDirectiveRef[] = [];
const elementVars: VariableAst[] = []; const elementVars: VariableAst[] = [];
const animationProps: BoundElementPropertyAst[] = [];
const events: BoundEventAst[] = []; const events: BoundEventAst[] = [];
const templateElementOrDirectiveProps: BoundProperty[] = []; const templateElementOrDirectiveProps: BoundElementOrDirectiveProperty[] = [];
const templateMatchableAttrs: string[][] = []; const templateMatchableAttrs: string[][] = [];
const templateElementVars: VariableAst[] = []; const templateElementVars: VariableAst[] = [];
@ -268,27 +378,16 @@ class TemplateParseVisitor implements html.Visitor {
element.attrs.forEach(attr => { element.attrs.forEach(attr => {
const hasBinding = this._parseAttr( const hasBinding = this._parseAttr(
isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events, isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, animationProps, events,
elementOrDirectiveRefs, elementVars); elementOrDirectiveRefs, elementVars);
let templateBindingsSource: string; const hasTemplateBinding = this._parseInlineTemplateBinding(
if (this._normalizeAttributeName(attr.name) == TEMPLATE_ATTR) { attr, templateMatchableAttrs, templateElementOrDirectiveProps, templateElementVars);
templateBindingsSource = attr.value;
} else if (attr.name.startsWith(TEMPLATE_ATTR_PREFIX)) { if (hasTemplateBinding && hasInlineTemplates) {
const key = attr.name.substring(TEMPLATE_ATTR_PREFIX.length); // remove the star this._reportError(
templateBindingsSource = (attr.value.length == 0) ? key : key + ' ' + attr.value; `Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with *`,
} attr.sourceSpan);
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) { if (!hasBinding && !hasTemplateBinding) {
@ -296,6 +395,10 @@ class TemplateParseVisitor implements html.Visitor {
attrs.push(this.visitAttribute(attr, null)); attrs.push(this.visitAttribute(attr, null));
matchableAttrs.push([attr.name, attr.value]); matchableAttrs.push([attr.name, attr.value]);
} }
if (hasTemplateBinding) {
hasInlineTemplates = true;
}
}); });
const elementCssSelector = createElementCssSelector(nodeName, matchableAttrs); const elementCssSelector = createElementCssSelector(nodeName, matchableAttrs);
@ -306,7 +409,8 @@ class TemplateParseVisitor implements html.Visitor {
isTemplateElement, element.name, directiveMetas, elementOrDirectiveProps, isTemplateElement, element.name, directiveMetas, elementOrDirectiveProps,
elementOrDirectiveRefs, element.sourceSpan, references); elementOrDirectiveRefs, element.sourceSpan, references);
const elementProps: BoundElementPropertyAst[] = const elementProps: BoundElementPropertyAst[] =
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts); this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts)
.concat(animationProps);
const isViewRoot = parent.isTemplateElement || hasInlineTemplates; const isViewRoot = parent.isTemplateElement || hasInlineTemplates;
const providerContext = new ProviderElementContext( const providerContext = new ProviderElementContext(
this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs, this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs,
@ -415,9 +519,39 @@ 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( private _parseAttr(
isTemplateElement: boolean, attr: html.Attribute, targetMatchableAttrs: string[][], isTemplateElement: boolean, attr: html.Attribute, targetMatchableAttrs: string[][],
targetProps: BoundProperty[], targetEvents: BoundEventAst[], targetProps: BoundElementOrDirectiveProperty[],
targetAnimationProps: BoundElementPropertyAst[], targetEvents: BoundEventAst[],
targetRefs: ElementOrDirectiveRef[], targetVars: VariableAst[]): boolean { targetRefs: ElementOrDirectiveRef[], targetVars: VariableAst[]): boolean {
const name = this._normalizeAttributeName(attr.name); const name = this._normalizeAttributeName(attr.name);
const value = attr.value; const value = attr.value;
@ -429,8 +563,9 @@ class TemplateParseVisitor implements html.Visitor {
if (bindParts !== null) { if (bindParts !== null) {
hasBinding = true; hasBinding = true;
if (isPresent(bindParts[KW_BIND_IDX])) { if (isPresent(bindParts[KW_BIND_IDX])) {
this._bindingParser.parsePropertyBinding( this._parsePropertyOrAnimation(
bindParts[IDENT_KW_IDX], value, false, srcSpan, targetMatchableAttrs, targetProps); bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetProps,
targetAnimationProps);
} else if (bindParts[KW_LET_IDX]) { } else if (bindParts[KW_LET_IDX]) {
if (isTemplateElement) { if (isTemplateElement) {
@ -445,42 +580,48 @@ class TemplateParseVisitor implements html.Visitor {
this._parseReference(identifier, value, srcSpan, targetRefs); this._parseReference(identifier, value, srcSpan, targetRefs);
} else if (bindParts[KW_ON_IDX]) { } else if (bindParts[KW_ON_IDX]) {
this._bindingParser.parseEvent( this._parseEventOrAnimationEvent(
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents); bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
} else if (bindParts[KW_BINDON_IDX]) { } else if (bindParts[KW_BINDON_IDX]) {
this._bindingParser.parsePropertyBinding( this._parsePropertyOrAnimation(
bindParts[IDENT_KW_IDX], value, false, srcSpan, targetMatchableAttrs, targetProps); bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetProps,
targetAnimationProps);
this._parseAssignmentEvent( this._parseAssignmentEvent(
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents); bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
} else if (bindParts[KW_AT_IDX]) { } else if (bindParts[KW_AT_IDX]) {
this._bindingParser.parseLiteralAttr( if (_isAnimationLabel(name) && isPresent(value) && value.length > 0) {
name, value, srcSpan, targetMatchableAttrs, targetProps); 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);
} else if (bindParts[IDENT_BANANA_BOX_IDX]) { } else if (bindParts[IDENT_BANANA_BOX_IDX]) {
this._bindingParser.parsePropertyBinding( this._parsePropertyOrAnimation(
bindParts[IDENT_BANANA_BOX_IDX], value, false, srcSpan, targetMatchableAttrs, bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, targetMatchableAttrs, targetProps,
targetProps); targetAnimationProps);
this._parseAssignmentEvent( this._parseAssignmentEvent(
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, targetMatchableAttrs, targetEvents); bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
} else if (bindParts[IDENT_PROPERTY_IDX]) { } else if (bindParts[IDENT_PROPERTY_IDX]) {
this._bindingParser.parsePropertyBinding( this._parsePropertyOrAnimation(
bindParts[IDENT_PROPERTY_IDX], value, false, srcSpan, targetMatchableAttrs, bindParts[IDENT_PROPERTY_IDX], value, srcSpan, targetMatchableAttrs, targetProps,
targetProps); targetAnimationProps);
} else if (bindParts[IDENT_EVENT_IDX]) { } else if (bindParts[IDENT_EVENT_IDX]) {
this._bindingParser.parseEvent( this._parseEventOrAnimationEvent(
bindParts[IDENT_EVENT_IDX], value, srcSpan, targetMatchableAttrs, targetEvents); bindParts[IDENT_EVENT_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
} }
} else { } else {
hasBinding = this._bindingParser.parsePropertyInterpolation( hasBinding =
name, value, srcSpan, targetMatchableAttrs, targetProps); this._parsePropertyInterpolation(name, value, srcSpan, targetMatchableAttrs, targetProps);
} }
if (!hasBinding) { if (!hasBinding) {
this._bindingParser.parseLiteralAttr(name, value, srcSpan, targetMatchableAttrs, targetProps); this._parseLiteralAttr(name, value, srcSpan, targetProps);
} }
return hasBinding; return hasBinding;
@ -509,13 +650,127 @@ class TemplateParseVisitor implements html.Visitor {
targetRefs.push(new ElementOrDirectiveRef(identifier, value, sourceSpan)); 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( private _parseAssignmentEvent(
name: string, expression: string, sourceSpan: ParseSourceSpan, name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) { targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
this._bindingParser.parseEvent( this._parseEventOrAnimationEvent(
`${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, targetEvents); `${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): private _parseDirectives(selectorMatcher: SelectorMatcher, elementCssSelector: CssSelector):
{directives: CompileDirectiveMetadata[], matchElement: boolean} { {directives: CompileDirectiveMetadata[], matchElement: boolean} {
// Need to sort the directives so that we get consistent results throughout, // Need to sort the directives so that we get consistent results throughout,
@ -538,7 +793,7 @@ class TemplateParseVisitor implements html.Visitor {
private _createDirectiveAsts( private _createDirectiveAsts(
isTemplateElement: boolean, elementName: string, directives: CompileDirectiveMetadata[], isTemplateElement: boolean, elementName: string, directives: CompileDirectiveMetadata[],
props: BoundProperty[], elementOrDirectiveRefs: ElementOrDirectiveRef[], props: BoundElementOrDirectiveProperty[], elementOrDirectiveRefs: ElementOrDirectiveRef[],
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] { elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] {
const matchedReferences = new Set<string>(); const matchedReferences = new Set<string>();
let component: CompileDirectiveMetadata = null; let component: CompileDirectiveMetadata = null;
@ -548,13 +803,12 @@ class TemplateParseVisitor implements html.Visitor {
if (directive.isComponent) { if (directive.isComponent) {
component = directive; component = directive;
} }
const hostProperties: BoundElementPropertyAst[] = [];
const hostEvents: BoundEventAst[] = [];
const directiveProperties: BoundDirectivePropertyAst[] = []; const directiveProperties: BoundDirectivePropertyAst[] = [];
const hostProperties = this._createDirectiveHostPropertyAsts(
this._bindingParser.createDirectiveHostPropertyAsts(directive, sourceSpan); elementName, directive.hostProperties, sourceSpan, hostProperties);
// Note: We need to check the host properties here as well, this._createDirectiveHostEventAsts(directive.hostListeners, sourceSpan, hostEvents);
// 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); this._createDirectivePropertyAsts(directive.inputs, props, directiveProperties);
elementOrDirectiveRefs.forEach((elOrDirRef) => { elementOrDirectiveRefs.forEach((elOrDirRef) => {
if ((elOrDirRef.value.length === 0 && directive.isComponent) || if ((elOrDirRef.value.length === 0 && directive.isComponent) ||
@ -585,11 +839,47 @@ class TemplateParseVisitor implements html.Visitor {
return directiveAsts; 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( private _createDirectivePropertyAsts(
directiveProperties: {[key: string]: string}, boundProps: BoundProperty[], directiveProperties: {[key: string]: string}, boundProps: BoundElementOrDirectiveProperty[],
targetBoundDirectiveProps: BoundDirectivePropertyAst[]) { targetBoundDirectiveProps: BoundDirectivePropertyAst[]) {
if (directiveProperties) { if (directiveProperties) {
const boundPropsByName = new Map<string, BoundProperty>(); const boundPropsByName = new Map<string, BoundElementOrDirectiveProperty>();
boundProps.forEach(boundProp => { boundProps.forEach(boundProp => {
const prevValue = boundPropsByName.get(boundProp.name); const prevValue = boundPropsByName.get(boundProp.name);
if (!prevValue || prevValue.isLiteral) { if (!prevValue || prevValue.isLiteral) {
@ -612,7 +902,7 @@ class TemplateParseVisitor implements html.Visitor {
} }
private _createElementPropertyAsts( private _createElementPropertyAsts(
elementName: string, props: BoundProperty[], elementName: string, props: BoundElementOrDirectiveProperty[],
directives: DirectiveAst[]): BoundElementPropertyAst[] { directives: DirectiveAst[]): BoundElementPropertyAst[] {
const boundElementProps: BoundElementPropertyAst[] = []; const boundElementProps: BoundElementPropertyAst[] = [];
const boundDirectivePropsIndex = new Map<string, BoundDirectivePropertyAst>(); const boundDirectivePropsIndex = new Map<string, BoundDirectivePropertyAst>();
@ -623,15 +913,98 @@ class TemplateParseVisitor implements html.Visitor {
}); });
}); });
props.forEach((prop: BoundProperty) => { props.forEach((prop: BoundElementOrDirectiveProperty) => {
if (!prop.isLiteral && !boundDirectivePropsIndex.get(prop.name)) { if (!prop.isLiteral && !boundDirectivePropsIndex.get(prop.name)) {
boundElementProps.push(this._bindingParser.createElementPropertyAst(elementName, prop)); boundElementProps.push(this._createElementPropertyAst(
elementName, prop.name, prop.expression, prop.sourceSpan));
} }
}); });
this._checkPropertiesInSchema(elementName, boundElementProps);
return 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[] { private _findComponentDirectives(directives: DirectiveAst[]): DirectiveAst[] {
return directives.filter(directive => directive.directive.isComponent); return directives.filter(directive => directive.directive.isComponent);
} }
@ -644,11 +1017,7 @@ class TemplateParseVisitor implements html.Visitor {
private _assertOnlyOneComponent(directives: DirectiveAst[], sourceSpan: ParseSourceSpan) { private _assertOnlyOneComponent(directives: DirectiveAst[], sourceSpan: ParseSourceSpan) {
const componentTypeNames = this._findComponentDirectiveNames(directives); const componentTypeNames = this._findComponentDirectiveNames(directives);
if (componentTypeNames.length > 1) { if (componentTypeNames.length > 1) {
this._reportError( this._reportError(`More than one component: ${componentTypeNames.join(',')}`, sourceSpan);
`More than one component matched on this element.\n` +
`Make sure that only one component's selector can match a given element.\n` +
`Conflicting components: ${componentTypeNames.join(',')}`,
sourceSpan);
} }
} }
@ -706,28 +1075,6 @@ 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 { class NonBindableVisitor implements html.Visitor {
@ -766,6 +1113,12 @@ class NonBindableVisitor implements html.Visitor {
visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any { return expansionCase; } 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 { class ElementOrDirectiveRef {
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {} constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
} }
@ -833,6 +1186,21 @@ function createElementCssSelector(elementName: string, matchableAttrs: string[][
const EMPTY_ELEMENT_CONTEXT = new ElementContext(true, new SelectorMatcher(), null, null); const EMPTY_ELEMENT_CONTEXT = new ElementContext(true, new SelectorMatcher(), null, null);
const NON_BINDABLE_VISITOR = new NonBindableVisitor(); 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 { function _isEmptyTextNode(node: html.Node): boolean {
return node instanceof html.Text && node.value.trim().length == 0; return node instanceof html.Text && node.value.trim().length == 0;
} }

View File

@ -6,7 +6,9 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {isBlank, isPrimitive, isStrictStringMap} from './facade/lang'; import {CompileTokenMetadata} from './compile_metadata';
import {isBlank, isPresent, isPrimitive, isStrictStringMap} from './facade/lang';
import * as o from './output/output_ast';
export const MODULE_SUFFIX = ''; export const MODULE_SUFFIX = '';
@ -70,6 +72,25 @@ export class ValueTransformer implements ValueVisitor {
visitOther(value: any, context: any): any { return value; } 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> { export class SyncAsyncResult<T> {
constructor(public syncResult: T, public asyncResult: Promise<T> = null) { constructor(public syncResult: T, public asyncResult: Promise<T> = null) {
if (!asyncResult) { if (!asyncResult) {

View File

@ -0,0 +1,15 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {TemplateAst} from '../template_parser/template_ast';
import {CompileNode} from './compile_element';
export class CompileBinding {
constructor(public node: CompileNode, public sourceAst: TemplateAst) {}
}

View File

@ -8,18 +8,18 @@
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileIdentifierMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata} from '../compile_metadata'; import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileIdentifierMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata} from '../compile_metadata';
import {createDiTokenExpression} from '../compiler_util/identifier_util'; import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
import {DirectiveWrapperCompiler, DirectiveWrapperExpressions} from '../directive_wrapper_compiler'; import {ListWrapper, MapWrapper} from '../facade/collection';
import {MapWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, identifierToken, resolveIdentifier, resolveIdentifierToken} from '../identifiers'; import {Identifiers, identifierToken, resolveIdentifier, resolveIdentifierToken} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {convertValueToOutputAst} from '../output/value_util'; import {convertValueToOutputAst} from '../output/value_util';
import {ProviderAst, ProviderAstType, ReferenceAst, TemplateAst} from '../template_parser/template_ast'; import {ProviderAst, ProviderAstType, ReferenceAst, TemplateAst} from '../template_parser/template_ast';
import {createDiTokenExpression} from '../util';
import {CompileMethod} from './compile_method'; import {CompileMethod} from './compile_method';
import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query'; import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query';
import {CompileView, CompileViewRootNode} from './compile_view'; import {CompileView} from './compile_view';
import {InjectMethodVars, ViewProperties} from './constants'; import {InjectMethodVars, ViewProperties} from './constants';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewFactoryDependency} from './deps'; import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewFactoryDependency} from './deps';
import {getPropertyInView, injectFromViewParentInjector} from './util'; import {getPropertyInView, injectFromViewParentInjector} from './util';
@ -49,8 +49,9 @@ export class CompileElement extends CompileNode {
private _queryCount = 0; private _queryCount = 0;
private _queries = new Map<any, CompileQuery[]>(); private _queries = new Map<any, CompileQuery[]>();
private _componentConstructorViewQueryLists: o.Expression[] = [];
public contentNodesByNgContentIndex: Array<CompileViewRootNode>[] = null; public contentNodesByNgContentIndex: Array<o.Expression>[] = null;
public embeddedView: CompileView; public embeddedView: CompileView;
public referenceTokens: {[key: string]: CompileTokenMetadata}; public referenceTokens: {[key: string]: CompileTokenMetadata};
@ -97,9 +98,6 @@ export class CompileElement extends CompileNode {
this.view.createMethod.addStmt(statement); this.view.createMethod.addStmt(statement);
this.appElement = o.THIS_EXPR.prop(fieldName); this.appElement = o.THIS_EXPR.prop(fieldName);
this.instances.set(resolveIdentifierToken(Identifiers.AppElement).reference, this.appElement); this.instances.set(resolveIdentifierToken(Identifiers.AppElement).reference, this.appElement);
if (this.hasViewContainer) {
this.view.viewContainerAppElements.push(this.appElement);
}
} }
private _createComponentFactoryResolver() { private _createComponentFactoryResolver() {
@ -190,7 +188,8 @@ export class CompileElement extends CompileNode {
{name: DirectiveWrapperCompiler.dirWrapperClassName(provider.useClass)}); {name: DirectiveWrapperCompiler.dirWrapperClassName(provider.useClass)});
this._targetDependencies.push( this._targetDependencies.push(
new DirectiveWrapperDependency(provider.useClass, directiveWrapperIdentifier)); new DirectiveWrapperDependency(provider.useClass, directiveWrapperIdentifier));
return DirectiveWrapperExpressions.create(directiveWrapperIdentifier, depsExpr); return o.importExpr(directiveWrapperIdentifier)
.instantiate(depsExpr, o.importType(directiveWrapperIdentifier));
} else { } else {
return o.importExpr(provider.useClass) return o.importExpr(provider.useClass)
.instantiate(depsExpr, o.importType(provider.useClass)); .instantiate(depsExpr, o.importType(provider.useClass));
@ -205,8 +204,7 @@ export class CompileElement extends CompileNode {
resolvedProvider.eager, this); resolvedProvider.eager, this);
if (isDirectiveWrapper) { if (isDirectiveWrapper) {
this.directiveWrapperInstance.set(resolvedProvider.token.reference, instance); this.directiveWrapperInstance.set(resolvedProvider.token.reference, instance);
this.instances.set( this.instances.set(resolvedProvider.token.reference, instance.prop('context'));
resolvedProvider.token.reference, DirectiveWrapperExpressions.context(instance));
} else { } else {
this.instances.set(resolvedProvider.token.reference, instance); this.instances.set(resolvedProvider.token.reference, instance);
} }
@ -220,8 +218,9 @@ export class CompileElement extends CompileNode {
var queriesWithReads: _QueryWithRead[] = []; var queriesWithReads: _QueryWithRead[] = [];
MapWrapper.values(this._resolvedProviders).forEach((resolvedProvider) => { MapWrapper.values(this._resolvedProviders).forEach((resolvedProvider) => {
var queriesForProvider = this._getQueriesFor(resolvedProvider.token); var queriesForProvider = this._getQueriesFor(resolvedProvider.token);
queriesWithReads.push( ListWrapper.addAll(
...queriesForProvider.map(query => new _QueryWithRead(query, resolvedProvider.token))); queriesWithReads,
queriesForProvider.map(query => new _QueryWithRead(query, resolvedProvider.token)));
}); });
Object.keys(this.referenceTokens).forEach(varName => { Object.keys(this.referenceTokens).forEach(varName => {
var token = this.referenceTokens[varName]; var token = this.referenceTokens[varName];
@ -233,8 +232,9 @@ export class CompileElement extends CompileNode {
} }
this.view.locals.set(varName, varValue); this.view.locals.set(varName, varValue);
var varToken = new CompileTokenMetadata({value: varName}); var varToken = new CompileTokenMetadata({value: varName});
queriesWithReads.push( ListWrapper.addAll(
...this._getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken))); queriesWithReads,
this._getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
}); });
queriesWithReads.forEach((queryWithRead) => { queriesWithReads.forEach((queryWithRead) => {
var value: o.Expression; var value: o.Expression;
@ -256,9 +256,16 @@ export class CompileElement extends CompileNode {
}); });
if (isPresent(this.component)) { if (isPresent(this.component)) {
var componentConstructorViewQueryList = isPresent(this.component) ?
o.literalArr(this._componentConstructorViewQueryLists) :
o.NULL_EXPR;
var compExpr = isPresent(this.getComponent()) ? this.getComponent() : o.NULL_EXPR; var compExpr = isPresent(this.getComponent()) ? this.getComponent() : o.NULL_EXPR;
this.view.createMethod.addStmt( this.view.createMethod.addStmt(
this.appElement.callMethod('initComponent', [compExpr, this._compViewExpr]).toStmt()); this.appElement
.callMethod(
'initComponent',
[compExpr, componentConstructorViewQueryList, this._compViewExpr])
.toStmt());
} }
} }
@ -285,7 +292,7 @@ export class CompileElement extends CompileNode {
this.view.createMethod, this.view.updateContentQueriesMethod))); this.view.createMethod, this.view.updateContentQueriesMethod)));
} }
addContentNode(ngContentIndex: number, nodeExpr: CompileViewRootNode) { addContentNode(ngContentIndex: number, nodeExpr: o.Expression) {
this.contentNodesByNgContentIndex[ngContentIndex].push(nodeExpr); this.contentNodesByNgContentIndex[ngContentIndex].push(nodeExpr);
} }
@ -308,7 +315,8 @@ export class CompileElement extends CompileNode {
while (!currentEl.isNull()) { while (!currentEl.isNull()) {
queries = currentEl._queries.get(token.reference); queries = currentEl._queries.get(token.reference);
if (isPresent(queries)) { if (isPresent(queries)) {
result.push(...queries.filter((query) => query.meta.descendants || distance <= 1)); ListWrapper.addAll(
result, queries.filter((query) => query.meta.descendants || distance <= 1));
} }
if (currentEl._directives.length > 0) { if (currentEl._directives.length > 0) {
distance++; distance++;
@ -317,7 +325,7 @@ export class CompileElement extends CompileNode {
} }
queries = this.view.componentView.viewQueries.get(token.reference); queries = this.view.componentView.viewQueries.get(token.reference);
if (isPresent(queries)) { if (isPresent(queries)) {
result.push(...queries); ListWrapper.addAll(result, queries);
} }
return result; return result;
} }
@ -334,6 +342,20 @@ export class CompileElement extends CompileNode {
private _getLocalDependency( private _getLocalDependency(
requestingProviderType: ProviderAstType, dep: CompileDiDependencyMetadata): o.Expression { requestingProviderType: ProviderAstType, dep: CompileDiDependencyMetadata): o.Expression {
var result: o.Expression = null; var result: o.Expression = null;
// constructor content query
if (!result && isPresent(dep.query)) {
result = this._addQuery(dep.query, null).queryList;
}
// constructor view query
if (!result && isPresent(dep.viewQuery)) {
result = createQueryList(
dep.viewQuery, null,
`_viewQuery_${dep.viewQuery.selectors[0].name}_${this.nodeIndex}_${this._componentConstructorViewQueryLists.length}`,
this.view);
this._componentConstructorViewQueryLists.push(result);
}
if (isPresent(dep.token)) { if (isPresent(dep.token)) {
// access builtins with special visibility // access builtins with special visibility
if (!result) { if (!result) {

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ListWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {TemplateAst} from '../template_parser/template_ast'; import {TemplateAst} from '../template_parser/template_ast';
@ -65,8 +66,6 @@ export class CompileMethod {
this._newState = new _DebugState(nodeIndex, templateAst); this._newState = new _DebugState(nodeIndex, templateAst);
} }
push(...stmts: o.Statement[]) { this.addStmts(stmts); }
addStmt(stmt: o.Statement) { addStmt(stmt: o.Statement) {
this._updateDebugContextIfNeeded(); this._updateDebugContextIfNeeded();
this._bodyStatements.push(stmt); this._bodyStatements.push(stmt);
@ -74,7 +73,7 @@ export class CompileMethod {
addStmts(stmts: o.Statement[]) { addStmts(stmts: o.Statement[]) {
this._updateDebugContextIfNeeded(); this._updateDebugContextIfNeeded();
this._bodyStatements.push(...stmts); ListWrapper.addAll(this._bodyStatements, stmts);
} }
finish(): o.Statement[] { return this._bodyStatements; } finish(): o.Statement[] { return this._bodyStatements; }

View File

@ -8,12 +8,11 @@
import {CompilePipeMetadata} from '../compile_metadata'; import {CompilePipeMetadata} from '../compile_metadata';
import {createPureProxy} from '../compiler_util/identifier_util';
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from '../identifiers'; import {Identifiers, resolveIdentifier, resolveIdentifierToken} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {CompileView} from './compile_view'; import {CompileView} from './compile_view';
import {getPropertyInView, injectFromViewParentInjector} from './util'; import {createPureProxy, getPropertyInView, injectFromViewParentInjector} from './util';
export class CompilePipe { export class CompilePipe {
static call(view: CompileView, name: string, args: o.Expression[]): o.Expression { static call(view: CompileView, name: string, args: o.Expression[]): o.Expression {
@ -66,8 +65,7 @@ export class CompilePipe {
createPureProxy( createPureProxy(
pipeInstanceSeenFromPureProxy.prop('transform') pipeInstanceSeenFromPureProxy.prop('transform')
.callMethod(o.BuiltinMethod.Bind, [pipeInstanceSeenFromPureProxy]), .callMethod(o.BuiltinMethod.Bind, [pipeInstanceSeenFromPureProxy]),
args.length, purePipeProxyInstance, args.length, purePipeProxyInstance, callingView);
{fields: callingView.fields, ctorStmts: callingView.createMethod});
return o.importExpr(resolveIdentifier(Identifiers.castByValue)) return o.importExpr(resolveIdentifier(Identifiers.castByValue))
.callFn([purePipeProxyInstance, pipeInstanceSeenFromPureProxy.prop('transform')]) .callFn([purePipeProxyInstance, pipeInstanceSeenFromPureProxy.prop('transform')])
.callFn(args); .callFn(args);

View File

@ -8,46 +8,33 @@
import {AnimationEntryCompileResult} from '../animation/animation_compiler'; import {AnimationEntryCompileResult} from '../animation/animation_compiler';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompilePipeMetadata} from '../compile_metadata'; 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 {CompilerConfig} from '../config';
import {MapWrapper} from '../facade/collection'; import {ListWrapper, MapWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ViewType} from '../private_import_core'; import {ViewType} from '../private_import_core';
import {CompileBinding} from './compile_binding';
import {CompileElement, CompileNode} from './compile_element'; import {CompileElement, CompileNode} from './compile_element';
import {CompileMethod} from './compile_method'; import {CompileMethod} from './compile_method';
import {CompilePipe} from './compile_pipe'; import {CompilePipe} from './compile_pipe';
import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query'; import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query';
import {getPropertyInView, getViewFactoryName} from './util'; import {EventHandlerVars} from './constants';
import {NameResolver} from './expression_converter';
export enum CompileViewRootNodeType { import {createPureProxy, getPropertyInView, getViewFactoryName} from './util';
Node,
ViewContainer,
NgContent
}
export class CompileViewRootNode {
constructor(
public type: CompileViewRootNodeType, public expr: o.Expression,
public ngContentIndex?: number) {}
}
export class CompileView implements NameResolver { export class CompileView implements NameResolver {
public viewType: ViewType; public viewType: ViewType;
public viewQueries: Map<any, CompileQuery[]>; public viewQueries: Map<any, CompileQuery[]>;
public viewChildren: o.Expression[] = [];
public nodes: CompileNode[] = []; public nodes: CompileNode[] = [];
// root nodes or AppElements for ViewContainers
public rootNodesOrAppElements: o.Expression[] = [];
public rootNodes: CompileViewRootNode[] = []; public bindings: CompileBinding[] = [];
public lastRenderNode: o.Expression = o.NULL_EXPR;
public viewContainerAppElements: o.Expression[] = [];
public classStatements: o.Statement[] = [];
public createMethod: CompileMethod; public createMethod: CompileMethod;
public animationBindingsMethod: CompileMethod; public animationBindingsMethod: CompileMethod;
public injectorGetMethod: CompileMethod; public injectorGetMethod: CompileMethod;
@ -60,12 +47,12 @@ export class CompileView implements NameResolver {
public afterViewLifecycleCallbacksMethod: CompileMethod; public afterViewLifecycleCallbacksMethod: CompileMethod;
public destroyMethod: CompileMethod; public destroyMethod: CompileMethod;
public detachMethod: CompileMethod; public detachMethod: CompileMethod;
public methods: o.ClassMethod[] = []; public eventHandlerMethods: o.ClassMethod[] = [];
public ctorStmts: o.Statement[] = [];
public fields: o.ClassField[] = []; public fields: o.ClassField[] = [];
public getters: o.ClassGetter[] = []; public getters: o.ClassGetter[] = [];
public disposables: o.Expression[] = []; public disposables: o.Expression[] = [];
public subscriptions: o.Expression[] = [];
public componentView: CompileView; public componentView: CompileView;
public purePipes = new Map<string, CompilePipe>(); public purePipes = new Map<string, CompilePipe>();
@ -115,12 +102,22 @@ export class CompileView implements NameResolver {
var viewQueries = new Map<any, CompileQuery[]>(); var viewQueries = new Map<any, CompileQuery[]>();
if (this.viewType === ViewType.COMPONENT) { if (this.viewType === ViewType.COMPONENT) {
var directiveInstance = o.THIS_EXPR.prop('context'); var directiveInstance = o.THIS_EXPR.prop('context');
this.component.viewQueries.forEach((queryMeta, queryIndex) => { ListWrapper.forEachWithIndex(this.component.viewQueries, (queryMeta, queryIndex) => {
var propName = `_viewQuery_${queryMeta.selectors[0].name}_${queryIndex}`; var propName = `_viewQuery_${queryMeta.selectors[0].name}_${queryIndex}`;
var queryList = createQueryList(queryMeta, directiveInstance, propName, this); var queryList = createQueryList(queryMeta, directiveInstance, propName, this);
var query = new CompileQuery(queryMeta, queryList, directiveInstance, this); var query = new CompileQuery(queryMeta, queryList, directiveInstance, this);
addQueryToTokenMap(viewQueries, query); addQueryToTokenMap(viewQueries, query);
}); });
var constructorViewQueryCount = 0;
this.component.type.diDeps.forEach((dep) => {
if (isPresent(dep.viewQuery)) {
var queryList = o.THIS_EXPR.prop('declarationAppElement')
.prop('componentConstructorViewQueries')
.key(o.literal(constructorViewQueryCount++));
var query = new CompileQuery(dep.viewQuery, queryList, null, this);
addQueryToTokenMap(viewQueries, query);
}
});
} }
this.viewQueries = viewQueries; this.viewQueries = viewQueries;
templateVariableBindings.forEach( templateVariableBindings.forEach(
@ -152,6 +149,48 @@ 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() { afterNodes() {
MapWrapper.values(this.viewQueries) MapWrapper.values(this.viewQueries)
.forEach( .forEach(

View File

@ -8,32 +8,81 @@
import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core'; import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
import {createEnumExpression} from '../compiler_util/identifier_util'; import {CompileIdentifierMetadata} from '../compile_metadata';
import {Identifiers} from '../identifiers'; import {Identifiers, resolveEnumIdentifier, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ChangeDetectorStatus, ViewType} from '../private_import_core'; import {ChangeDetectorStatus, ViewType} from '../private_import_core';
function _enumExpression(classIdentifier: CompileIdentifierMetadata, name: string): o.Expression {
return o.importExpr(resolveEnumIdentifier(classIdentifier, name));
}
export class ViewTypeEnum { export class ViewTypeEnum {
static fromValue(value: ViewType): o.Expression { static fromValue(value: ViewType): o.Expression {
return createEnumExpression(Identifiers.ViewType, value); 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}`);
}
} }
} }
export class ViewEncapsulationEnum { export class ViewEncapsulationEnum {
static fromValue(value: ViewEncapsulation): o.Expression { static fromValue(value: ViewEncapsulation): o.Expression {
return createEnumExpression(Identifiers.ViewEncapsulation, value); 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}`);
}
} }
} }
export class ChangeDetectionStrategyEnum { export class ChangeDetectionStrategyEnum {
static fromValue(value: ChangeDetectionStrategy): o.Expression { static fromValue(value: ChangeDetectionStrategy): o.Expression {
return createEnumExpression(Identifiers.ChangeDetectionStrategy, value); 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}`);
}
} }
} }
export class ChangeDetectorStatusEnum { export class ChangeDetectorStatusEnum {
static fromValue(value: ChangeDetectorStatusEnum): o.Expression { static fromValue(value: ChangeDetectorStatusEnum): o.Expression {
return createEnumExpression(Identifiers.ChangeDetectorStatus, value); 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}`);
}
} }
} }
@ -45,9 +94,12 @@ export class ViewConstructorVars {
export class ViewProperties { export class ViewProperties {
static renderer = o.THIS_EXPR.prop('renderer'); static renderer = o.THIS_EXPR.prop('renderer');
static projectableNodes = o.THIS_EXPR.prop('projectableNodes');
static viewUtils = o.THIS_EXPR.prop('viewUtils'); static viewUtils = o.THIS_EXPR.prop('viewUtils');
} }
export class EventHandlerVars { static event = o.variable('$event'); }
export class InjectMethodVars { export class InjectMethodVars {
static token = o.variable('token'); static token = o.variable('token');
static requestNodeIndex = o.variable('requestNodeIndex'); static requestNodeIndex = o.variable('requestNodeIndex');
@ -58,4 +110,5 @@ export class DetectChangesVars {
static throwOnChange = o.variable(`throwOnChange`); static throwOnChange = o.variable(`throwOnChange`);
static changes = o.variable(`changes`); static changes = o.variable(`changes`);
static changed = o.variable(`changed`); static changed = o.variable(`changed`);
static valUnwrapper = o.variable(`valUnwrapper`);
} }

View File

@ -6,134 +6,195 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {EventHandlerVars, convertActionBinding} from '../compiler_util/expression_converter'; import {CompileDirectiveMetadata} from '../compile_metadata';
import {createInlineArray} from '../compiler_util/identifier_util';
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
import {MapWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers'; import {identifierToken} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast'; import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast';
import {CompileBinding} from './compile_binding';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
import {CompileMethod} from './compile_method'; import {CompileMethod} from './compile_method';
import {ViewProperties} from './constants'; import {EventHandlerVars, ViewProperties} from './constants';
import {getHandleEventMethodName} from './util'; import {convertCdStatementToIr} from './expression_converter';
export function bindOutputs( export class CompileEventListener {
boundEvents: BoundEventAst[], directives: DirectiveAst[], compileElement: CompileElement, private _method: CompileMethod;
bindToRenderer: boolean): boolean { private _hasComponentHostListener: boolean = false;
const usedEvents = collectEvents(boundEvents, directives); private _methodName: string;
if (!usedEvents.size) { private _eventParam: o.FnParam;
return false; private _actionResultExprs: o.Expression[] = [];
}
if (bindToRenderer) {
subscribeToRenderEvents(usedEvents, compileElement);
}
subscribeToDirectiveEvents(usedEvents, directives, compileElement);
generateHandleEventMethod(boundEvents, directives, compileElement);
return true;
}
function collectEvents( static getOrCreate(
boundEvents: BoundEventAst[], directives: DirectiveAst[]): Map<string, EventSummary> { compileElement: CompileElement, eventTarget: string, eventName: string, eventPhase: string,
const usedEvents = new Map<string, EventSummary>(); targetEventListeners: CompileEventListener[]): CompileEventListener {
boundEvents.forEach((event) => { usedEvents.set(event.fullName, event); }); var listener = targetEventListeners.find(
directives.forEach((dirAst) => { listener => listener.eventTarget == eventTarget && listener.eventName == eventName &&
dirAst.hostEvents.forEach((event) => { usedEvents.set(event.fullName, event); }); listener.eventPhase == eventPhase);
}); if (!listener) {
return usedEvents; listener = new CompileEventListener(
} compileElement, eventTarget, eventName, eventPhase, targetEventListeners.length);
targetEventListeners.push(listener);
function subscribeToRenderEvents(
usedEvents: Map<string, EventSummary>, compileElement: CompileElement) {
const eventAndTargetExprs: o.Expression[] = [];
usedEvents.forEach((event) => {
if (!event.phase) {
eventAndTargetExprs.push(o.literal(event.name), o.literal(event.target));
} }
}); return listener;
if (eventAndTargetExprs.length) { }
const disposableVar = o.variable(`disposable_${compileElement.view.disposables.length}`);
compileElement.view.disposables.push(disposableVar); get methodName() { return this._methodName; }
compileElement.view.createMethod.addStmt( get isAnimation() { return !!this.eventPhase; }
disposableVar
.set(o.importExpr(resolveIdentifier(Identifiers.subscribeToRenderElement)).callFn([ constructor(
o.THIS_EXPR, compileElement.renderNode, createInlineArray(eventAndTargetExprs), public compileElement: CompileElement, public eventTarget: string, public eventName: string,
handleEventExpr(compileElement) public eventPhase: string, listenerIndex: number) {
])) this._method = new CompileMethod(compileElement.view);
.toDeclStmt(o.FUNCTION_TYPE, [o.StmtModifier.Private])); this._methodName =
`_handle_${sanitizeEventName(eventName)}_${compileElement.nodeIndex}_${listenerIndex}`;
this._eventParam = new o.FnParam(
EventHandlerVars.event.name,
o.importType(this.compileElement.view.genConfig.renderTypes.renderEvent));
}
addAction(
hostEvent: BoundEventAst, directive: CompileDirectiveMetadata,
directiveInstance: o.Expression) {
if (isPresent(directive) && directive.isComponent) {
this._hasComponentHostListener = true;
}
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]);
}
}
this._method.addStmts(actionStmts);
}
finishMethod() {
var markPathToRootStart = this._hasComponentHostListener ?
this.compileElement.appElement.prop('componentView') :
o.THIS_EXPR;
var resultExpr: o.Expression = o.literal(true);
this._actionResultExprs.forEach((expr) => { resultExpr = resultExpr.and(expr); });
var stmts =
(<o.Statement[]>[markPathToRootStart.callMethod('markPathToRootAsCheckOnce', []).toStmt()])
.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._methodName, [this._eventParam], stmts, o.BOOL_TYPE, [o.StmtModifier.Private]));
}
listenToRenderer() {
var listenExpr: o.Expression;
var eventListener = o.THIS_EXPR.callMethod(
'eventHandler',
[o.THIS_EXPR.prop(this._methodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])]);
if (isPresent(this.eventTarget)) {
listenExpr = ViewProperties.renderer.callMethod(
'listenGlobal', [o.literal(this.eventTarget), o.literal(this.eventName), eventListener]);
} else {
listenExpr = ViewProperties.renderer.callMethod(
'listen', [this.compileElement.renderNode, o.literal(this.eventName), eventListener]);
}
var disposable = o.variable(`disposable_${this.compileElement.view.disposables.length}`);
this.compileElement.view.disposables.push(disposable);
// private is fine here as no child view will reference the event handler...
this.compileElement.view.createMethod.addStmt(
disposable.set(listenExpr).toDeclStmt(o.FUNCTION_TYPE, [o.StmtModifier.Private]));
}
listenToAnimation(animationTransitionVar: o.ReadVarExpr): o.Statement {
const callbackMethod = this.eventPhase == 'start' ? 'onStart' : 'onDone';
return animationTransitionVar
.callMethod(
callbackMethod,
[o.THIS_EXPR.prop(this.methodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])])
.toStmt();
}
listenToDirective(directiveInstance: o.Expression, observablePropName: string) {
var subscription = o.variable(`subscription_${this.compileElement.view.subscriptions.length}`);
this.compileElement.view.subscriptions.push(subscription);
var eventListener = o.THIS_EXPR.callMethod(
'eventHandler',
[o.THIS_EXPR.prop(this._methodName).callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR])]);
this.compileElement.view.createMethod.addStmt(
subscription
.set(directiveInstance.prop(observablePropName)
.callMethod(o.BuiltinMethod.SubscribeObservable, [eventListener]))
.toDeclStmt(null, [o.StmtModifier.Final]));
} }
} }
function subscribeToDirectiveEvents( export function collectEventListeners(
usedEvents: Map<string, EventSummary>, directives: DirectiveAst[], hostEvents: BoundEventAst[], dirs: DirectiveAst[],
compileElement: CompileElement) { compileElement: CompileElement): CompileEventListener[] {
const usedEventNames = MapWrapper.keys(usedEvents); const eventListeners: CompileEventListener[] = [];
directives.forEach((dirAst) => {
const dirWrapper = compileElement.directiveWrapperInstance.get(dirAst.directive.type.reference); hostEvents.forEach((hostEvent) => {
compileElement.view.createMethod.addStmts(DirectiveWrapperExpressions.subscribe( compileElement.view.bindings.push(new CompileBinding(compileElement, hostEvent));
dirAst.directive, dirAst.hostProperties, usedEventNames, dirWrapper, o.THIS_EXPR, var listener = CompileEventListener.getOrCreate(
handleEventExpr(compileElement))); compileElement, hostEvent.target, hostEvent.name, hostEvent.phase, eventListeners);
listener.addAction(hostEvent, null, null);
});
dirs.forEach((directiveAst) => {
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);
});
});
eventListeners.forEach((listener) => listener.finishMethod());
return eventListeners;
}
export function bindDirectiveOutputs(
directiveAst: DirectiveAst, directiveInstance: o.Expression,
eventListeners: CompileEventListener[]) {
Object.keys(directiveAst.directive.outputs).forEach(observablePropName => {
const eventName = directiveAst.directive.outputs[observablePropName];
eventListeners.filter(listener => listener.eventName == eventName).forEach((listener) => {
listener.listenToDirective(directiveInstance, observablePropName);
});
}); });
} }
function generateHandleEventMethod( export function bindRenderOutputs(eventListeners: CompileEventListener[]) {
boundEvents: BoundEventAst[], directives: DirectiveAst[], compileElement: CompileElement) { eventListeners.forEach(listener => {
const hasComponentHostListener = // the animation listeners are handled within property_binder.ts to
directives.some((dirAst) => dirAst.hostEvents.some((event) => dirAst.directive.isComponent)); // allow them to be placed next to the animation factory statements
if (!listener.isAnimation) {
const markPathToRootStart = listener.listenToRenderer();
hasComponentHostListener ? compileElement.appElement.prop('componentView') : o.THIS_EXPR;
const handleEventStmts = new CompileMethod(compileElement.view);
handleEventStmts.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst);
handleEventStmts.push(markPathToRootStart.callMethod('markPathToRootAsCheckOnce', []).toStmt());
const eventNameVar = o.variable('eventName');
const resultVar = o.variable('result');
handleEventStmts.push(resultVar.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE));
directives.forEach((dirAst, dirIdx) => {
const dirWrapper = compileElement.directiveWrapperInstance.get(dirAst.directive.type.reference);
if (dirAst.hostEvents.length > 0) {
handleEventStmts.push(
resultVar
.set(DirectiveWrapperExpressions
.handleEvent(
dirAst.hostEvents, dirWrapper, eventNameVar, EventHandlerVars.event)
.and(resultVar))
.toStmt());
} }
}); });
boundEvents.forEach((renderEvent, renderEventIdx) => {
const evalResult = convertActionBinding(
compileElement.view, compileElement.view, compileElement.view.componentContext,
renderEvent.handler, `sub_${renderEventIdx}`);
const trueStmts = evalResult.stmts;
if (evalResult.preventDefault) {
trueStmts.push(resultVar.set(evalResult.preventDefault.and(resultVar)).toStmt());
}
// TODO(tbosch): convert this into a `switch` once our OutputAst supports it.
handleEventStmts.push(
new o.IfStmt(eventNameVar.equals(o.literal(renderEvent.fullName)), trueStmts));
});
handleEventStmts.push(new o.ReturnStatement(resultVar));
compileElement.view.methods.push(new o.ClassMethod(
getHandleEventMethodName(compileElement.nodeIndex),
[
new o.FnParam(eventNameVar.name, o.STRING_TYPE),
new o.FnParam(EventHandlerVars.event.name, o.DYNAMIC_TYPE)
],
handleEventStmts.finish(), o.BOOL_TYPE));
} }
function handleEventExpr(compileElement: CompileElement) { function convertStmtIntoExpression(stmt: o.Statement): o.Expression {
const handleEventMethodName = getHandleEventMethodName(compileElement.nodeIndex); if (stmt instanceof o.ExpressionStatement) {
return o.THIS_EXPR.callMethod('eventHandler', [o.THIS_EXPR.prop(handleEventMethodName)]); return stmt.expr;
} else if (stmt instanceof o.ReturnStatement) {
return stmt.value;
}
return null;
} }
type EventSummary = { function sanitizeEventName(name: string): string {
name: string, return name.replace(/[^a-zA-Z_]/g, '_');
target: string, }
phase: string
}

View File

@ -10,130 +10,52 @@
import * as cdAst from '../expression_parser/ast'; import * as cdAst from '../expression_parser/ast';
import {isBlank, isPresent} from '../facade/lang'; import {isBlank, isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
import {ClassBuilder} from '../output/class_builder';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {createPureProxy} from './identifier_util';
const VAL_UNWRAPPER_VAR = o.variable(`valUnwrapper`);
export interface NameResolver { export interface NameResolver {
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression; callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression;
getLocal(name: string): o.Expression; getLocal(name: string): o.Expression;
createLiteralArray(values: o.Expression[]): o.Expression;
createLiteralMap(values: Array<Array<string|o.Expression>>): o.Expression;
} }
export class EventHandlerVars { static event = o.variable('$event'); } export class ExpressionWithWrappedValueInfo {
export class ConvertPropertyBindingResult {
constructor( constructor(
public stmts: o.Statement[], public currValExpr: o.Expression, public expression: o.Expression, public needsValueUnwrapper: boolean,
public forceUpdate: o.Expression) {} public temporaryCount: number) {}
} }
/** export function convertCdExpressionToIr(
* Converts the given expression AST into an executable output AST, assuming the expression is nameResolver: NameResolver, implicitReceiver: o.Expression, expression: cdAst.AST,
* used in a property binding. valueUnwrapper: o.ReadVarExpr, bindingIndex: number): ExpressionWithWrappedValueInfo {
*/ const visitor = new _AstToIrVisitor(nameResolver, implicitReceiver, valueUnwrapper, bindingIndex);
export function convertPropertyBinding( const irAst: o.Expression = expression.visit(visitor, _Mode.Expression);
builder: ClassBuilder, nameResolver: NameResolver, implicitReceiver: o.Expression, return new ExpressionWithWrappedValueInfo(
expression: cdAst.AST, bindingId: string): ConvertPropertyBindingResult { irAst, visitor.needsValueUnwrapper, visitor.temporaryCount);
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 class ConvertActionBindingResult { export function convertCdStatementToIr(
constructor(public stmts: o.Statement[], public preventDefault: o.ReadVarExpr) {} 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;
} }
/** function temporaryName(bindingIndex: number, temporaryNumber: number): string {
* Converts the given expression AST into an executable output AST, assuming the expression is return `tmp_${bindingIndex}_${temporaryNumber}`;
* 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 {
* Creates variables that are shared by multiple calls to `convertActionBinding` / return new o.DeclareVarStmt(temporaryName(bindingIndex, temporaryNumber), o.NULL_EXPR);
* `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( function prependTemporaryDecls(
temporaryCount: number, bindingId: string, statements: o.Statement[]) { temporaryCount: number, bindingIndex: number, statements: o.Statement[]) {
for (let i = temporaryCount - 1; i >= 0; i--) { for (let i = temporaryCount - 1; i >= 0; i--) {
statements.unshift(temporaryDeclaration(bindingId, i)); statements.unshift(temporaryDeclaration(bindingIndex, i));
} }
} }
@ -170,9 +92,8 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
public temporaryCount: number = 0; public temporaryCount: number = 0;
constructor( constructor(
private _builder: ClassBuilder, private _nameResolver: NameResolver, private _nameResolver: NameResolver, private _implicitReceiver: o.Expression,
private _implicitReceiver: o.Expression, private _valueUnwrapper: o.ReadVarExpr, private _valueUnwrapper: o.ReadVarExpr, private bindingIndex: number) {}
private bindingId: string, private isAction: boolean) {}
visitBinary(ast: cdAst.Binary, mode: _Mode): any { visitBinary(ast: cdAst.Binary, mode: _Mode): any {
var op: o.BinaryOperator; var op: o.BinaryOperator;
@ -249,9 +170,6 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
const input = this.visit(ast.exp, _Mode.Expression); const input = this.visit(ast.exp, _Mode.Expression);
const args = this.visitAll(ast.args, _Mode.Expression); const args = this.visitAll(ast.args, _Mode.Expression);
const value = this._nameResolver.callPipe(ast.name, input, args); 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; this.needsValueUnwrapper = true;
return convertToStatementIfNeeded(mode, this._valueUnwrapper.callMethod('unwrap', [value])); return convertToStatementIfNeeded(mode, this._valueUnwrapper.callMethod('unwrap', [value]));
} }
@ -291,10 +209,8 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
} }
visitLiteralArray(ast: cdAst.LiteralArray, mode: _Mode): any { visitLiteralArray(ast: cdAst.LiteralArray, mode: _Mode): any {
const parts = this.visitAll(ast.expressions, mode); return convertToStatementIfNeeded(
const literalArr = mode, this._nameResolver.createLiteralArray(this.visitAll(ast.expressions, mode)));
this.isAction ? o.literalArr(parts) : createCachedLiteralArray(this._builder, parts);
return convertToStatementIfNeeded(mode, literalArr);
} }
visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any { visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any {
@ -302,22 +218,13 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
for (let i = 0; i < ast.keys.length; i++) { for (let i = 0; i < ast.keys.length; i++) {
parts.push([ast.keys[i], this.visit(ast.values[i], _Mode.Expression)]); parts.push([ast.keys[i], this.visit(ast.values[i], _Mode.Expression)]);
} }
const literalMap = return convertToStatementIfNeeded(mode, this._nameResolver.createLiteralMap(parts));
this.isAction ? o.literalMap(parts) : createCachedLiteralMap(this._builder, parts);
return convertToStatementIfNeeded(mode, literalMap);
} }
visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any { visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any {
return convertToStatementIfNeeded(mode, o.literal(ast.value)); 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 { visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any {
const leftMostSafe = this.leftMostSafeNode(ast); const leftMostSafe = this.leftMostSafeNode(ast);
if (leftMostSafe) { if (leftMostSafe) {
@ -327,7 +234,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
let result: any = null; let result: any = null;
let receiver = this.visit(ast.receiver, _Mode.Expression); let receiver = this.visit(ast.receiver, _Mode.Expression);
if (receiver === this._implicitReceiver) { if (receiver === this._implicitReceiver) {
var varExpr = this._getLocal(ast.name); var varExpr = this._nameResolver.getLocal(ast.name);
if (isPresent(varExpr)) { if (isPresent(varExpr)) {
result = varExpr.callFn(args); result = varExpr.callFn(args);
} }
@ -351,7 +258,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
let result: any = null; let result: any = null;
var receiver = this.visit(ast.receiver, _Mode.Expression); var receiver = this.visit(ast.receiver, _Mode.Expression);
if (receiver === this._implicitReceiver) { if (receiver === this._implicitReceiver) {
result = this._getLocal(ast.name); result = this._nameResolver.getLocal(ast.name);
} }
if (isBlank(result)) { if (isBlank(result)) {
result = receiver.prop(ast.name); result = receiver.prop(ast.name);
@ -363,7 +270,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
visitPropertyWrite(ast: cdAst.PropertyWrite, mode: _Mode): any { visitPropertyWrite(ast: cdAst.PropertyWrite, mode: _Mode): any {
let receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression); let receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression);
if (receiver === this._implicitReceiver) { if (receiver === this._implicitReceiver) {
var varExpr = this._getLocal(ast.name); var varExpr = this._nameResolver.getLocal(ast.name);
if (isPresent(varExpr)) { if (isPresent(varExpr)) {
throw new Error('Cannot assign to a reference or variable!'); throw new Error('Cannot assign to a reference or variable!');
} }
@ -552,12 +459,12 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
private allocateTemporary(): o.ReadVarExpr { private allocateTemporary(): o.ReadVarExpr {
const tempNumber = this._currentTemporary++; const tempNumber = this._currentTemporary++;
this.temporaryCount = Math.max(this._currentTemporary, this.temporaryCount); this.temporaryCount = Math.max(this._currentTemporary, this.temporaryCount);
return new o.ReadVarExpr(temporaryName(this.bindingId, tempNumber)); return new o.ReadVarExpr(temporaryName(this.bindingIndex, tempNumber));
} }
private releaseTemporary(temporary: o.ReadVarExpr) { private releaseTemporary(temporary: o.ReadVarExpr) {
this._currentTemporary--; this._currentTemporary--;
if (temporary.name != temporaryName(this.bindingId, this._currentTemporary)) { if (temporary.name != temporaryName(this.bindingIndex, this._currentTemporary)) {
throw new Error(`Temporary ${temporary.name} released out of order`); throw new Error(`Temporary ${temporary.name} released out of order`);
} }
} }
@ -570,69 +477,3 @@ function flattenStatements(arg: any, output: o.Statement[]) {
output.push(arg); 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;
}

View File

@ -7,15 +7,16 @@
*/ */
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata';
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {LifecycleHooks} from '../private_import_core'; import {LifecycleHooks} from '../private_import_core';
import {DirectiveAst, ProviderAst, ProviderAstType} from '../template_parser/template_ast'; import {DirectiveAst, ProviderAst} from '../template_parser/template_ast';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
import {CompileView} from './compile_view'; import {CompileView} from './compile_view';
import {DetectChangesVars} from './constants'; import {DetectChangesVars} from './constants';
var STATE_IS_NEVER_CHECKED = o.THIS_EXPR.prop('numberOfChecks').identical(new o.LiteralExpr(0)); var STATE_IS_NEVER_CHECKED = o.THIS_EXPR.prop('numberOfChecks').identical(new o.LiteralExpr(0));
var NOT_THROW_ON_CHANGES = o.not(DetectChangesVars.throwOnChange); var NOT_THROW_ON_CHANGES = o.not(DetectChangesVars.throwOnChange);
@ -55,24 +56,11 @@ export function bindDirectiveAfterViewLifecycleCallbacks(
} }
} }
export function bindDirectiveWrapperLifecycleCallbacks(
dir: DirectiveAst, directiveWrapperIntance: o.Expression, compileElement: CompileElement) {
compileElement.view.destroyMethod.addStmts(
DirectiveWrapperExpressions.ngOnDestroy(dir.directive, directiveWrapperIntance));
compileElement.view.detachMethod.addStmts(DirectiveWrapperExpressions.ngOnDetach(
dir.hostProperties, directiveWrapperIntance, o.THIS_EXPR,
compileElement.component ? compileElement.appElement.prop('componentView') : o.THIS_EXPR,
compileElement.renderNode));
}
export function bindInjectableDestroyLifecycleCallbacks( export function bindInjectableDestroyLifecycleCallbacks(
provider: ProviderAst, providerInstance: o.Expression, compileElement: CompileElement) { provider: ProviderAst, providerInstance: o.Expression, compileElement: CompileElement) {
var onDestroyMethod = compileElement.view.destroyMethod; var onDestroyMethod = compileElement.view.destroyMethod;
onDestroyMethod.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst); onDestroyMethod.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst);
if (provider.providerType !== ProviderAstType.Directive && if (provider.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1) {
provider.providerType !== ProviderAstType.Component &&
provider.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1) {
onDestroyMethod.addStmt(providerInstance.callMethod('ngOnDestroy', []).toStmt()); onDestroyMethod.addStmt(providerInstance.callMethod('ngOnDestroy', []).toStmt());
} }
} }

View File

@ -8,144 +8,284 @@
import {SecurityContext} from '@angular/core'; 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 {triggerAnimation, writeToRenderer} from '../compiler_util/render_util';
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
import * as cdAst from '../expression_parser/ast'; import * as cdAst from '../expression_parser/ast';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {EMPTY_STATE as EMPTY_ANIMATION_STATE, LifecycleHooks, isDefaultChangeDetectionStrategy} from '../private_import_core'; 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 {BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, PropertyBindingType} from '../template_parser/template_ast';
import {camelCaseToDashCase} from '../util'; import {camelCaseToDashCase} from '../util';
import {CompileBinding} from './compile_binding';
import {CompileElement, CompileNode} from './compile_element'; import {CompileElement, CompileNode} from './compile_element';
import {CompileMethod} from './compile_method'; import {CompileMethod} from './compile_method';
import {CompileView} from './compile_view'; import {CompileView} from './compile_view';
import {DetectChangesVars, ViewProperties} from './constants'; import {DetectChangesVars, ViewProperties} from './constants';
import {getHandleEventMethodName} from './util'; import {CompileEventListener} from './event_binder';
import {convertCdExpressionToIr, temporaryDeclaration} from './expression_converter';
export function bindRenderText( function createBindFieldExpr(exprIndex: number): o.ReadPropExpr {
boundText: BoundTextAst, compileNode: CompileNode, view: CompileView): void { return o.THIS_EXPR.prop(`_expr_${exprIndex}`);
const valueField = createCheckBindingField(view); }
const evalResult = convertPropertyBinding(
view, view, view.componentContext, boundText.value, valueField.bindingId); function createCurrValueExpr(exprIndex: number): o.ReadVarExpr {
if (!evalResult) { 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
return null; return null;
} }
view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileNode.nodeIndex, boundText); if (checkExpression.temporaryCount) {
view.detectChangesRenderPropertiesMethod.addStmts(createCheckBindingStmt( for (let i = 0; i < checkExpression.temporaryCount; i++) {
evalResult, valueField.expression, DetectChangesVars.throwOnChange, method.addStmt(temporaryDeclaration(bindingIndex, i));
[o.THIS_EXPR.prop('renderer') }
.callMethod('setText', [compileNode.renderNode, evalResult.currValExpr]) }
.toStmt()]));
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);
}
} }
export function bindRenderInputs( function bind(
boundProps: BoundElementPropertyAst[], hasEvents: boolean, compileElement: CompileElement) { 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,
[o.THIS_EXPR.prop('renderer')
.callMethod('setText', [compileNode.renderNode, currValExpr])
.toStmt()],
view.detectChangesRenderPropertiesMethod, bindingIndex);
}
function bindAndWriteToRenderer(
boundProps: BoundElementPropertyAst[], context: o.Expression, compileElement: CompileElement,
isHostProp: boolean, eventListeners: CompileEventListener[]) {
var view = compileElement.view; var view = compileElement.view;
var renderNode = compileElement.renderNode; var renderNode = compileElement.renderNode;
boundProps.forEach((boundProp) => { boundProps.forEach((boundProp) => {
const bindingField = createCheckBindingField(view); var bindingIndex = view.bindings.length;
view.bindings.push(new CompileBinding(compileElement, boundProp));
view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileElement.nodeIndex, boundProp); view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileElement.nodeIndex, boundProp);
const evalResult = convertPropertyBinding( var fieldExpr = createBindFieldExpr(bindingIndex);
view, view, compileElement.view.componentContext, boundProp.value, bindingField.bindingId); var currValExpr = createCurrValueExpr(bindingIndex);
if (!evalResult) { var oldRenderValue: o.Expression = sanitizedValue(boundProp, fieldExpr);
return; var renderValue: o.Expression = sanitizedValue(boundProp, currValExpr);
} var updateStmts: o.Statement[] = [];
var checkBindingStmts: o.Statement[] = [];
var compileMethod = view.detectChangesRenderPropertiesMethod; var compileMethod = view.detectChangesRenderPropertiesMethod;
switch (boundProp.type) { switch (boundProp.type) {
case PropertyBindingType.Property: 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: 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: case PropertyBindingType.Class:
updateStmts.push(
o.THIS_EXPR.prop('renderer')
.callMethod('setElementClass', [renderNode, o.literal(boundProp.name), renderValue])
.toStmt());
break;
case PropertyBindingType.Style: case PropertyBindingType.Style:
checkBindingStmts.push(...writeToRenderer( var strValue: o.Expression = renderValue.callMethod('toString', []);
o.THIS_EXPR, boundProp, renderNode, evalResult.currValExpr, if (isPresent(boundProp.unit)) {
view.genConfig.logBindingUpdate)); 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());
break; break;
case PropertyBindingType.Animation: case PropertyBindingType.Animation:
compileMethod = view.animationBindingsMethod; compileMethod = view.animationBindingsMethod;
const {updateStmts, detachStmts} = triggerAnimation( const detachStmts: o.Statement[] = [];
o.THIS_EXPR, o.THIS_EXPR, boundProp,
(hasEvents ? o.THIS_EXPR.prop(getHandleEventMethodName(compileElement.nodeIndex)) : const animationName = boundProp.name;
o.importExpr(resolveIdentifier(Identifiers.noop))) const targetViewExpr: o.Expression =
.callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR]), isHostProp ? compileElement.appElement.prop('componentView') : o.THIS_EXPR;
compileElement.renderNode, evalResult.currValExpr, bindingField.expression);
checkBindingStmts.push(...updateStmts); const animationFnExpr =
targetViewExpr.prop('componentType').prop('animations').key(o.literal(animationName));
// it's important to normalize the void value as `void` explicitly
// so that the styles data can be obtained from the stringmap
const emptyStateValue = o.literal(EMPTY_ANIMATION_STATE);
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]))
.toDeclStmt());
eventListeners.forEach(listener => {
if (listener.isAnimation && listener.eventName === animationName) {
let animationStmt = listener.listenToAnimation(animationTransitionVar);
updateStmts.push(animationStmt);
detachStmts.push(animationStmt);
}
});
view.detachMethod.addStmts(detachStmts); view.detachMethod.addStmts(detachStmts);
break; break;
} }
compileMethod.addStmts(createCheckBindingStmt(
evalResult, bindingField.expression, DetectChangesVars.throwOnChange, checkBindingStmts)); bind(
view, currValExpr, fieldExpr, boundProp.value, context, updateStmts, compileMethod,
view.bindings.length);
}); });
} }
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 {
bindAndWriteToRenderer(
boundProps, compileElement.view.componentContext, compileElement, false, eventListeners);
}
export function bindDirectiveHostProps( export function bindDirectiveHostProps(
directiveAst: DirectiveAst, directiveWrapperInstance: o.Expression, directiveAst: DirectiveAst, directiveInstance: o.Expression, compileElement: CompileElement,
compileElement: CompileElement, elementName: string, eventListeners: CompileEventListener[]): void {
schemaRegistry: ElementSchemaRegistry): void { bindAndWriteToRenderer(
// We need to provide the SecurityContext for properties that could need sanitization. directiveAst.hostProperties, directiveInstance, compileElement, true, eventListeners);
const runtimeSecurityCtxExprs =
directiveAst.hostProperties.filter(boundProp => boundProp.needsRuntimeSecurityContext)
.map((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}`);
}
return createEnumExpression(Identifiers.SecurityContext, ctx);
});
compileElement.view.detectChangesRenderPropertiesMethod.addStmts(
DirectiveWrapperExpressions.checkHost(
directiveAst.hostProperties, directiveWrapperInstance, o.THIS_EXPR,
compileElement.component ? compileElement.appElement.prop('componentView') : o.THIS_EXPR,
compileElement.renderNode, DetectChangesVars.throwOnChange, runtimeSecurityCtxExprs));
} }
export function bindDirectiveInputs( export function bindDirectiveInputs(
directiveAst: DirectiveAst, directiveWrapperInstance: o.Expression, dirIndex: number, directiveAst: DirectiveAst, directiveWrapperInstance: o.Expression,
compileElement: CompileElement) { compileElement: CompileElement) {
var view = compileElement.view; var view = compileElement.view;
var detectChangesInInputsMethod = view.detectChangesInInputsMethod; var detectChangesInInputsMethod = view.detectChangesInInputsMethod;
detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst); detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, compileElement.sourceAst);
directiveAst.inputs.forEach((input, inputIdx) => { directiveAst.inputs.forEach((input) => {
// Note: We can't use `fields.length` here, as we are not adding a field! var bindingIndex = view.bindings.length;
const bindingId = `${compileElement.nodeIndex}_${dirIndex}_${inputIdx}`; view.bindings.push(new CompileBinding(compileElement, input));
detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, input); detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, input);
const evalResult = var currValExpr = createCurrValueExpr(bindingIndex);
convertPropertyBinding(view, view, view.componentContext, input.value, bindingId); const evalResult = evalCdAst(
view, currValExpr, input.value, view.componentContext, detectChangesInInputsMethod,
bindingIndex);
if (!evalResult) { if (!evalResult) {
return; return;
} }
detectChangesInInputsMethod.addStmts(evalResult.stmts); detectChangesInInputsMethod.addStmt(directiveWrapperInstance
detectChangesInInputsMethod.addStmt( .callMethod(
directiveWrapperInstance `check_${input.directiveName}`,
.callMethod( [
`check_${input.directiveName}`, currValExpr, DetectChangesVars.throwOnChange,
[ evalResult.forceUpdate || o.literal(false)
evalResult.currValExpr, DetectChangesVars.throwOnChange, ])
evalResult.forceUpdate || o.literal(false) .toStmt());
])
.toStmt());
}); });
var isOnPushComp = directiveAst.directive.isComponent && var isOnPushComp = directiveAst.directive.isComponent &&
!isDefaultChangeDetectionStrategy(directiveAst.directive.changeDetection); !isDefaultChangeDetectionStrategy(directiveAst.directive.changeDetection);
let directiveDetectChangesExpr = DirectiveWrapperExpressions.ngDoCheck( let directiveDetectChangesExpr = directiveWrapperInstance.callMethod(
directiveWrapperInstance, o.THIS_EXPR, compileElement.renderNode, 'detectChangesInternal',
DetectChangesVars.throwOnChange); [o.THIS_EXPR, compileElement.renderNode, DetectChangesVars.throwOnChange]);
const directiveDetectChangesStmt = isOnPushComp ? const directiveDetectChangesStmt = isOnPushComp ?
new o.IfStmt(directiveDetectChangesExpr, [compileElement.appElement.prop('componentView') new o.IfStmt(directiveDetectChangesExpr, [compileElement.appElement.prop('componentView')
.callMethod('markAsCheckOnce', []) .callMethod('markAsCheckOnce', [])
@ -153,3 +293,10 @@ export function bindDirectiveInputs(
directiveDetectChangesExpr.toStmt(); directiveDetectChangesExpr.toStmt();
detectChangesInInputsMethod.addStmt(directiveDetectChangesStmt); 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();
}

View File

@ -8,10 +8,10 @@
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata';
import {createDiTokenExpression} from '../compiler_util/identifier_util';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, resolveIdentifier} from '../identifiers'; import {Identifiers, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {createDiTokenExpression} from '../util';
import {CompileView} from './compile_view'; import {CompileView} from './compile_view';
@ -69,6 +69,38 @@ export function getViewFactoryName(
return `viewFactory_${component.type.name}${embeddedTemplateIndex}`; return `viewFactory_${component.type.name}${embeddedTemplateIndex}`;
} }
export function getHandleEventMethodName(elementIndex: number): string { export function createFlatArray(expressions: o.Expression[]): o.Expression {
return `handleEvent_${elementIndex}`; var lastNonArrayExpressions: o.Expression[] = [];
} var result: o.Expression = o.literalArr([]);
for (var i = 0; i < expressions.length; i++) {
var expr = expressions[i];
if (expr.type instanceof o.ArrayType) {
if (lastNonArrayExpressions.length > 0) {
result =
result.callMethod(o.BuiltinMethod.ConcatArray, [o.literalArr(lastNonArrayExpressions)]);
lastNonArrayExpressions = [];
}
result = result.callMethod(o.BuiltinMethod.ConcatArray, [expr]);
} else {
lastNonArrayExpressions.push(expr);
}
}
if (lastNonArrayExpressions.length > 0) {
result =
result.callMethod(o.BuiltinMethod.ConcatArray, [o.literalArr(lastNonArrayExpressions)]);
}
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());
}

View File

@ -6,18 +6,16 @@
* found in the LICENSE file at https://angular.io/license * 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 {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'; import {CompileElement} from './compile_element';
import {CompileView} from './compile_view'; import {CompileView} from './compile_view';
import {bindOutputs} from './event_binder'; import {CompileEventListener, bindDirectiveOutputs, bindRenderOutputs, collectEventListeners} from './event_binder';
import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindDirectiveWrapperLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder'; import {bindDirectiveAfterContentLifecycleCallbacks, bindDirectiveAfterViewLifecycleCallbacks, bindInjectableDestroyLifecycleCallbacks, bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder'; import {bindDirectiveHostProps, bindDirectiveInputs, bindRenderInputs, bindRenderText} from './property_binder';
export function bindView( export function bindView(view: CompileView, parsedTemplate: TemplateAst[]): void {
view: CompileView, parsedTemplate: TemplateAst[], schemaRegistry: ElementSchemaRegistry): void { var visitor = new ViewBinderVisitor(view);
var visitor = new ViewBinderVisitor(view, schemaRegistry);
templateVisitAll(visitor, parsedTemplate); templateVisitAll(visitor, parsedTemplate);
view.pipes.forEach( view.pipes.forEach(
(pipe) => { bindPipeDestroyLifecycleCallbacks(pipe.meta, pipe.instance, pipe.view); }); (pipe) => { bindPipeDestroyLifecycleCallbacks(pipe.meta, pipe.instance, pipe.view); });
@ -26,7 +24,7 @@ export function bindView(
class ViewBinderVisitor implements TemplateAstVisitor { class ViewBinderVisitor implements TemplateAstVisitor {
private _nodeIndex: number = 0; private _nodeIndex: number = 0;
constructor(public view: CompileView, private _schemaRegistry: ElementSchemaRegistry) {} constructor(public view: CompileView) {}
visitBoundText(ast: BoundTextAst, parent: CompileElement): any { visitBoundText(ast: BoundTextAst, parent: CompileElement): any {
var node = this.view.nodes[this._nodeIndex++]; var node = this.view.nodes[this._nodeIndex++];
@ -42,29 +40,30 @@ class ViewBinderVisitor implements TemplateAstVisitor {
visitElement(ast: ElementAst, parent: CompileElement): any { visitElement(ast: ElementAst, parent: CompileElement): any {
var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++]; var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
const hasEvents = bindOutputs(ast.outputs, ast.directives, compileElement, true); var eventListeners: CompileEventListener[] = [];
bindRenderInputs(ast.inputs, hasEvents, compileElement); collectEventListeners(ast.outputs, ast.directives, compileElement).forEach(entry => {
ast.directives.forEach((directiveAst, dirIndex) => { eventListeners.push(entry);
});
bindRenderInputs(ast.inputs, compileElement, eventListeners);
bindRenderOutputs(eventListeners);
ast.directives.forEach((directiveAst) => {
var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference); var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
var directiveWrapperInstance = var directiveWrapperInstance =
compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference); compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference);
bindDirectiveInputs(directiveAst, directiveWrapperInstance, dirIndex, compileElement); bindDirectiveInputs(directiveAst, directiveWrapperInstance, compileElement);
bindDirectiveHostProps(
directiveAst, directiveWrapperInstance, compileElement, ast.name, this._schemaRegistry); bindDirectiveHostProps(directiveAst, directiveInstance, compileElement, eventListeners);
bindDirectiveOutputs(directiveAst, directiveInstance, eventListeners);
}); });
templateVisitAll(this, ast.children, compileElement); templateVisitAll(this, ast.children, compileElement);
// afterContent and afterView lifecycles need to be called bottom up // afterContent and afterView lifecycles need to be called bottom up
// so that children are notified before parents // so that children are notified before parents
ast.directives.forEach((directiveAst) => { ast.directives.forEach((directiveAst) => {
var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference); var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
var directiveWrapperInstance =
compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference);
bindDirectiveAfterContentLifecycleCallbacks( bindDirectiveAfterContentLifecycleCallbacks(
directiveAst.directive, directiveInstance, compileElement); directiveAst.directive, directiveInstance, compileElement);
bindDirectiveAfterViewLifecycleCallbacks( bindDirectiveAfterViewLifecycleCallbacks(
directiveAst.directive, directiveInstance, compileElement); directiveAst.directive, directiveInstance, compileElement);
bindDirectiveWrapperLifecycleCallbacks(
directiveAst, directiveWrapperInstance, compileElement);
}); });
ast.providers.forEach((providerAst) => { ast.providers.forEach((providerAst) => {
var providerInstance = compileElement.instances.get(providerAst.token.reference); var providerInstance = compileElement.instances.get(providerAst.token.reference);
@ -75,25 +74,24 @@ class ViewBinderVisitor implements TemplateAstVisitor {
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any { visitEmbeddedTemplate(ast: EmbeddedTemplateAst, parent: CompileElement): any {
var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++]; var compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
bindOutputs(ast.outputs, ast.directives, compileElement, false); var eventListeners = collectEventListeners(ast.outputs, ast.directives, compileElement);
ast.directives.forEach((directiveAst, dirIndex) => { ast.directives.forEach((directiveAst) => {
var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference); var directiveInstance = compileElement.instances.get(directiveAst.directive.type.reference);
var directiveWrapperInstance = var directiveWrapperInstance =
compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference); compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference);
bindDirectiveInputs(directiveAst, directiveWrapperInstance, dirIndex, compileElement); bindDirectiveInputs(directiveAst, directiveWrapperInstance, compileElement);
bindDirectiveOutputs(directiveAst, directiveInstance, eventListeners);
bindDirectiveAfterContentLifecycleCallbacks( bindDirectiveAfterContentLifecycleCallbacks(
directiveAst.directive, directiveInstance, compileElement); directiveAst.directive, directiveInstance, compileElement);
bindDirectiveAfterViewLifecycleCallbacks( bindDirectiveAfterViewLifecycleCallbacks(
directiveAst.directive, directiveInstance, compileElement); directiveAst.directive, directiveInstance, compileElement);
bindDirectiveWrapperLifecycleCallbacks(
directiveAst, directiveWrapperInstance, compileElement);
}); });
ast.providers.forEach((providerAst) => { ast.providers.forEach((providerAst) => {
var providerInstance = compileElement.instances.get(providerAst.token.reference); var providerInstance = compileElement.instances.get(providerAst.token.reference);
bindInjectableDestroyLifecycleCallbacks(providerAst, providerInstance, compileElement); bindInjectableDestroyLifecycleCallbacks(providerAst, providerInstance, compileElement);
}); });
bindView(compileElement.embeddedView, ast.children, this._schemaRegistry); bindView(compileElement.embeddedView, ast.children);
return null; return null;
} }

View File

@ -9,21 +9,19 @@
import {ViewEncapsulation} from '@angular/core'; import {ViewEncapsulation} from '@angular/core';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata} from '../compile_metadata';
import {createSharedBindingVariablesIfNeeded} from '../compiler_util/expression_converter'; import {ListWrapper} from '../facade/collection';
import {createDiTokenExpression, createInlineArray} from '../compiler_util/identifier_util';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, identifierToken, resolveIdentifier} from '../identifiers'; import {Identifiers, identifierToken, resolveIdentifier} from '../identifiers';
import {createClassStmt} from '../output/class_builder';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ParseSourceSpan} from '../parse_util';
import {ChangeDetectorStatus, ViewType, isDefaultChangeDetectionStrategy} from '../private_import_core'; 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 {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 {CompileElement, CompileNode} from './compile_element';
import {CompileView, CompileViewRootNode, CompileViewRootNodeType} from './compile_view'; import {CompileView} from './compile_view';
import {ChangeDetectorStatusEnum, DetectChangesVars, InjectMethodVars, ViewConstructorVars, ViewEncapsulationEnum, ViewProperties, ViewTypeEnum} from './constants'; import {ChangeDetectorStatusEnum, DetectChangesVars, InjectMethodVars, ViewConstructorVars, ViewEncapsulationEnum, ViewProperties, ViewTypeEnum} from './constants';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewFactoryDependency} from './deps'; import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewFactoryDependency} from './deps';
import {getViewFactoryName} from './util'; import {createFlatArray, getViewFactoryName} from './util';
const IMPLICIT_TEMPLATE_VAR = '\$implicit'; const IMPLICIT_TEMPLATE_VAR = '\$implicit';
const CLASS_ATTR = 'class'; const CLASS_ATTR = 'class';
@ -39,12 +37,9 @@ export function buildView(
Array<ViewFactoryDependency|ComponentFactoryDependency|DirectiveWrapperDependency>): Array<ViewFactoryDependency|ComponentFactoryDependency|DirectiveWrapperDependency>):
number { number {
var builderVisitor = new ViewBuilderVisitor(view, targetDependencies); var builderVisitor = new ViewBuilderVisitor(view, targetDependencies);
const parentEl = templateVisitAll(
view.declarationElement.isNull() ? view.declarationElement : view.declarationElement.parent; builderVisitor, template,
templateVisitAll(builderVisitor, template, parentEl); view.declarationElement.isNull() ? view.declarationElement : view.declarationElement.parent);
if (view.viewType === ViewType.EMBEDDED || view.viewType === ViewType.HOST) {
view.lastRenderNode = builderVisitor.getOrCreateLastRenderNode();
}
return builderVisitor.nestedViewCount; return builderVisitor.nestedViewCount;
} }
@ -77,16 +72,10 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
if (this._isRootNode(parent)) { if (this._isRootNode(parent)) {
// store appElement as root node only for ViewContainers // store appElement as root node only for ViewContainers
if (this.view.viewType !== ViewType.COMPONENT) { if (this.view.viewType !== ViewType.COMPONENT) {
this.view.rootNodes.push(new CompileViewRootNode( this.view.rootNodesOrAppElements.push(vcAppEl || node.renderNode);
vcAppEl ? CompileViewRootNodeType.ViewContainer : CompileViewRootNodeType.Node,
vcAppEl || node.renderNode));
} }
} else if (isPresent(parent.component) && isPresent(ngContentIndex)) { } else if (isPresent(parent.component) && isPresent(ngContentIndex)) {
parent.addContentNode( parent.addContentNode(ngContentIndex, vcAppEl || node.renderNode);
ngContentIndex,
new CompileViewRootNode(
vcAppEl ? CompileViewRootNodeType.ViewContainer : CompileViewRootNodeType.Node,
vcAppEl || node.renderNode));
} }
} }
@ -107,23 +96,6 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
} }
} }
getOrCreateLastRenderNode(): o.Expression {
const view = this.view;
if (view.rootNodes.length === 0 ||
view.rootNodes[view.rootNodes.length - 1].type !== CompileViewRootNodeType.Node) {
var fieldName = `_el_${view.nodes.length}`;
view.fields.push(
new o.ClassField(fieldName, o.importType(view.genConfig.renderTypes.renderElement)));
view.createMethod.addStmt(o.THIS_EXPR.prop(fieldName)
.set(ViewProperties.renderer.callMethod(
'createTemplateAnchor', [o.NULL_EXPR, o.NULL_EXPR]))
.toStmt());
view.rootNodes.push(
new CompileViewRootNode(CompileViewRootNodeType.Node, o.THIS_EXPR.prop(fieldName)));
}
return view.rootNodes[view.rootNodes.length - 1].expr;
}
visitBoundText(ast: BoundTextAst, parent: CompileElement): any { visitBoundText(ast: BoundTextAst, parent: CompileElement): any {
return this._visitText(ast, '', parent); return this._visitText(ast, '', parent);
} }
@ -156,6 +128,9 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
// have debug information for them... // have debug information for them...
this.view.createMethod.resetDebugInfo(null, ast); this.view.createMethod.resetDebugInfo(null, ast);
var parentRenderNode = this._getParentRenderNode(parent); var parentRenderNode = this._getParentRenderNode(parent);
var nodesExpression = ViewProperties.projectableNodes.key(
o.literal(ast.index),
new o.ArrayType(o.importType(this.view.genConfig.renderTypes.renderNode)));
if (parentRenderNode !== o.NULL_EXPR) { if (parentRenderNode !== o.NULL_EXPR) {
this.view.createMethod.addStmt( this.view.createMethod.addStmt(
ViewProperties.renderer ViewProperties.renderer
@ -163,20 +138,18 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
'projectNodes', 'projectNodes',
[ [
parentRenderNode, parentRenderNode,
o.THIS_EXPR.callMethod('projectedNodes', [o.literal(ast.index)]) o.importExpr(resolveIdentifier(Identifiers.flattenNestedViewRenderNodes))
.callFn([nodesExpression])
]) ])
.toStmt()); .toStmt());
} else if (this._isRootNode(parent)) { } else if (this._isRootNode(parent)) {
if (this.view.viewType !== ViewType.COMPONENT) { if (this.view.viewType !== ViewType.COMPONENT) {
// store root nodes only for embedded/host views // store root nodes only for embedded/host views
this.view.rootNodes.push( this.view.rootNodesOrAppElements.push(nodesExpression);
new CompileViewRootNode(CompileViewRootNodeType.NgContent, null, ast.index));
} }
} else { } else {
if (isPresent(parent.component) && isPresent(ast.ngContentIndex)) { if (isPresent(parent.component) && isPresent(ast.ngContentIndex)) {
parent.addContentNode( parent.addContentNode(ast.ngContentIndex, nodesExpression);
ast.ngContentIndex,
new CompileViewRootNode(CompileViewRootNodeType.NgContent, null, ast.index));
} }
} }
return null; return null;
@ -184,29 +157,19 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
visitElement(ast: ElementAst, parent: CompileElement): any { visitElement(ast: ElementAst, parent: CompileElement): any {
var nodeIndex = this.view.nodes.length; var nodeIndex = this.view.nodes.length;
var createRenderNodeExpr: o.Expression; var createRenderNodeExpr: o.InvokeMethodExpr;
var debugContextExpr = this.view.createMethod.resetDebugInfoExpr(nodeIndex, ast); var debugContextExpr = this.view.createMethod.resetDebugInfoExpr(nodeIndex, ast);
var directives = ast.directives.map(directiveAst => directiveAst.directive); if (nodeIndex === 0 && this.view.viewType === ViewType.HOST) {
var component = directives.find(directive => directive.isComponent); createRenderNodeExpr = o.THIS_EXPR.callMethod(
if (ast.name === NG_CONTAINER_TAG) { 'selectOrCreateHostElement', [o.literal(ast.name), rootSelectorVar, debugContextExpr]);
createRenderNodeExpr = ViewProperties.renderer.callMethod(
'createTemplateAnchor', [this._getParentRenderNode(parent), debugContextExpr]);
} else { } else {
const htmlAttrs = _readHtmlAttrs(ast.attrs); if (ast.name === NG_CONTAINER_TAG) {
const attrNameAndValues = createInlineArray( createRenderNodeExpr = ViewProperties.renderer.callMethod(
_mergeHtmlAndDirectiveAttrs(htmlAttrs, directives).map(v => o.literal(v))); 'createTemplateAnchor', [this._getParentRenderNode(parent), debugContextExpr]);
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 { } else {
createRenderNodeExpr = createRenderNodeExpr = ViewProperties.renderer.callMethod(
o.importExpr(resolveIdentifier(Identifiers.createRenderElement)).callFn([ 'createElement',
ViewProperties.renderer, this._getParentRenderNode(parent), o.literal(ast.name), [this._getParentRenderNode(parent), o.literal(ast.name), debugContextExpr]);
attrNameAndValues, debugContextExpr
]);
} }
} }
var fieldName = `_el_${nodeIndex}`; var fieldName = `_el_${nodeIndex}`;
@ -216,29 +179,41 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
var renderNode = o.THIS_EXPR.prop(fieldName); 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( var compileElement = new CompileElement(
parent, this.view, nodeIndex, renderNode, ast, component, directives, ast.providers, parent, this.view, nodeIndex, renderNode, ast, component, directives, ast.providers,
ast.hasViewContainer, false, ast.references, this.targetDependencies); ast.hasViewContainer, false, ast.references, this.targetDependencies);
this.view.nodes.push(compileElement); this.view.nodes.push(compileElement);
var compViewExpr: o.ReadPropExpr = null; var compViewExpr: o.ReadVarExpr = null;
if (isPresent(component)) { if (isPresent(component)) {
let nestedComponentIdentifier = let nestedComponentIdentifier =
new CompileIdentifierMetadata({name: getViewFactoryName(component, 0)}); new CompileIdentifierMetadata({name: getViewFactoryName(component, 0)});
this.targetDependencies.push( this.targetDependencies.push(
new ViewFactoryDependency(component.type, nestedComponentIdentifier)); new ViewFactoryDependency(component.type, nestedComponentIdentifier));
compViewExpr = o.THIS_EXPR.prop(`compView_${nodeIndex}`); // fix highlighting: ` compViewExpr = o.variable(`compView_${nodeIndex}`); // fix highlighting: `
this.view.fields.push(new o.ClassField(
compViewExpr.name,
o.importType(resolveIdentifier(Identifiers.AppView), [o.importType(component.type)])));
this.view.viewChildren.push(compViewExpr);
compileElement.setComponentView(compViewExpr); compileElement.setComponentView(compViewExpr);
this.view.createMethod.addStmt( this.view.createMethod.addStmt(
compViewExpr compViewExpr
.set(o.importExpr(nestedComponentIdentifier).callFn([ .set(o.importExpr(nestedComponentIdentifier).callFn([
ViewProperties.viewUtils, compileElement.injector, compileElement.appElement ViewProperties.viewUtils, compileElement.injector, compileElement.appElement
])) ]))
.toStmt()); .toDeclStmt());
} }
compileElement.beforeChildren(); compileElement.beforeChildren();
this._addRootNodeAndProject(compileElement); this._addRootNodeAndProject(compileElement);
@ -246,8 +221,18 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
compileElement.afterChildren(this.view.nodes.length - nodeIndex - 1); compileElement.afterChildren(this.view.nodes.length - nodeIndex - 1);
if (isPresent(compViewExpr)) { if (isPresent(compViewExpr)) {
var codeGenContentNodes: o.Expression;
if (this.view.component.type.isHost) {
codeGenContentNodes = ViewProperties.projectableNodes;
} else {
codeGenContentNodes = o.literalArr(
compileElement.contentNodesByNgContentIndex.map(nodes => createFlatArray(nodes)));
}
this.view.createMethod.addStmt( this.view.createMethod.addStmt(
compViewExpr.callMethod('create', [compileElement.getComponent(), o.NULL_EXPR]).toStmt()); compViewExpr
.callMethod(
'create', [compileElement.getComponent(), codeGenContentNodes, o.NULL_EXPR])
.toStmt());
} }
return null; return null;
} }
@ -344,22 +329,18 @@ function _isNgContainer(node: CompileNode, view: CompileView): boolean {
function _mergeHtmlAndDirectiveAttrs( function _mergeHtmlAndDirectiveAttrs(
declaredHtmlAttrs: {[key: string]: string}, directives: CompileDirectiveMetadata[]): string[] { declaredHtmlAttrs: {[key: string]: string},
const mapResult: {[key: string]: string} = {}; directives: CompileDirectiveMetadata[]): string[][] {
Object.keys(declaredHtmlAttrs).forEach(key => { mapResult[key] = declaredHtmlAttrs[key]; }); var result: {[key: string]: string} = {};
Object.keys(declaredHtmlAttrs).forEach(key => { result[key] = declaredHtmlAttrs[key]; });
directives.forEach(directiveMeta => { directives.forEach(directiveMeta => {
Object.keys(directiveMeta.hostAttributes).forEach(name => { Object.keys(directiveMeta.hostAttributes).forEach(name => {
const value = directiveMeta.hostAttributes[name]; const value = directiveMeta.hostAttributes[name];
const prevValue = mapResult[name]; var prevValue = result[name];
mapResult[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value; result[name] = isPresent(prevValue) ? mergeAttributeValue(name, prevValue, value) : value;
}); });
}); });
const arrResult: string[] = []; return mapToKeyValueArray(result);
// 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} { function _readHtmlAttrs(attrs: AttrAst[]): {[key: string]: string} {
@ -376,6 +357,15 @@ 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[]) { function createViewTopLevelStmts(view: CompileView, targetStatements: o.Statement[]) {
var nodeDebugInfosVar: o.Expression = o.NULL_EXPR; var nodeDebugInfosVar: o.Expression = o.NULL_EXPR;
if (view.genConfig.genDebugInfo) { if (view.genConfig.genDebugInfo) {
@ -454,6 +444,9 @@ function createViewClass(
if (view.genConfig.genDebugInfo) { if (view.genConfig.genDebugInfo) {
superConstructorArgs.push(nodeDebugInfosVar); superConstructorArgs.push(nodeDebugInfosVar);
} }
var viewConstructor = new o.ClassMethod(
null, viewConstructorArgs, [o.SUPER_EXPR.callFn(superConstructorArgs).toStmt()]);
var viewMethods = [ var viewMethods = [
new o.ClassMethod( new o.ClassMethod(
'createInternal', [new o.FnParam(rootSelectorVar.name, o.STRING_TYPE)], 'createInternal', [new o.FnParam(rootSelectorVar.name, o.STRING_TYPE)],
@ -472,32 +465,17 @@ function createViewClass(
'detectChangesInternal', [new o.FnParam(DetectChangesVars.throwOnChange.name, o.BOOL_TYPE)], 'detectChangesInternal', [new o.FnParam(DetectChangesVars.throwOnChange.name, o.BOOL_TYPE)],
generateDetectChangesMethod(view)), generateDetectChangesMethod(view)),
new o.ClassMethod('dirtyParentQueriesInternal', [], view.dirtyParentQueriesMethod.finish()), new o.ClassMethod('dirtyParentQueriesInternal', [], view.dirtyParentQueriesMethod.finish()),
new o.ClassMethod('destroyInternal', [], generateDestroyMethod(view)), new o.ClassMethod('destroyInternal', [], view.destroyMethod.finish()),
new o.ClassMethod('detachInternal', [], view.detachMethod.finish()), new o.ClassMethod('detachInternal', [], view.detachMethod.finish())
generateVisitRootNodesMethod(view), generateVisitProjectableNodesMethod(view) ].concat(view.eventHandlerMethods);
].filter((method) => method.body.length > 0);
var superClass = view.genConfig.genDebugInfo ? Identifiers.DebugAppView : Identifiers.AppView; var superClass = view.genConfig.genDebugInfo ? Identifiers.DebugAppView : Identifiers.AppView;
var viewClass = new o.ClassStmt(
var viewClass = createClassStmt({ view.className, o.importExpr(resolveIdentifier(superClass), [getContextType(view)]),
name: view.className, view.fields, view.getters, viewConstructor,
parent: o.importExpr(resolveIdentifier(superClass), [getContextType(view)]), viewMethods.filter((method) => method.body.length > 0));
parentArgs: superConstructorArgs,
ctorParams: viewConstructorArgs,
builders: [{methods: viewMethods}, view]
});
return viewClass; return viewClass;
} }
function generateDestroyMethod(view: CompileView): o.Statement[] {
const stmts: o.Statement[] = [];
view.viewContainerAppElements.forEach(
(appElement) => { stmts.push(appElement.callMethod('destroyNestedViews', []).toStmt()); });
view.viewChildren.forEach(
(viewChild) => { stmts.push(viewChild.callMethod('destroy', []).toStmt()); });
stmts.push(...view.destroyMethod.finish());
return stmts;
}
function createViewFactory( function createViewFactory(
view: CompileView, viewClass: o.ClassStmt, renderCompTypeVar: o.ReadVarExpr): o.Statement { view: CompileView, viewClass: o.ClassStmt, renderCompTypeVar: o.ReadVarExpr): o.Statement {
var viewFactoryArgs = [ var viewFactoryArgs = [
@ -571,9 +549,9 @@ function generateCreateMethod(view: CompileView): o.Statement[] {
.callMethod( .callMethod(
'init', 'init',
[ [
view.lastRenderNode, createFlatArray(view.rootNodesOrAppElements),
o.literalArr(view.nodes.map(node => node.renderNode)), o.literalArr(view.nodes.map(node => node.renderNode)), o.literalArr(view.disposables),
view.disposables.length ? o.literalArr(view.disposables) : o.NULL_EXPR, o.literalArr(view.subscriptions)
]) ])
.toStmt(), .toStmt(),
new o.ReturnStatement(resultExpr) new o.ReturnStatement(resultExpr)
@ -589,22 +567,19 @@ function generateDetectChangesMethod(view: CompileView): o.Statement[] {
view.updateViewQueriesMethod.isEmpty() && view.afterViewLifecycleCallbacksMethod.isEmpty()) { view.updateViewQueriesMethod.isEmpty() && view.afterViewLifecycleCallbacksMethod.isEmpty()) {
return stmts; return stmts;
} }
stmts.push(...view.animationBindingsMethod.finish()); ListWrapper.addAll(stmts, view.animationBindingsMethod.finish());
stmts.push(...view.detectChangesInInputsMethod.finish()); ListWrapper.addAll(stmts, view.detectChangesInInputsMethod.finish());
view.viewContainerAppElements.forEach((appElement) => { stmts.push(
stmts.push( o.THIS_EXPR.callMethod('detectContentChildrenChanges', [DetectChangesVars.throwOnChange])
appElement.callMethod('detectChangesInNestedViews', [DetectChangesVars.throwOnChange]) .toStmt());
.toStmt());
});
var afterContentStmts = view.updateContentQueriesMethod.finish().concat( var afterContentStmts = view.updateContentQueriesMethod.finish().concat(
view.afterContentLifecycleCallbacksMethod.finish()); view.afterContentLifecycleCallbacksMethod.finish());
if (afterContentStmts.length > 0) { if (afterContentStmts.length > 0) {
stmts.push(new o.IfStmt(o.not(DetectChangesVars.throwOnChange), afterContentStmts)); stmts.push(new o.IfStmt(o.not(DetectChangesVars.throwOnChange), afterContentStmts));
} }
stmts.push(...view.detectChangesRenderPropertiesMethod.finish()); ListWrapper.addAll(stmts, view.detectChangesRenderPropertiesMethod.finish());
view.viewChildren.forEach((viewChild) => { stmts.push(o.THIS_EXPR.callMethod('detectViewChildrenChanges', [DetectChangesVars.throwOnChange])
stmts.push(viewChild.callMethod('detectChanges', [DetectChangesVars.throwOnChange]).toStmt()); .toStmt());
});
var afterViewStmts = var afterViewStmts =
view.updateViewQueriesMethod.finish().concat(view.afterViewLifecycleCallbacksMethod.finish()); view.updateViewQueriesMethod.finish().concat(view.afterViewLifecycleCallbacksMethod.finish());
if (afterViewStmts.length > 0) { if (afterViewStmts.length > 0) {
@ -621,7 +596,12 @@ function generateDetectChangesMethod(view: CompileView): o.Statement[] {
DetectChangesVars.changes.set(o.NULL_EXPR) DetectChangesVars.changes.set(o.NULL_EXPR)
.toDeclStmt(new o.MapType(o.importType(resolveIdentifier(Identifiers.SimpleChange))))); .toDeclStmt(new o.MapType(o.importType(resolveIdentifier(Identifiers.SimpleChange)))));
} }
varStmts.push(...createSharedBindingVariablesIfNeeded(stmts)); if (readVars.has(DetectChangesVars.valUnwrapper.name)) {
varStmts.push(
DetectChangesVars.valUnwrapper
.set(o.importExpr(resolveIdentifier(Identifiers.ValueUnwrapper)).instantiate([]))
.toDeclStmt(null, [o.StmtModifier.Final]));
}
return varStmts.concat(stmts); return varStmts.concat(stmts);
} }
@ -651,61 +631,3 @@ function getChangeDetectionMode(view: CompileView): ChangeDetectorStatus {
} }
return mode; return mode;
} }
function generateVisitRootNodesMethod(view: CompileView): o.ClassMethod {
const cbVar = o.variable('cb');
const ctxVar = o.variable('ctx');
const stmts: o.Statement[] = generateVisitNodesStmts(view.rootNodes, cbVar, ctxVar);
return new o.ClassMethod(
'visitRootNodesInternal',
[new o.FnParam(cbVar.name, o.DYNAMIC_TYPE), new o.FnParam(ctxVar.name, o.DYNAMIC_TYPE)],
stmts);
}
function generateVisitProjectableNodesMethod(view: CompileView): o.ClassMethod {
const nodeIndexVar = o.variable('nodeIndex');
const ngContentIndexVar = o.variable('ngContentIndex');
const cbVar = o.variable('cb');
const ctxVar = o.variable('ctx');
const stmts: o.Statement[] = [];
view.nodes.forEach((node) => {
if (node instanceof CompileElement && node.component) {
node.contentNodesByNgContentIndex.forEach((projectedNodes, ngContentIndex) => {
stmts.push(new o.IfStmt(
nodeIndexVar.equals(o.literal(node.nodeIndex))
.and(ngContentIndexVar.equals(o.literal(ngContentIndex))),
generateVisitNodesStmts(projectedNodes, cbVar, ctxVar)));
});
}
});
return new o.ClassMethod(
'visitProjectableNodesInternal',
[
new o.FnParam(nodeIndexVar.name, o.NUMBER_TYPE),
new o.FnParam(ngContentIndexVar.name, o.NUMBER_TYPE),
new o.FnParam(cbVar.name, o.DYNAMIC_TYPE), new o.FnParam(ctxVar.name, o.DYNAMIC_TYPE)
],
stmts);
}
function generateVisitNodesStmts(
nodes: CompileViewRootNode[], cb: o.Expression, ctx: o.Expression): o.Statement[] {
const stmts: o.Statement[] = [];
nodes.forEach((node) => {
switch (node.type) {
case CompileViewRootNodeType.Node:
stmts.push(cb.callFn([node.expr, ctx]).toStmt());
break;
case CompileViewRootNodeType.ViewContainer:
stmts.push(cb.callFn([node.expr.prop('nativeElement'), ctx]).toStmt());
stmts.push(node.expr.callMethod('visitNestedViewRootNodes', [cb, ctx]).toStmt());
break;
case CompileViewRootNodeType.NgContent:
stmts.push(
o.THIS_EXPR.callMethod('visitProjectedNodes', [o.literal(node.ngContentIndex), cb, ctx])
.toStmt());
break;
}
});
return stmts;
}

View File

@ -12,7 +12,6 @@ import {AnimationEntryCompileResult} from '../animation/animation_compiler';
import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata, CompilePipeMetadata} from '../compile_metadata';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {TemplateAst} from '../template_parser/template_ast'; import {TemplateAst} from '../template_parser/template_ast';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
@ -32,7 +31,7 @@ export class ViewCompileResult {
@Injectable() @Injectable()
export class ViewCompiler { export class ViewCompiler {
constructor(private _genConfig: CompilerConfig, private _schemaRegistry: ElementSchemaRegistry) {} constructor(private _genConfig: CompilerConfig) {}
compileComponent( compileComponent(
component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression, component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression,
@ -48,7 +47,7 @@ export class ViewCompiler {
buildView(view, template, dependencies); buildView(view, template, dependencies);
// Need to separate binding from creation to be able to refer to // Need to separate binding from creation to be able to refer to
// variables that have been declared after usage. // variables that have been declared after usage.
bindView(view, template, this._schemaRegistry); bindView(view, template);
finishView(view, statements); finishView(view, statements);
return new ViewCompileResult(statements, view.viewFactory.name, dependencies); return new ViewCompileResult(statements, view.viewFactory.name, dependencies);

View File

@ -1,63 +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 {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); });
});
}

View File

@ -74,10 +74,7 @@ export function main() {
return; return;
} }
} }
const errMsgs = ast.errors.map(err => err.message).join('\n'); throw Error(`Expected an error containing "${message}" to be reported`);
throw Error(
`Expected an error containing "${message}" to be reported, but got the errors:\n` +
errMsgs);
} }
function expectActionError(text: string, message: string) { function expectActionError(text: string, message: string) {
@ -507,10 +504,16 @@ export function main() {
validate(p); validate(p);
}); });
it('should report when encountering pipes', () => { 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', () => {
expectError( expectError(
validate(parseSimpleBinding('a | somePipe')), validate(parseSimpleBinding('name + 1')),
'Host binding expression cannot contain pipes'); 'Host binding expression can only contain field access and constants');
}); });
it('should report when encountering interpolation', () => { it('should report when encountering interpolation', () => {
@ -518,10 +521,6 @@ export function main() {
validate(parseSimpleBinding('{{exp}}')), validate(parseSimpleBinding('{{exp}}')),
'Got interpolation ({{}}) where expression was expected'); 'Got interpolation ({{}}) where expression was expected');
}); });
it('should report when encountering field write', () => {
expectError(validate(parseSimpleBinding('a = b')), 'Bindings cannot contain assignments');
});
}); });
describe('wrapLiteralPrimitive', () => { describe('wrapLiteralPrimitive', () => {

View File

@ -183,22 +183,6 @@ export function main() {
})); }));
}); });
it('should dedupe declarations in NgModule',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
@Component({template: ''})
class MyComp {
}
@NgModule({declarations: [MyComp, MyComp]})
class MyModule {
}
const modMeta = resolver.getNgModuleMetadata(MyModule);
expect(modMeta.declaredDirectives.length).toBe(1);
expect(modMeta.declaredDirectives[0].type.reference).toBe(MyComp);
}));
}); });
} }

View File

@ -11,7 +11,7 @@
import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter'; import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter';
import {print} from '../../src/facade/lang'; import {print} from '../../src/facade/lang';
import {assetUrl} from '../../src/identifiers'; import {assetUrl} from '../../src/util';
function unimplemented(): any { function unimplemented(): any {
throw new Error('unimplemented'); throw new Error('unimplemented');

View File

@ -10,7 +10,7 @@
import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter'; import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter';
import {print} from '../../src/facade/lang'; import {print} from '../../src/facade/lang';
import {assetUrl} from '../../src/identifiers'; import {assetUrl} from '../../src/util';
import {SimpleJsImportGenerator, codegenExportsVars, codegenStmts} from './output_emitter_util'; import {SimpleJsImportGenerator, codegenExportsVars, codegenStmts} from './output_emitter_util';

View File

@ -7,9 +7,9 @@
*/ */
import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata'; 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 * as o from '@angular/compiler/src/output/output_ast';
import {ImportGenerator} from '@angular/compiler/src/output/path_util'; import {ImportGenerator} from '@angular/compiler/src/output/path_util';
import {assetUrl} from '@angular/compiler/src/util';
import {EventEmitter} from '@angular/core'; import {EventEmitter} from '@angular/core';
import {BaseError} from '@angular/core/src/facade/errors'; import {BaseError} from '@angular/core/src/facade/errors';
import {ViewType} from '@angular/core/src/linker/view_type'; import {ViewType} from '@angular/core/src/linker/view_type';

View File

@ -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', () => { it('should return security contexts for elements', () => {
expect(registry.securityContext('iframe', 'srcdoc', false)).toBe(SecurityContext.HTML); expect(registry.securityContext('iframe', 'srcdoc')).toBe(SecurityContext.HTML);
expect(registry.securityContext('p', 'innerHTML', false)).toBe(SecurityContext.HTML); expect(registry.securityContext('p', 'innerHTML')).toBe(SecurityContext.HTML);
expect(registry.securityContext('a', 'href', false)).toBe(SecurityContext.URL); expect(registry.securityContext('a', 'href')).toBe(SecurityContext.URL);
expect(registry.securityContext('a', 'style', false)).toBe(SecurityContext.STYLE); expect(registry.securityContext('a', 'style')).toBe(SecurityContext.STYLE);
expect(registry.securityContext('ins', 'cite', false)).toBe(SecurityContext.URL); expect(registry.securityContext('ins', 'cite')).toBe(SecurityContext.URL);
expect(registry.securityContext('base', 'href', false)).toBe(SecurityContext.RESOURCE_URL); expect(registry.securityContext('base', 'href')).toBe(SecurityContext.RESOURCE_URL);
}); });
it('should detect properties on namespaced elements', () => { it('should detect properties on namespaced elements', () => {
@ -162,14 +162,9 @@ If 'onAnything' is a directive input, make sure the directive is imported by the
}); });
it('should check security contexts case insensitive', () => { it('should check security contexts case insensitive', () => {
expect(registry.securityContext('p', 'iNnErHtMl', false)).toBe(SecurityContext.HTML); expect(registry.securityContext('p', 'iNnErHtMl')).toBe(SecurityContext.HTML);
expect(registry.securityContext('p', 'formaction', false)).toBe(SecurityContext.URL); expect(registry.securityContext('p', 'formaction')).toBe(SecurityContext.URL);
expect(registry.securityContext('p', 'formAction', false)).toBe(SecurityContext.URL); expect(registry.securityContext('p', 'formAction')).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', () => { describe('Angular custom elements', () => {

View File

@ -58,12 +58,6 @@ export function main() {
expect(matched).toEqual([s1[0], 1, s2[0], 2]); 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', () => { it('should select by attr name case sensitive independent of the value', () => {
matcher.addSelectables(s1 = CssSelector.parse('[someAttr]'), 1); matcher.addSelectables(s1 = CssSelector.parse('[someAttr]'), 1);
matcher.addSelectables(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2); matcher.addSelectables(s2 = CssSelector.parse('[someAttr][someAttr2]'), 2);

View File

@ -1,59 +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 {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
]);
});
});
});
}

View File

@ -124,7 +124,7 @@ export function main() {
new class extends NullVisitor{ new class extends NullVisitor{
visitElementProperty(ast: BoundElementPropertyAst, context: any): any{return ast;} visitElementProperty(ast: BoundElementPropertyAst, context: any): any{return ast;}
}, },
new BoundElementPropertyAst('foo', null, null, false, null, 'bar', null)); new BoundElementPropertyAst('foo', null, null, null, 'bar', null));
}); });
it('should visit AttrAst', () => { it('should visit AttrAst', () => {
@ -171,7 +171,7 @@ export function main() {
new ElementAst('foo', [], [], [], [], [], [], false, [], 0, null, null), new ElementAst('foo', [], [], [], [], [], [], false, [], 0, null, null),
new ReferenceAst('foo', null, null), new VariableAst('foo', 'bar', null), new ReferenceAst('foo', null, null), new VariableAst('foo', 'bar', null),
new BoundEventAst('foo', 'bar', 'goo', null, null), new BoundEventAst('foo', 'bar', 'goo', null, null),
new BoundElementPropertyAst('foo', null, null, false, null, 'bar', null), new BoundElementPropertyAst('foo', null, null, null, 'bar', null),
new AttrAst('foo', 'bar', null), new BoundTextAst(null, 0, null), new AttrAst('foo', 'bar', null), new BoundTextAst(null, 0, null),
new TextAst('foo', 0, null), new DirectiveAst(null, [], [], [], null), new TextAst('foo', 0, null), new DirectiveAst(null, [], [], [], null),
new BoundDirectivePropertyAst('foo', 'bar', null, null) new BoundDirectivePropertyAst('foo', 'bar', null, null)
@ -1495,12 +1495,8 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("<div [
{moduleUrl: someModuleUrl, name: 'DirB', reference: {} as Type<any>}), {moduleUrl: someModuleUrl, name: 'DirB', reference: {} as Type<any>}),
template: new CompileTemplateMetadata({ngContentSelectors: []}) template: new CompileTemplateMetadata({ngContentSelectors: []})
}); });
expect(() => parse('<div>', [dirB, dirA])) expect(() => parse('<div>', [dirB, dirA])).toThrowError(`Template parse errors:
.toThrowError( More than one component: DirB,DirA ("[ERROR ->]<div>"): TestComp@0:0`);
`Template parse errors:\n` +
`More than one component matched on this element.\n` +
`Make sure that only one component's selector can match a given element.\n` +
`Conflicting components: DirB,DirA ("[ERROR ->]<div>"): TestComp@0:0`);
}); });
it('should not allow components or element bindings nor dom events on explicit embedded templates', it('should not allow components or element bindings nor dom events on explicit embedded templates',

View File

@ -8,7 +8,7 @@
import {ResourceLoader} from '@angular/compiler'; import {ResourceLoader} from '@angular/compiler';
import {ListWrapper} from './facade/collection'; import {ListWrapper} from './facade/collection';
import {isBlank} from './facade/lang'; import {isBlank, normalizeBlank} from './facade/lang';
/** /**
* A mock implementation of {@link ResourceLoader} that allows outgoing requests to be mocked * 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[] = []; private _requests: _PendingRequest[] = [];
get(url: string): Promise<string> { get(url: string): Promise<string> {
const request = new _PendingRequest(url); var request = new _PendingRequest(url);
this._requests.push(request); this._requests.push(request);
return request.getPromise(); return request.getPromise();
} }
@ -33,7 +33,7 @@ export class MockResourceLoader extends ResourceLoader {
* The response given will be returned if the expectation matches. * The response given will be returned if the expectation matches.
*/ */
expect(url: string, response: string) { expect(url: string, response: string) {
const expectation = new _Expectation(url, response); var expectation = new _Expectation(url, response);
this._expectations.push(expectation); this._expectations.push(expectation);
} }
@ -90,7 +90,7 @@ export class MockResourceLoader extends ResourceLoader {
if (this._definitions.has(url)) { if (this._definitions.has(url)) {
var response = this._definitions.get(url); var response = this._definitions.get(url);
request.complete(response == null ? null : response); request.complete(normalizeBlank(response));
return; return;
} }

View File

@ -26,9 +26,7 @@ export class MockSchemaRegistry implements ElementSchemaRegistry {
return value === void 0 ? true : value; return value === void 0 ? true : value;
} }
allKnownElementNames(): string[] { return Object.keys(this.existingElements); } securityContext(tagName: string, property: string): SecurityContext {
securityContext(selector: string, property: string, isAttribute: boolean): SecurityContext {
return SecurityContext.NONE; return SecurityContext.NONE;
} }

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {StringMapWrapper} from '../facade/collection'; import {ListWrapper, StringMapWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {FILL_STYLE_FLAG} from './animation_constants'; import {FILL_STYLE_FLAG} from './animation_constants';
@ -57,7 +57,7 @@ export function balanceAnimationKeyframes(
// phase 2: normalize the final keyframe // phase 2: normalize the final keyframe
var finalKeyframe = keyframes[limit]; var finalKeyframe = keyframes[limit];
finalKeyframe.styles.styles.unshift(finalStateStyles); ListWrapper.insert(finalKeyframe.styles.styles, 0, finalStateStyles);
var flatenedFinalKeyframeStyles = flattenStyles(finalKeyframe.styles.styles); var flatenedFinalKeyframeStyles = flattenStyles(finalKeyframe.styles.styles);
var extraFinalKeyframeStyles: {[key: string]: string} = {}; var extraFinalKeyframeStyles: {[key: string]: string} = {};

View File

@ -7,6 +7,7 @@
*/ */
import {Optional, Provider, SkipSelf} from '../../di'; import {Optional, Provider, SkipSelf} from '../../di';
import {ListWrapper} from '../../facade/collection';
import {getTypeNameForDebugging, isPresent} from '../../facade/lang'; import {getTypeNameForDebugging, isPresent} from '../../facade/lang';
import {ChangeDetectorRef} from '../change_detector_ref'; import {ChangeDetectorRef} from '../change_detector_ref';
@ -50,7 +51,7 @@ export class IterableDiffers {
static create(factories: IterableDifferFactory[], parent?: IterableDiffers): IterableDiffers { static create(factories: IterableDifferFactory[], parent?: IterableDiffers): IterableDiffers {
if (isPresent(parent)) { if (isPresent(parent)) {
var copied = parent.factories.slice(); var copied = ListWrapper.clone(parent.factories);
factories = factories.concat(copied); factories = factories.concat(copied);
return new IterableDiffers(factories); return new IterableDiffers(factories);
} else { } else {

View File

@ -41,7 +41,7 @@ export class KeyValueDiffers {
static create(factories: KeyValueDifferFactory[], parent?: KeyValueDiffers): KeyValueDiffers { static create(factories: KeyValueDifferFactory[], parent?: KeyValueDiffers): KeyValueDiffers {
if (isPresent(parent)) { if (isPresent(parent)) {
var copied = parent.factories.slice(); var copied = ListWrapper.clone(parent.factories);
factories = factories.concat(copied); factories = factories.concat(copied);
return new KeyValueDiffers(factories); return new KeyValueDiffers(factories);
} else { } else {

View File

@ -7,7 +7,7 @@
*/ */
import {Injector} from '../di'; import {Injector} from '../di';
import {MapWrapper, Predicate} from '../facade/collection'; import {ListWrapper, MapWrapper, Predicate} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {RenderDebugInfo} from '../render/api'; import {RenderDebugInfo} from '../render/api';
@ -92,7 +92,8 @@ export class DebugElement extends DebugNode {
if (siblingIndex !== -1) { if (siblingIndex !== -1) {
var previousChildren = this.childNodes.slice(0, siblingIndex + 1); var previousChildren = this.childNodes.slice(0, siblingIndex + 1);
var nextChildren = this.childNodes.slice(siblingIndex + 1); var nextChildren = this.childNodes.slice(siblingIndex + 1);
this.childNodes = previousChildren.concat(newChildren, nextChildren); this.childNodes =
ListWrapper.concat(ListWrapper.concat(previousChildren, newChildren), nextChildren);
for (var i = 0; i < newChildren.length; ++i) { for (var i = 0; i < newChildren.length; ++i) {
var newChild = newChildren[i]; var newChild = newChildren[i];
if (isPresent(newChild.parent)) { if (isPresent(newChild.parent)) {

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ListWrapper} from '../facade/collection';
import {BaseError, WrappedError} from '../facade/errors'; import {BaseError, WrappedError} from '../facade/errors';
import {stringify} from '../facade/lang'; import {stringify} from '../facade/lang';
import {Type} from '../type'; import {Type} from '../type';
@ -16,7 +17,7 @@ import {ReflectiveKey} from './reflective_key';
function findFirstClosedCycle(keys: any[]): any[] { function findFirstClosedCycle(keys: any[]): any[] {
var res: any[] = []; var res: any[] = [];
for (var i = 0; i < keys.length; ++i) { for (var i = 0; i < keys.length; ++i) {
if (res.indexOf(keys[i]) > -1) { if (ListWrapper.contains(res, keys[i])) {
res.push(keys[i]); res.push(keys[i]);
return res; return res;
} }
@ -27,8 +28,8 @@ function findFirstClosedCycle(keys: any[]): any[] {
function constructResolvingPath(keys: any[]): string { function constructResolvingPath(keys: any[]): string {
if (keys.length > 1) { if (keys.length > 1) {
const reversed = findFirstClosedCycle(keys.slice().reverse()); var reversed = findFirstClosedCycle(ListWrapper.reversed(keys));
const tokenStrs = reversed.map(k => stringify(k.token)); var tokenStrs = reversed.map(k => stringify(k.token));
return ' (' + tokenStrs.join(' -> ') + ')'; return ' (' + tokenStrs.join(' -> ') + ')';
} }
@ -87,7 +88,7 @@ export class AbstractProviderError extends BaseError {
export class NoProviderError extends AbstractProviderError { export class NoProviderError extends AbstractProviderError {
constructor(injector: ReflectiveInjector, key: ReflectiveKey) { constructor(injector: ReflectiveInjector, key: ReflectiveKey) {
super(injector, key, function(keys: any[]) { super(injector, key, function(keys: any[]) {
const first = stringify(keys[0].token); var first = stringify(ListWrapper.first(keys).token);
return `No provider for ${first}!${constructResolvingPath(keys)}`; return `No provider for ${first}!${constructResolvingPath(keys)}`;
}); });
} }
@ -166,7 +167,7 @@ export class InstantiationError extends WrappedError {
} }
get message(): string { get message(): string {
var first = stringify(this.keys[0].token); var first = stringify(ListWrapper.first(this.keys).token);
return `${this.originalError.message}: Error during instantiation of ${first}!${constructResolvingPath(this.keys)}.`; return `${this.originalError.message}: Error during instantiation of ${first}!${constructResolvingPath(this.keys)}.`;
} }

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ListWrapper} from '../facade/collection';
import {unimplemented} from '../facade/errors'; import {unimplemented} from '../facade/errors';
import {Type} from '../type'; import {Type} from '../type';
@ -16,6 +17,8 @@ import {AbstractProviderError, CyclicDependencyError, InstantiationError, NoProv
import {ReflectiveKey} from './reflective_key'; import {ReflectiveKey} from './reflective_key';
import {ReflectiveDependency, ResolvedReflectiveFactory, ResolvedReflectiveProvider, resolveReflectiveProviders} from './reflective_provider'; 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 // Threshold for the dynamic version
const _MAX_CONSTRUCTION_COUNTER = 10; const _MAX_CONSTRUCTION_COUNTER = 10;
const UNDEFINED = new Object(); const UNDEFINED = new Object();
@ -283,7 +286,8 @@ export class ReflectiveInjectorDynamicStrategy implements ReflectiveInjectorStra
constructor( constructor(
public protoStrategy: ReflectiveProtoInjectorDynamicStrategy, public protoStrategy: ReflectiveProtoInjectorDynamicStrategy,
public injector: ReflectiveInjector_) { public injector: ReflectiveInjector_) {
this.objs = new Array(protoStrategy.providers.length).fill(UNDEFINED); this.objs = new Array(protoStrategy.providers.length);
ListWrapper.fill(this.objs, UNDEFINED);
} }
resetConstructionCounter(): void { this.injector._constructionCounter = 0; } resetConstructionCounter(): void { this.injector._constructionCounter = 0; }
@ -293,9 +297,9 @@ export class ReflectiveInjectorDynamicStrategy implements ReflectiveInjectorStra
} }
getObjByKeyId(keyId: number): any { getObjByKeyId(keyId: number): any {
const p = this.protoStrategy; var p = this.protoStrategy;
for (let i = 0; i < p.keyIds.length; i++) { for (var i = 0; i < p.keyIds.length; i++) {
if (p.keyIds[i] === keyId) { if (p.keyIds[i] === keyId) {
if (this.objs[i] === UNDEFINED) { if (this.objs[i] === UNDEFINED) {
this.objs[i] = this.injector._new(p.providers[i]); this.objs[i] = this.injector._new(p.providers[i]);

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {MapWrapper} from '../facade/collection'; import {ListWrapper, MapWrapper} from '../facade/collection';
import {isBlank, isPresent} from '../facade/lang'; import {isBlank, isPresent} from '../facade/lang';
import {reflector} from '../reflection/reflection'; import {reflector} from '../reflection/reflection';
import {Type} from '../type'; import {Type} from '../type';
@ -170,7 +170,7 @@ export function mergeResolvedReflectiveProviders(
var resolvedProvider: ResolvedReflectiveProvider; var resolvedProvider: ResolvedReflectiveProvider;
if (provider.multiProvider) { if (provider.multiProvider) {
resolvedProvider = new ResolvedReflectiveProvider_( resolvedProvider = new ResolvedReflectiveProvider_(
provider.key, provider.resolvedFactories.slice(), provider.multiProvider); provider.key, ListWrapper.clone(provider.resolvedFactories), provider.multiProvider);
} else { } else {
resolvedProvider = provider; resolvedProvider = provider;
} }

View File

@ -10,10 +10,8 @@ import {ChangeDetectorRef} from '../change_detection/change_detection';
import {Injector} from '../di/injector'; import {Injector} from '../di/injector';
import {unimplemented} from '../facade/errors'; import {unimplemented} from '../facade/errors';
import {Type} from '../type'; import {Type} from '../type';
import {AppElement} from './element'; import {AppElement} from './element';
import {ElementRef} from './element_ref'; import {ElementRef} from './element_ref';
import {AppView} from './view';
import {ViewRef} from './view_ref'; import {ViewRef} from './view_ref';
import {ViewUtils} from './view_utils'; import {ViewUtils} from './view_utils';
@ -77,7 +75,7 @@ export class ComponentRef_<C> extends ComponentRef<C> {
get changeDetectorRef(): ChangeDetectorRef { return this._hostElement.parentView.ref; }; get changeDetectorRef(): ChangeDetectorRef { return this._hostElement.parentView.ref; };
get componentType(): Type<any> { return this._componentType; } get componentType(): Type<any> { return this._componentType; }
destroy(): void { this._hostElement.parentView.detachAndDestroy(); } destroy(): void { this._hostElement.parentView.destroy(); }
onDestroy(callback: Function): void { this.hostView.onDestroy(callback); } onDestroy(callback: Function): void { this.hostView.onDestroy(callback); }
} }
@ -106,15 +104,8 @@ export class ComponentFactory<C> {
projectableNodes = []; projectableNodes = [];
} }
// Note: Host views don't need a declarationAppElement! // Note: Host views don't need a declarationAppElement!
var hostView: AppView<any> = this._viewFactory(vu, injector, null); var hostView = this._viewFactory(vu, injector, null);
hostView.visitProjectableNodesInternal = var hostElement = hostView.create(EMPTY_CONTEXT, projectableNodes, rootSelectorOrNode);
(nodeIndex: number, ngContentIndex: number, cb: any, ctx: any) => {
const nodes = projectableNodes[ngContentIndex] || [];
for (var i = 0; i < nodes.length; i++) {
cb(nodes[i], ctx);
}
};
var hostElement = hostView.create(EMPTY_CONTEXT, rootSelectorOrNode);
return new ComponentRef_<C>(hostElement, this._componentType); return new ComponentRef_<C>(hostElement, this._componentType);
} }
} }

View File

@ -7,6 +7,7 @@
*/ */
import {Injector} from '../di/injector'; import {Injector} from '../di/injector';
import {ListWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {ElementRef} from './element_ref'; import {ElementRef} from './element_ref';
@ -22,10 +23,11 @@ import {ViewType} from './view_type';
* that is needed for later instantiations. * that is needed for later instantiations.
*/ */
export class AppElement { export class AppElement {
public nestedViews: AppView<any>[]; public nestedViews: AppView<any>[] = null;
public componentView: AppView<any>; public componentView: AppView<any> = null;
public component: any; public component: any;
public componentConstructorViewQueries: QueryList<any>[];
constructor( constructor(
public index: number, public parentIndex: number, public parentView: AppView<any>, public index: number, public parentIndex: number, public parentView: AppView<any>,
@ -35,38 +37,16 @@ export class AppElement {
get vcRef(): ViewContainerRef_ { return new ViewContainerRef_(this); } get vcRef(): ViewContainerRef_ { return new ViewContainerRef_(this); }
initComponent(component: any, view: AppView<any>) { initComponent(
component: any, componentConstructorViewQueries: QueryList<any>[], view: AppView<any>) {
this.component = component; this.component = component;
this.componentConstructorViewQueries = componentConstructorViewQueries;
this.componentView = view; this.componentView = view;
} }
get parentInjector(): Injector { return this.parentView.injector(this.parentIndex); } get parentInjector(): Injector { return this.parentView.injector(this.parentIndex); }
get injector(): Injector { return this.parentView.injector(this.index); } get injector(): Injector { return this.parentView.injector(this.index); }
detectChangesInNestedViews(throwOnChange: boolean): void {
if (this.nestedViews) {
for (var i = 0; i < this.nestedViews.length; i++) {
this.nestedViews[i].detectChanges(throwOnChange);
}
}
}
destroyNestedViews(): void {
if (this.nestedViews) {
for (var i = 0; i < this.nestedViews.length; i++) {
this.nestedViews[i].destroy();
}
}
}
visitNestedViewRootNodes<C>(cb: (node: any, ctx: C) => void, c: C): void {
if (this.nestedViews) {
for (var i = 0; i < this.nestedViews.length; i++) {
this.nestedViews[i].visitRootNodesInternal(cb, c);
}
}
}
mapNestedViews(nestedViewClass: any, callback: Function): any[] { mapNestedViews(nestedViewClass: any, callback: Function): any[] {
var result: any[] /** TODO #9100 */ = []; var result: any[] /** TODO #9100 */ = [];
if (isPresent(this.nestedViews)) { if (isPresent(this.nestedViews)) {
@ -89,8 +69,8 @@ export class AppElement {
nestedViews = []; nestedViews = [];
this.nestedViews = nestedViews; this.nestedViews = nestedViews;
} }
nestedViews.splice(previousIndex, 1); ListWrapper.removeAt(nestedViews, previousIndex);
nestedViews.splice(currentIndex, 0, view); ListWrapper.insert(nestedViews, currentIndex, view);
var refRenderNode: any /** TODO #9100 */; var refRenderNode: any /** TODO #9100 */;
if (currentIndex > 0) { if (currentIndex > 0) {
var prevView = nestedViews[currentIndex - 1]; var prevView = nestedViews[currentIndex - 1];
@ -113,7 +93,7 @@ export class AppElement {
nestedViews = []; nestedViews = [];
this.nestedViews = nestedViews; this.nestedViews = nestedViews;
} }
nestedViews.splice(viewIndex, 0, view); ListWrapper.insert(nestedViews, viewIndex, view);
var refRenderNode: any /** TODO #9100 */; var refRenderNode: any /** TODO #9100 */;
if (viewIndex > 0) { if (viewIndex > 0) {
var prevView = nestedViews[viewIndex - 1]; var prevView = nestedViews[viewIndex - 1];
@ -128,7 +108,7 @@ export class AppElement {
} }
detachView(viewIndex: number): AppView<any> { detachView(viewIndex: number): AppView<any> {
const view = this.nestedViews.splice(viewIndex, 1)[0]; var view = ListWrapper.removeAt(this.nestedViews, viewIndex);
if (view.type === ViewType.COMPONENT) { if (view.type === ViewType.COMPONENT) {
throw new Error(`Component views can't be moved!`); throw new Error(`Component views can't be moved!`);
} }

Some files were not shown because too many files have changed in this diff Show More