Compare commits

..

19 Commits
2.3.1 ... 2.4.0

Author SHA1 Message Date
6efdf84d3e docs(changelog): add changelog for 2.4.0 2016-12-19 17:42:17 -08:00
e61bfc8b24 chore(release): cut the 2.4.0 release 2016-12-19 17:19:25 -08:00
070f9d0644 refactor: formatting fixes 2016-12-19 17:18:33 -08:00
8d5da1e57a feat: update to rxjs@5.0.1 and unpin the rxjs peerDeps via ^5.0.1 (#13572)
Now that rxjs is stable and the rxjs team follows semver, we can update and unpin the dependency safely.

From now on the Angular application/library developers are in charge of controlling the rxjs version as long as it's newer than 5.0.1.

closes #13561
closes #13478
closes #13572
2016-12-19 17:09:41 -08:00
b6406191c7 build(npm): update angular version in shrinkwrap files 2016-12-19 17:09:41 -08:00
124face441 refactor(compiler-cli): support extracting the mesage bundle without writing a file (#13580) 2016-12-19 17:09:41 -08:00
de4ace77fe feat(compiler-cli): private i18n API for the CLI (#13536)
Also change the Extractor API to align with the Codegen API (internal APIs)
2016-12-19 17:09:41 -08:00
debb0c9798 fix(compiler-cli): produce metadata for .d.ts files without metadata (#13526)
Fixes #13307
Fixes #13473
Fixes #13521
2016-12-19 17:09:40 -08:00
9b87bb6d7f fix(compiler): do not lex }} when interpolation is disabled (#13531)
* doc(compiler): fix the ICU expander API docs

* test(compiler): add lexer and parser specs

* fix(compiler): do not lex `}}` when interpolation is disabled

fix #13525
2016-12-19 17:09:40 -08:00
71e88a8c3c refactor(core): fix typo (#13515)
Closes #13512
2016-12-19 17:09:40 -08:00
c26c24c544 fix(upgrade): fix registerForNg1Tests (#13522)
Fix an issue in `registerForNg1Tests`, where it passes a `null` as
`ng1Injector` to `_bootstrapDone`. This causes a "TypeError: Cannot
read property 'get' of null" to be thrown from `_bootstrapDone`.
2016-12-19 17:09:40 -08:00
3f178410c3 fix(i18n): add a default example to xmb placeholders (#13507)
Otherwise the TC would not be able to load the message
2016-12-19 17:09:40 -08:00
b36f4bc00d fix(animations): allow players to be destroyed before initialized (#13346)
Closes #13293
Closes #13346
2016-12-19 17:09:40 -08:00
355c537883 refactor(compiler): format update (#13506) 2016-12-19 17:09:39 -08:00
f277303ca3 refactor(compiler): don't print stack trace on template parse errors (#13390) 2016-12-19 17:09:39 -08:00
50afbe094f fix(build): use bash string comparison operator (#13502) 2016-12-19 17:09:39 -08:00
15ea758d01 feature(DEVELOPER.md): add easy way to publish personal snapshot builds (#13469) 2016-12-19 17:09:39 -08:00
1f0f429f2a refactor(compiler): store metadata of top level symbols also in summaries (#13289)
This allows a build using summaries to not need .metadata.json files at all
any more.

Part of #12787
2016-12-19 17:09:39 -08:00
dbb364e23a docs(changelog): minor updates to 2.3.1 changelog 2016-12-14 21:57:56 -08:00
89 changed files with 2336 additions and 1256 deletions

View File

@ -1,3 +1,23 @@
<a name="2.4.0"></a>
# [2.4.0 stability-interjection](https://github.com/angular/angular/compare/2.3.1...2.4.0) (2016-12-20)
### Bug Fixes
* **animations:** allow players to be destroyed before initialized ([#13346](https://github.com/angular/angular/issues/13346)) ([b36f4bc](https://github.com/angular/angular/commit/b36f4bc)), closes [#13293](https://github.com/angular/angular/issues/13293)
* **build:** use bash string comparison operator ([#13502](https://github.com/angular/angular/issues/13502)) ([50afbe0](https://github.com/angular/angular/commit/50afbe0))
* **compiler:** do not lex `}}` when interpolation is disabled ([#13531](https://github.com/angular/angular/issues/13531)) ([9b87bb6](https://github.com/angular/angular/commit/9b87bb6)), closes [#13525](https://github.com/angular/angular/issues/13525)
* **compiler-cli:** produce metadata for .d.ts files without metadata ([#13526](https://github.com/angular/angular/issues/13526)) ([debb0c9](https://github.com/angular/angular/commit/debb0c9)), closes [#13307](https://github.com/angular/angular/issues/13307) [#13473](https://github.com/angular/angular/issues/13473) [#13521](https://github.com/angular/angular/issues/13521)
* **i18n:** add a default example to xmb placeholders ([#13507](https://github.com/angular/angular/issues/13507)) ([3f17841](https://github.com/angular/angular/commit/3f17841))
* **upgrade:** fix `registerForNg1Tests` ([#13522](https://github.com/angular/angular/issues/13522)) ([c26c24c](https://github.com/angular/angular/commit/c26c24c))
### Features
* update to `rxjs@5.0.1` and unpin the rxjs peerDeps via `^5.0.1` ([#13572](https://github.com/angular/angular/issues/13572)) ([8d5da1e](https://github.com/angular/angular/commit/8d5da1e)), closes [#13561](https://github.com/angular/angular/issues/13561) [#13478](https://github.com/angular/angular/issues/13478)
<a name="2.3.1"></a> <a name="2.3.1"></a>
## [2.3.1](https://github.com/angular/angular/compare/2.3.0...2.3.1) (2016-12-15) ## [2.3.1](https://github.com/angular/angular/compare/2.3.0...2.3.1) (2016-12-15)
@ -20,25 +40,20 @@
* **compiler:** update to metadata version 3 ([#13464](https://github.com/angular/angular/issues/13464)) ([b9b557c](https://github.com/angular/angular/commit/b9b557c)) * **compiler:** update to metadata version 3 ([#13464](https://github.com/angular/angular/issues/13464)) ([b9b557c](https://github.com/angular/angular/commit/b9b557c))
* **core:** detectChanges() doesn't work on detached instance ([4d6ac9d](https://github.com/angular/angular/commit/4d6ac9d)), closes [#13426](https://github.com/angular/angular/issues/13426) [#13472](https://github.com/angular/angular/issues/13472) * **core:** detectChanges() doesn't work on detached instance ([4d6ac9d](https://github.com/angular/angular/commit/4d6ac9d)), closes [#13426](https://github.com/angular/angular/issues/13426) [#13472](https://github.com/angular/angular/issues/13472)
* **core:** properly destroy embedded Views attatched to ApplicationRef ([#13459](https://github.com/angular/angular/issues/13459)) ([d40bbf4](https://github.com/angular/angular/commit/d40bbf4)), closes [#13062](https://github.com/angular/angular/issues/13062) * **core:** properly destroy embedded Views attatched to ApplicationRef ([#13459](https://github.com/angular/angular/issues/13459)) ([d40bbf4](https://github.com/angular/angular/commit/d40bbf4)), closes [#13062](https://github.com/angular/angular/issues/13062)
* **dom_adapter:** remove logError from logGroup ([#12925](https://github.com/angular/angular/issues/12925)) ([5fab871](https://github.com/angular/angular/commit/5fab871)) * **core:** remove logError from logGroup ([#12925](https://github.com/angular/angular/issues/12925)) ([5fab871](https://github.com/angular/angular/commit/5fab871))
* **forms:** ensure `select[multiple]` retains selections ([b3dcff0](https://github.com/angular/angular/commit/b3dcff0)), closes [#12527](https://github.com/angular/angular/issues/12527) [#12654](https://github.com/angular/angular/issues/12654) * **forms:** ensure `select[multiple]` retains selections ([b3dcff0](https://github.com/angular/angular/commit/b3dcff0)), closes [#12527](https://github.com/angular/angular/issues/12527) [#12654](https://github.com/angular/angular/issues/12654)
* **forms:** fix Validators.min/maxLength with FormArray ([#13095](https://github.com/angular/angular/issues/13095)) ([7383e4a](https://github.com/angular/angular/commit/7383e4a)), closes [#13089](https://github.com/angular/angular/issues/13089) * **forms:** fix Validators.min/maxLength with FormArray ([#13095](https://github.com/angular/angular/issues/13095)) ([7383e4a](https://github.com/angular/angular/commit/7383e4a)), closes [#13089](https://github.com/angular/angular/issues/13089)
* **forms:** introduce checkbox required validator ([124267c](https://github.com/angular/angular/commit/124267c)), closes [#11459](https://github.com/angular/angular/issues/11459) [#13364](https://github.com/angular/angular/issues/13364) * **forms:** introduce checkbox required validator ([124267c](https://github.com/angular/angular/commit/124267c)), closes [#11459](https://github.com/angular/angular/issues/11459) [#13364](https://github.com/angular/angular/issues/13364)
* **http:** check response body text against undefined ([#13017](https://github.com/angular/angular/issues/13017)) ([f106a18](https://github.com/angular/angular/commit/f106a18)) * **http:** check response body text against undefined ([#13017](https://github.com/angular/angular/issues/13017)) ([f106a18](https://github.com/angular/angular/commit/f106a18))
* **http:** create a copy of headers when merge options ([#13365](https://github.com/angular/angular/issues/13365)) ([65c9b5b](https://github.com/angular/angular/commit/65c9b5b)), closes [#11980](https://github.com/angular/angular/issues/11980) * **http:** create a copy of headers when merge options ([#13365](https://github.com/angular/angular/issues/13365)) ([65c9b5b](https://github.com/angular/angular/commit/65c9b5b)), closes [#11980](https://github.com/angular/angular/issues/11980)
* **language-service:** correctly type `undefined` ([0a7364f](https://github.com/angular/angular/commit/0a7364f)), closes [#13412](https://github.com/angular/angular/issues/13412) [#13414](https://github.com/angular/angular/issues/13414) * **language-service:** correctly type `undefined` ([0a7364f](https://github.com/angular/angular/commit/0a7364f)), closes [#13412](https://github.com/angular/angular/issues/13412) [#13414](https://github.com/angular/angular/issues/13414)
* Better error when directive not listed in NgModule.declarations ([b0cd514](https://github.com/angular/angular/commit/b0cd514)) * **compiler**: better error when directive not listed in NgModule.declarations ([b0cd514](https://github.com/angular/angular/commit/b0cd514))
* Better instructions on running examples and their tests ([203cc7e](https://github.com/angular/angular/commit/203cc7e))
* **language-service:** treat string unions as strings ([#13406](https://github.com/angular/angular/issues/13406)) ([14dd2b3](https://github.com/angular/angular/commit/14dd2b3)), closes [#13403](https://github.com/angular/angular/issues/13403) * **language-service:** treat string unions as strings ([#13406](https://github.com/angular/angular/issues/13406)) ([14dd2b3](https://github.com/angular/angular/commit/14dd2b3)), closes [#13403](https://github.com/angular/angular/issues/13403)
* **router:** add support for query params with multiple values ([e4d5a5f](https://github.com/angular/angular/commit/e4d5a5f)), closes [#11373](https://github.com/angular/angular/issues/11373) * **router:** add support for query params with multiple values ([e4d5a5f](https://github.com/angular/angular/commit/e4d5a5f)), closes [#11373](https://github.com/angular/angular/issues/11373)
* **router:** Use T type in Resolve interface ([#13242](https://github.com/angular/angular/issues/13242)) ([5ee8155](https://github.com/angular/angular/commit/5ee8155)) * **router:** Use T type in Resolve interface ([#13242](https://github.com/angular/angular/issues/13242)) ([5ee8155](https://github.com/angular/angular/commit/5ee8155))
* **selector:** SelectorMatcher match elements with :not selector ([#12977](https://github.com/angular/angular/issues/12977)) ([392c9ac](https://github.com/angular/angular/commit/392c9ac)) * **selector:** SelectorMatcher match elements with :not selector ([#12977](https://github.com/angular/angular/issues/12977)) ([392c9ac](https://github.com/angular/angular/commit/392c9ac))
* **tsc-wrapped:** generate metadata for exports without module specifier ([cd03c77](https://github.com/angular/angular/commit/cd03c77)), closes [#13327](https://github.com/angular/angular/issues/13327) * **tsc-wrapped:** generate metadata for exports without module specifier ([cd03c77](https://github.com/angular/angular/commit/cd03c77)), closes [#13327](https://github.com/angular/angular/issues/13327)
* **upgrade:** fix downgrade content projection and injector inheritance ([86c5098](https://github.com/angular/angular/commit/86c5098)), closes [#6629](https://github.com/angular/angular/issues/6629) [#7727](https://github.com/angular/angular/issues/7727) [#8729](https://github.com/angular/angular/issues/8729) [#9643](https://github.com/angular/angular/issues/9643) [#9649](https://github.com/angular/angular/issues/9649) [#12675](https://github.com/angular/angular/issues/12675) * **upgrade:** fix downgrade content projection and injector inheritance ([86c5098](https://github.com/angular/angular/commit/86c5098)), closes [#6629](https://github.com/angular/angular/issues/6629) [#7727](https://github.com/angular/angular/issues/7727) [#8729](https://github.com/angular/angular/issues/8729) [#9643](https://github.com/angular/angular/issues/9643) [#9649](https://github.com/angular/angular/issues/9649) [#12675](https://github.com/angular/angular/issues/12675)
### Features
* **upgrade:** enable Angular 1 unit testing of upgrade module ([2fc0560](https://github.com/angular/angular/commit/2fc0560)), closes [#5462](https://github.com/angular/angular/issues/5462) [#12675](https://github.com/angular/angular/issues/12675) * **upgrade:** enable Angular 1 unit testing of upgrade module ([2fc0560](https://github.com/angular/angular/commit/2fc0560)), closes [#5462](https://github.com/angular/angular/issues/5462) [#12675](https://github.com/angular/angular/issues/12675)
@ -46,6 +61,16 @@
* **animations:** always run the animation queue outside of zones ([e2622ad](https://github.com/angular/angular/commit/e2622ad)), closes [#13440](https://github.com/angular/angular/issues/13440) * **animations:** always run the animation queue outside of zones ([e2622ad](https://github.com/angular/angular/commit/e2622ad)), closes [#13440](https://github.com/angular/angular/issues/13440)
### Note ###
Due to regression in the 2.3.0 release that was fixed by [#13464](https://github.com/angular/angular/pull/13464),
components that have been compiled using 2.3.0 and published to npm will need to be recompiled and republished.
The >=2.3.1 compiler will issue is the following error if it encounters components compiled with 2.3.0:
`Unsupported metadata version 2 for module ${module}. This module should be compiled with a newer version of ngc`.
We are adding more tests to our test suite to catch these kinds of problems before we cut a release.
<a name="2.3.0"></a> <a name="2.3.0"></a>
# [2.3.0](https://github.com/angular/angular/compare/2.3.0-rc.0...2.3.0) (2016-12-07) # [2.3.0](https://github.com/angular/angular/compare/2.3.0-rc.0...2.3.0) (2016-12-07)

View File

@ -1,6 +1,6 @@
# Building and Testing Angular 2 for JS # Building and Testing Angular
This document describes how to set up your development environment to build and test Angular 2 JS version. This document describes how to set up your development environment to build and test Angular.
It also explains the basic mechanics of using `git`, `node`, and `npm`. It also explains the basic mechanics of using `git`, `node`, and `npm`.
* [Prerequisite Software](#prerequisite-software) * [Prerequisite Software](#prerequisite-software)
@ -137,4 +137,21 @@ You can automatically format your code by running:
$ gulp format $ gulp format
``` ```
## Publishing your own personal snapshot build
You may find that your un-merged change needs some validation from external participants.
Rather than requiring them to pull your Pull Request and build Angular locally, you can
publish the `*-builds` snapshots just like our Travis build does.
First time, you need to create the github repositories:
``` shell
$ export TOKEN=[get one from https://github.com/settings/tokens]
$ CREATE_REPOS=1 ./scripts/publish/publish-build-artifacts.sh [github username]
```
For subsequent snapshots, just run
``` shell
$ ./scripts/publish/publish-build-artifacts.sh [github username]
```

View File

@ -24,17 +24,25 @@ 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.js', 'node_modules/angular/angular.js',
'node_modules/angular-mocks/angular-mocks.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/proxy.js', 'node_modules/zone.js/dist/sync-test.js', 'node_modules/zone.js/dist/long-stack-trace-zone.js',
'node_modules/zone.js/dist/jasmine-patch.js', 'node_modules/zone.js/dist/async-test.js', 'node_modules/zone.js/dist/proxy.js',
'node_modules/zone.js/dist/sync-test.js',
'node_modules/zone.js/dist/jasmine-patch.js',
'node_modules/zone.js/dist/async-test.js',
'node_modules/zone.js/dist/fake-async-test.js', 'node_modules/zone.js/dist/fake-async-test.js',
// Including systemjs because it defines `__eval`, which produces correct stack traces. // Including systemjs because it defines `__eval`, which produces correct stack traces.
'shims_for_IE.js', 'node_modules/systemjs/dist/system.src.js', 'shims_for_IE.js',
'node_modules/systemjs/dist/system.src.js',
{pattern: 'node_modules/rxjs/**', included: false, watched: false, served: true}, {pattern: 'node_modules/rxjs/**', included: false, watched: false, served: true},
'node_modules/reflect-metadata/Reflect.js', 'tools/build/file2modulename.js', 'test-main.js', 'node_modules/reflect-metadata/Reflect.js',
{pattern: 'dist/all/empty.*', included: false, watched: false}, { 'tools/build/file2modulename.js',
'test-main.js',
{pattern: 'dist/all/empty.*', included: false, watched: false},
{
pattern: 'modules/@angular/platform-browser/test/static_assets/**', pattern: 'modules/@angular/platform-browser/test/static_assets/**',
included: false, included: false,
watched: false watched: false

View File

@ -7,7 +7,7 @@
"dependencies": { "dependencies": {
"@angular/core": "^2.0.0-rc.7", "@angular/core": "^2.0.0-rc.7",
"reflect-metadata": "^0.1.2", "reflect-metadata": "^0.1.2",
"rxjs": "5.0.0-rc.4", "rxjs": "^5.0.1",
"jpm": "1.1.4", "jpm": "1.1.4",
"firefox-profile": "0.4.0", "firefox-profile": "0.4.0",
"selenium-webdriver": "^2.53.3" "selenium-webdriver": "^2.53.3"

View File

@ -1,5 +1,5 @@
<div> <div>
<h1>hello world</h1> <h1 i18n>hello world</h1>
<a [routerLink]="['lazy']">lazy</a> <a [routerLink]="['lazy']">lazy</a>
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div> </div>

View File

@ -34,9 +34,9 @@ const EXPECTED_XMB = `<?xml version="1.0" encoding="UTF-8" ?>
<!ELEMENT ex (#PCDATA)> <!ELEMENT ex (#PCDATA)>
]> ]>
<messagebundle> <messagebundle>
<msg id="3772663375917578720">other-3rdP-component</msg>
<msg id="8136548302122759730" desc="desc" meaning="meaning">translate me</msg> <msg id="8136548302122759730" desc="desc" meaning="meaning">translate me</msg>
<msg id="3492007542396725315">Welcome</msg> <msg id="3492007542396725315">Welcome</msg>
<msg id="3772663375917578720">other-3rdP-component</msg>
</messagebundle> </messagebundle>
`; `;
@ -44,10 +44,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/>
@ -58,6 +54,10 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
<source>Welcome</source> <source>Welcome</source>
<target/> <target/>
</trans-unit> </trans-unit>
<trans-unit id="63a85808f03b8181e36a952e0fa38202c2304862" datatype="html">
<source>other-3rdP-component</source>
<target/>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@ -19,7 +19,6 @@ import {AngularCompilerOptions, CodeGenerator, CompilerHostContext, NodeCompiler
const glob = require('glob'); const glob = require('glob');
/** /**
* Main method. * Main method.
* Standalone program that executes codegen using the ngtools API and tests that files were * Standalone program that executes codegen using the ngtools API and tests that files were
@ -30,6 +29,7 @@ function main() {
Promise.resolve() Promise.resolve()
.then(() => codeGenTest()) .then(() => codeGenTest())
.then(() => i18nTest())
.then(() => lazyRoutesTest()) .then(() => lazyRoutesTest())
.then(() => { .then(() => {
console.log('All done!'); console.log('All done!');
@ -42,7 +42,6 @@ function main() {
}); });
} }
function codeGenTest() { function codeGenTest() {
const basePath = path.join(__dirname, '../ngtools_src'); const basePath = path.join(__dirname, '../ngtools_src');
const project = path.join(basePath, 'tsconfig-build.json'); const project = path.join(basePath, 'tsconfig-build.json');
@ -52,12 +51,9 @@ function codeGenTest() {
const config = tsc.readConfiguration(project, basePath); const config = tsc.readConfiguration(project, basePath);
const hostContext = new NodeCompilerHostContext(); const hostContext = new NodeCompilerHostContext();
const delegateHost = ts.createCompilerHost(config.parsed.options, true); const delegateHost = ts.createCompilerHost(config.parsed.options, true);
const host: ts.CompilerHost = Object.assign({}, delegateHost, { const host: ts.CompilerHost = Object.assign(
writeFile: (fileName: string, ...rest: any[]) => { {}, delegateHost,
wroteFiles.push(fileName); {writeFile: (fileName: string, ...rest: any[]) => { wroteFiles.push(fileName); }});
return delegateHost.writeFile.call(delegateHost, fileName, ...rest);
}
});
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host); const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
config.ngOptions.basePath = basePath; config.ngOptions.basePath = basePath;
@ -112,6 +108,67 @@ function codeGenTest() {
}); });
} }
function i18nTest() {
const basePath = path.join(__dirname, '../ngtools_src');
const project = path.join(basePath, 'tsconfig-build.json');
const readResources: string[] = [];
const wroteFiles: string[] = [];
const config = tsc.readConfiguration(project, basePath);
const hostContext = new NodeCompilerHostContext();
const delegateHost = ts.createCompilerHost(config.parsed.options, true);
const host: ts.CompilerHost = Object.assign(
{}, delegateHost,
{writeFile: (fileName: string, ...rest: any[]) => { wroteFiles.push(fileName); }});
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
config.ngOptions.basePath = basePath;
console.log(`>>> running i18n extraction for ${project}`);
return __NGTOOLS_PRIVATE_API_2
.extractI18n({
basePath,
compilerOptions: config.parsed.options, program, host,
angularCompilerOptions: config.ngOptions,
i18nFormat: 'xlf',
readResource: (fileName: string) => {
readResources.push(fileName);
return hostContext.readResource(fileName);
},
})
.then(() => {
console.log(`>>> i18n extraction done, asserting read and wrote files`);
const allFiles = glob.sync(path.join(basePath, '**/*'), {nodir: true});
assert(wroteFiles.length == 1, `Expected a single message bundle file.`);
assert(
wroteFiles[0].endsWith('/ngtools_src/messages.xlf'),
`Expected the bundle file to be "message.xlf".`);
allFiles.forEach((fileName: string) => {
// Skip tsconfig.
if (fileName.match(/tsconfig-build.json$/)) {
return;
}
// Assert that file was read.
if (fileName.match(/\.css$/) || fileName.match(/\.html$/)) {
assert(
readResources.indexOf(fileName) != -1,
`Expected resource "${fileName}" to be read.`);
}
});
console.log(`done, no errors.`);
})
.catch((e: any) => {
console.error(e.stack);
console.error('Extraction failed');
throw e;
});
}
function lazyRoutesTest() { function lazyRoutesTest() {
const basePath = path.join(__dirname, '../ngtools_src'); const basePath = path.join(__dirname, '../ngtools_src');

View File

@ -27,9 +27,14 @@ function main() {
const basePath = path.resolve(__dirname, '..'); const basePath = path.resolve(__dirname, '..');
const project = path.resolve(basePath, 'tsconfig-build.json'); const project = path.resolve(basePath, 'tsconfig-build.json');
const readFiles: string[] = []; const readFiles: string[] = [];
const writtenFiles: {fileName: string, content: string}[] = [];
class AssertingHostContext extends NodeCompilerHostContext { class AssertingHostContext extends NodeCompilerHostContext {
readFile(fileName: string): string { readFile(fileName: string): string {
if (/.*\/node_modules\/.*/.test(fileName) && !/.*ngsummary\.json$/.test(fileName)) {
// Only allow to read summaries from node_modules
return null;
}
readFiles.push(path.relative(basePath, fileName)); readFiles.push(path.relative(basePath, fileName));
return super.readFile(fileName); return super.readFile(fileName);
} }
@ -45,16 +50,29 @@ function main() {
config.ngOptions.generateCodeForLibraries = false; config.ngOptions.generateCodeForLibraries = false;
console.log(`>>> running codegen for ${project}`); console.log(`>>> running codegen for ${project}`);
codegen(config, (host) => new AssertingHostContext()) codegen(
config,
(host) => {
host.writeFile = (fileName: string, content: string) => {
fileName = path.relative(basePath, fileName);
writtenFiles.push({fileName, content});
};
return new AssertingHostContext();
})
.then((exitCode: any) => { .then((exitCode: any) => {
console.log(`>>> codegen done, asserting read files`); console.log(`>>> codegen done, asserting read files`);
assertSomeFileMatch(readFiles, /^node_modules\/.*\.ngsummary\.json$/); assertSomeFileMatch(readFiles, /^node_modules\/.*\.ngsummary\.json$/);
assertNoFileMatch(readFiles, /^node_modules\/.*\.metadata.json$/);
assertNoFileMatch(readFiles, /^node_modules\/.*\.html$/); assertNoFileMatch(readFiles, /^node_modules\/.*\.html$/);
assertNoFileMatch(readFiles, /^node_modules\/.*\.css$/); assertNoFileMatch(readFiles, /^node_modules\/.*\.css$/);
assertNoFileMatch(readFiles, /^src\/.*\.ngsummary\.json$/); assertNoFileMatch(readFiles, /^src\/.*\.ngsummary\.json$/);
assertSomeFileMatch(readFiles, /^src\/.*\.html$/); assertSomeFileMatch(readFiles, /^src\/.*\.html$/);
assertSomeFileMatch(readFiles, /^src\/.*\.css$/); assertSomeFileMatch(readFiles, /^src\/.*\.css$/);
console.log(`>>> asserting written files`);
assertWrittenFile(writtenFiles, /^src\/module\.ngfactory\.ts$/, /class MainModuleInjector/);
console.log(`done, no errors.`); console.log(`done, no errors.`);
process.exit(exitCode); process.exit(exitCode);
}) })
@ -97,4 +115,11 @@ function assertNoFileMatch(fileNames: string[], pattern: RegExp) {
`Expected no read files match ${pattern}, but found: \n${matches.join('\n')}`); `Expected no read files match ${pattern}, but found: \n${matches.join('\n')}`);
} }
function assertWrittenFile(
files: {fileName: string, content: string}[], filePattern: RegExp, contentPattern: RegExp) {
assert(
files.some(file => filePattern.test(file.fileName) && contentPattern.test(file.content)),
`Expected some written files for ${filePattern} and content ${contentPattern}`);
}
main(); main();

View File

@ -21,9 +21,7 @@ import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './
import {PathMappedCompilerHost} from './path_mapped_compiler_host'; import {PathMappedCompilerHost} from './path_mapped_compiler_host';
import {Console} from './private_import_core'; import {Console} from './private_import_core';
const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$/;
const GENERATED_META_FILES = /\.json$/; const GENERATED_META_FILES = /\.json$/;
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.ngstyle\.ts$/;
const PREAMBLE = `/** const PREAMBLE = `/**
* @fileoverview This file is generated by the Angular 2 template compiler. * @fileoverview This file is generated by the Angular 2 template compiler.
@ -102,14 +100,8 @@ export class CodeGenerator {
debug: options.debug === true, debug: options.debug === true,
translations: transContent, translations: transContent,
i18nFormat: cliOptions.i18nFormat, i18nFormat: cliOptions.i18nFormat,
locale: cliOptions.locale, locale: cliOptions.locale
excludeFilePattern: options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES :
GENERATED_FILES
}); });
return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost); return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost);
} }
} }
export function excludeFilePattern(options: AngularCompilerOptions): RegExp {
return options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
}

View File

@ -16,6 +16,8 @@ const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/; const DTS = /\.d\.ts$/;
const NODE_MODULES = '/node_modules/'; const NODE_MODULES = '/node_modules/';
const IS_GENERATED = /\.(ngfactory|ngstyle)$/; const IS_GENERATED = /\.(ngfactory|ngstyle)$/;
const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$/;
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.ngstyle\.ts$/;
export interface CompilerHostContext extends ts.ModuleResolutionHost { export interface CompilerHostContext extends ts.ModuleResolutionHost {
readResource(fileName: string): Promise<string>; readResource(fileName: string): Promise<string>;
@ -28,6 +30,7 @@ export class CompilerHost implements AotCompilerHost {
protected basePath: string; protected basePath: string;
private genDir: string; private genDir: string;
private resolverCache = new Map<string, ModuleMetadata[]>(); private resolverCache = new Map<string, ModuleMetadata[]>();
protected resolveModuleNameHost: CompilerHostContext;
constructor( constructor(
protected program: ts.Program, protected options: AngularCompilerOptions, protected program: ts.Program, protected options: AngularCompilerOptions,
@ -38,12 +41,31 @@ export class CompilerHost implements AotCompilerHost {
const genPath: string = path.relative(this.basePath, this.genDir); const genPath: string = path.relative(this.basePath, this.genDir);
this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..'); this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..');
this.resolveModuleNameHost = Object.create(this.context);
// When calling ts.resolveModuleName,
// additional allow checks for .d.ts files to be done based on
// checks for .ngsummary.json files,
// so that our codegen depends on fewer inputs and requires to be called
// less often.
// This is needed as we use ts.resolveModuleName in reflector_host
// and it should be able to resolve summary file names.
this.resolveModuleNameHost.fileExists = (fileName: string): boolean => {
if (this.context.fileExists(fileName)) {
return true;
}
if (DTS.test(fileName)) {
const base = fileName.substring(0, fileName.length - 5);
return this.context.fileExists(base + '.ngsummary.json');
}
return false;
};
} }
// We use absolute paths on disk as canonical. // We use absolute paths on disk as canonical.
getCanonicalFileName(fileName: string): string { return fileName; } getCanonicalFileName(fileName: string): string { return fileName; }
moduleNameToFileName(m: string, containingFile: string) { moduleNameToFileName(m: string, containingFile: string): string|null {
if (!containingFile || !containingFile.length) { if (!containingFile || !containingFile.length) {
if (m.indexOf('.') === 0) { if (m.indexOf('.') === 0) {
throw new Error('Resolution of relative paths requires a containing file.'); throw new Error('Resolution of relative paths requires a containing file.');
@ -53,7 +75,8 @@ export class CompilerHost implements AotCompilerHost {
} }
m = m.replace(EXT, ''); m = m.replace(EXT, '');
const resolved = const resolved =
ts.resolveModuleName(m, containingFile.replace(/\\/g, '/'), this.options, this.context) ts.resolveModuleName(
m, containingFile.replace(/\\/g, '/'), this.options, this.resolveModuleNameHost)
.resolvedModule; .resolvedModule;
return resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null; return resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null;
}; };
@ -158,6 +181,12 @@ export class CompilerHost implements AotCompilerHost {
const metadataPath = filePath.replace(DTS, '.metadata.json'); const metadataPath = filePath.replace(DTS, '.metadata.json');
if (this.context.fileExists(metadataPath)) { if (this.context.fileExists(metadataPath)) {
return this.readMetadata(metadataPath, filePath); return this.readMetadata(metadataPath, filePath);
} else {
// If there is a .d.ts file but no metadata file we need to produce a
// v3 metadata from the .d.ts file as v3 includes the exports we need
// to resolve symbols.
return [this.upgradeVersion1Metadata(
{'__symbolic': 'module', 'version': 1, 'metadata': {}}, filePath)];
} }
} else { } else {
const sf = this.getSourceFile(filePath); const sf = this.getSourceFile(filePath);
@ -173,16 +202,27 @@ export class CompilerHost implements AotCompilerHost {
} }
try { try {
const metadataOrMetadatas = JSON.parse(this.context.readFile(filePath)); const metadataOrMetadatas = JSON.parse(this.context.readFile(filePath));
const metadatas = metadataOrMetadatas ? const metadatas: ModuleMetadata[] = metadataOrMetadatas ?
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) : (Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
[]; [];
const v1Metadata = metadatas.find((m: any) => m['version'] === 1); const v1Metadata = metadatas.find(m => m.version === 1);
let v3Metadata = metadatas.find((m: any) => m['version'] === 3); let v3Metadata = metadatas.find(m => m.version === 3);
if (!v3Metadata && v1Metadata) { if (!v3Metadata && v1Metadata) {
metadatas.push(this.upgradeVersion1Metadata(v1Metadata, dtsFilePath));
}
this.resolverCache.set(filePath, metadatas);
return metadatas;
} catch (e) {
console.error(`Failed to read JSON file ${filePath}`);
throw e;
}
}
private upgradeVersion1Metadata(v1Metadata: ModuleMetadata, dtsFilePath: string): ModuleMetadata {
// patch up v1 to v3 by merging the metadata with metadata collected from the d.ts file // patch up v1 to v3 by merging the metadata with metadata collected from the d.ts file
// as the only difference between the versions is whether all exports are contained in // as the only difference between the versions is whether all exports are contained in
// the metadata and the `extends` clause. // the metadata and the `extends` clause.
v3Metadata = {'__symbolic': 'module', 'version': 3, 'metadata': {}}; let v3Metadata: ModuleMetadata = {'__symbolic': 'module', 'version': 3, 'metadata': {}};
if (v1Metadata.exports) { if (v1Metadata.exports) {
v3Metadata.exports = v1Metadata.exports; v3Metadata.exports = v1Metadata.exports;
} }
@ -201,23 +241,26 @@ export class CompilerHost implements AotCompilerHost {
v3Metadata.exports = exports.exports; v3Metadata.exports = exports.exports;
} }
} }
metadatas.push(v3Metadata); return v3Metadata;
}
this.resolverCache.set(filePath, metadatas);
return metadatas;
} catch (e) {
console.error(`Failed to read JSON file ${filePath}`);
throw e;
}
} }
loadResource(filePath: string): Promise<string> { return this.context.readResource(filePath); } loadResource(filePath: string): Promise<string> { return this.context.readResource(filePath); }
loadSummary(filePath: string): string { return this.context.readFile(filePath); } loadSummary(filePath: string): string|null {
if (this.context.fileExists(filePath)) {
return this.context.readFile(filePath);
}
}
getOutputFileName(sourceFilePath: string): string { getOutputFileName(sourceFilePath: string): string {
return sourceFilePath.replace(EXT, '') + '.d.ts'; return sourceFilePath.replace(EXT, '') + '.d.ts';
} }
isSourceFile(filePath: string): boolean {
const excludeRegex =
this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
return !excludeRegex.test(filePath);
}
} }
export class CompilerHostContextAdapter { export class CompilerHostContextAdapter {

View File

@ -14,41 +14,15 @@
// 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 tsc from '@angular/tsc-wrapped'; import * as tsc from '@angular/tsc-wrapped';
import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {Extractor} from './extractor'; import {Extractor} from './extractor';
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): Promise<void> {
const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host); return Extractor.create(ngOptions, program, host).extract(cliOptions.i18nFormat);
const bundlePromise: Promise<compiler.MessageBundle> = extractor.extract();
return (bundlePromise).then(messageBundle => {
let ext: string;
let serializer: compiler.Serializer;
const format = (cliOptions.i18nFormat || 'xlf').toLowerCase();
switch (format) {
case 'xmb':
ext = 'xmb';
serializer = new compiler.Xmb();
break;
case 'xliff':
case 'xlf':
default:
ext = 'xlf';
serializer = new compiler.Xliff();
break;
}
const dstPath = path.join(ngOptions.genDir, `messages.${ext}`);
host.writeFile(dstPath, messageBundle.write(serializer), false);
});
} }
// Entry point // Entry point

View File

@ -15,29 +15,74 @@ import 'reflect-metadata';
import * as compiler from '@angular/compiler'; import * as compiler from '@angular/compiler';
import * as tsc from '@angular/tsc-wrapped'; import * as tsc from '@angular/tsc-wrapped';
import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {excludeFilePattern} from './codegen'; import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
import {CompilerHost, ModuleResolutionHostAdapter} from './compiler_host'; import {PathMappedCompilerHost} from './path_mapped_compiler_host';
export class Extractor { export class Extractor {
constructor( constructor(
private ngExtractor: compiler.Extractor, private ngCompilerHost: CompilerHost, private options: tsc.AngularCompilerOptions, private ngExtractor: compiler.Extractor,
public host: ts.CompilerHost, private ngCompilerHost: CompilerHost,
private program: ts.Program) {} private program: ts.Program) {}
extract(): Promise<compiler.MessageBundle> { extract(formatName: string): Promise<void> {
return this.ngExtractor.extract(this.program.getSourceFiles().map( // Checks the format and returns the extension
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName))); const ext = this.getExtension(formatName);
const promiseBundle = this.extractBundle();
return promiseBundle.then(bundle => {
const content = this.serialize(bundle, ext);
const dstPath = path.join(this.options.genDir, `messages.${ext}`);
this.host.writeFile(dstPath, content, false);
});
}
extractBundle(): Promise<compiler.MessageBundle> {
const files = this.program.getSourceFiles().map(
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName));
return this.ngExtractor.extract(files);
}
serialize(bundle: compiler.MessageBundle, ext: string): string {
let serializer: compiler.Serializer;
switch (ext) {
case 'xmb':
serializer = new compiler.Xmb();
break;
case 'xlf':
default:
serializer = new compiler.Xliff();
}
return bundle.write(serializer);
}
getExtension(formatName: string): string {
const format = (formatName || 'xlf').toLowerCase();
if (format === 'xmb') return 'xmb';
if (format === 'xlf' || format === 'xlif') return 'xlf';
throw new Error('Unsupported format "${formatName}"');
} }
static create( static create(
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program, options: tsc.AngularCompilerOptions, program: ts.Program, tsCompilerHost: ts.CompilerHost,
moduleResolverHost: ts.ModuleResolutionHost, ngCompilerHost?: CompilerHost): Extractor { compilerHostContext?: CompilerHostContext, ngCompilerHost?: CompilerHost): Extractor {
if (!ngCompilerHost) if (!ngCompilerHost) {
ngCompilerHost = const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
new CompilerHost(program, options, new ModuleResolutionHostAdapter(moduleResolverHost)); const context = compilerHostContext || new ModuleResolutionHostAdapter(tsCompilerHost);
const {extractor: ngExtractor} = compiler.Extractor.create( ngCompilerHost = usePathMapping ? new PathMappedCompilerHost(program, options, context) :
ngCompilerHost, {excludeFilePattern: excludeFilePattern(options)}); new CompilerHost(program, options, context);
return new Extractor(ngExtractor, ngCompilerHost, program); }
const {extractor: ngExtractor} = compiler.Extractor.create(ngCompilerHost);
return new Extractor(options, ngExtractor, tsCompilerHost, ngCompilerHost, program);
} }
} }

View File

@ -14,6 +14,7 @@ import 'reflect-metadata';
import * as ts from 'typescript'; import * as ts from 'typescript';
import * as tsc from '@angular/tsc-wrapped'; import * as tsc from '@angular/tsc-wrapped';
import {SyntaxError} from '@angular/compiler';
import {CodeGenerator} from './codegen'; import {CodeGenerator} from './codegen';
function codegen( function codegen(
@ -28,7 +29,7 @@ export function main(
const cliOptions = new tsc.NgcCliOptions(args); const cliOptions = new tsc.NgcCliOptions(args);
return tsc.main(project, cliOptions, codegen).then(() => 0).catch(e => { return tsc.main(project, cliOptions, codegen).then(() => 0).catch(e => {
if (e instanceof tsc.UserError) { if (e instanceof tsc.UserError || e instanceof SyntaxError) {
consoleError(e.message); consoleError(e.message);
return Promise.resolve(1); return Promise.resolve(1);
} else { } else {

View File

@ -13,16 +13,16 @@
* something else. * something else.
*/ */
import {AotCompilerHost, StaticReflector} from '@angular/compiler'; import {AotCompilerHost, AotSummaryResolver, StaticReflector, StaticSymbolCache, StaticSymbolResolver} from '@angular/compiler';
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped'; import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {CodeGenerator} from './codegen'; import {CodeGenerator} from './codegen';
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host'; import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
import {Extractor} from './extractor';
import {listLazyRoutesOfModule} from './ngtools_impl'; import {listLazyRoutesOfModule} from './ngtools_impl';
import {PathMappedCompilerHost} from './path_mapped_compiler_host'; import {PathMappedCompilerHost} from './path_mapped_compiler_host';
export interface NgTools_InternalApi_NG2_CodeGen_Options { export interface NgTools_InternalApi_NG2_CodeGen_Options {
basePath: string; basePath: string;
compilerOptions: ts.CompilerOptions; compilerOptions: ts.CompilerOptions;
@ -50,9 +50,18 @@ export interface NgTools_InternalApi_NG2_ListLazyRoutes_Options {
// Every new property under this line should be optional. // Every new property under this line should be optional.
} }
export interface NgTools_InternalApi_NG_2_LazyRouteMap { [route: string]: string; } export interface NgTools_InternalApi_NG_2_LazyRouteMap { [route: string]: string; }
export interface NgTools_InternalApi_NG2_ExtractI18n_Options {
basePath: string;
compilerOptions: ts.CompilerOptions;
program: ts.Program;
host: ts.CompilerHost;
angularCompilerOptions: AngularCompilerOptions;
i18nFormat: string;
readResource: (fileName: string) => Promise<string>;
// Every new property under this line should be optional.
}
/** /**
* A ModuleResolutionHostAdapter that overrides the readResource() method with the one * A ModuleResolutionHostAdapter that overrides the readResource() method with the one
@ -94,7 +103,6 @@ export class NgTools_InternalApi_NG_2 {
return codeGenerator.codegen(); return codeGenerator.codegen();
} }
/** /**
* @internal * @internal
* @private * @private
@ -111,7 +119,10 @@ export class NgTools_InternalApi_NG_2 {
new PathMappedCompilerHost(program, angularCompilerOptions, moduleResolutionHost) : new PathMappedCompilerHost(program, angularCompilerOptions, moduleResolutionHost) :
new CompilerHost(program, angularCompilerOptions, moduleResolutionHost); new CompilerHost(program, angularCompilerOptions, moduleResolutionHost);
const staticReflector = new StaticReflector(ngCompilerHost); const symbolCache = new StaticSymbolCache();
const summaryResolver = new AotSummaryResolver(ngCompilerHost, symbolCache);
const symbolResolver = new StaticSymbolResolver(ngCompilerHost, symbolCache, summaryResolver);
const staticReflector = new StaticReflector(symbolResolver);
const routeMap = listLazyRoutesOfModule(options.entryModule, ngCompilerHost, staticReflector); const routeMap = listLazyRoutesOfModule(options.entryModule, ngCompilerHost, staticReflector);
return Object.keys(routeMap).reduce( return Object.keys(routeMap).reduce(
@ -121,4 +132,19 @@ export class NgTools_InternalApi_NG_2 {
}, },
{}); {});
} }
/**
* @internal
* @private
*/
static extractI18n(options: NgTools_InternalApi_NG2_ExtractI18n_Options): Promise<void> {
const hostContext: CompilerHostContext =
new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host);
// Create the i18n extractor.
const extractor = Extractor.create(
options.angularCompilerOptions, options.program, options.host, hostContext);
return extractor.extract(options.i18nFormat);
}
} }

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 {ModuleMetadata} from '@angular/tsc-wrapped';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {CompilerHost} from '../src/compiler_host'; import {CompilerHost} from '../src/compiler_host';
@ -150,12 +151,14 @@ describe('CompilerHost', () => {
it('should be able to read a metadata file', () => { it('should be able to read a metadata file', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts')).toEqual([ expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts')).toEqual([
{__symbolic: 'module', version: 2, metadata: {foo: {__symbolic: 'class'}}} {__symbolic: 'module', version: 3, metadata: {foo: {__symbolic: 'class'}}}
]); ]);
}); });
it('should be able to read metadata from an otherwise unused .d.ts file ', () => { it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts')).toBeUndefined(); expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts')).toEqual([
dummyMetadata
]);
}); });
it('should be able to read empty metadata ', () => { it('should be able to read empty metadata ', () => {
@ -181,10 +184,21 @@ describe('CompilerHost', () => {
} }
]); ]);
}); });
it('should upgrade a missing metadata file into v3', () => {
expect(hostNestedGenDir.getMetadataFor('metadata_versions/v1_empty.d.ts')).toEqual([
{__symbolic: 'module', version: 3, metadata: {}, exports: [{from: './lib/utils'}]}
]);
});
}); });
const dummyModule = 'export let foo: any[];'; const dummyModule = 'export let foo: any[];';
const dummyMetadata: ModuleMetadata = {
__symbolic: 'module',
version: 3,
metadata:
{foo: {__symbolic: 'error', message: 'Variable not initialized', line: 0, character: 11}}
};
const FILES: Entry = { const FILES: Entry = {
'tmp': { 'tmp': {
'src': { 'src': {
@ -204,7 +218,7 @@ const FILES: Entry = {
'@angular': { '@angular': {
'core.d.ts': dummyModule, 'core.d.ts': dummyModule,
'core.metadata.json': 'core.metadata.json':
`{"__symbolic":"module", "version": 2, "metadata": {"foo": {"__symbolic": "class"}}}`, `{"__symbolic":"module", "version": 3, "metadata": {"foo": {"__symbolic": "class"}}}`,
'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}}, 'router': {'index.d.ts': dummyModule, 'src': {'providers.d.ts': dummyModule}},
'unused.d.ts': dummyModule, 'unused.d.ts': dummyModule,
'empty.d.ts': 'export declare var a: string;', 'empty.d.ts': 'export declare var a: string;',
@ -225,6 +239,9 @@ const FILES: Entry = {
`, `,
'v1.metadata.json': 'v1.metadata.json':
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`, `{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
'v1_empty.d.ts': `
export * from './lib/utils';
`
} }
} }
} }

View File

@ -29,13 +29,17 @@ describe('compiler-cli', () => {
"types": [], "types": [],
"outDir": "built", "outDir": "built",
"declaration": true, "declaration": true,
"module": "es2015" "module": "es2015",
"moduleResolution": "node"
}, },
"angularCompilerOptions": { "angularCompilerOptions": {
"annotateForClosureCompiler": true "annotateForClosureCompiler": true
}, },
"files": ["test.ts"] "files": ["test.ts"]
}`); }`);
const nodeModulesPath = path.resolve(basePath, 'node_modules');
fs.mkdirSync(nodeModulesPath);
fs.symlinkSync(path.resolve(__dirname, '..', '..'), path.resolve(nodeModulesPath, '@angular'));
}); });
// Restore reflector since AoT compiler will update it with a new static reflector // Restore reflector since AoT compiler will update it with a new static reflector

View File

@ -32,7 +32,9 @@ export * from './src/aot/compiler_host';
export * from './src/aot/static_reflector'; export * from './src/aot/static_reflector';
export * from './src/aot/static_reflection_capabilities'; export * from './src/aot/static_reflection_capabilities';
export * from './src/aot/static_symbol'; export * from './src/aot/static_symbol';
export * from './src/aot/static_symbol_resolver';
export * from './src/aot/summary_resolver'; export * from './src/aot/summary_resolver';
export * from './src/summary_resolver';
export {JitCompiler} from './src/jit/compiler'; export {JitCompiler} from './src/jit/compiler';
export * from './src/jit/compiler_factory'; export * from './src/jit/compiler_factory';
export * from './src/url_resolver'; export * from './src/url_resolver';
@ -60,4 +62,5 @@ export * from './src/style_compiler';
export * from './src/template_parser/template_parser'; export * from './src/template_parser/template_parser';
export {ViewCompiler} from './src/view_compiler/view_compiler'; export {ViewCompiler} from './src/view_compiler/view_compiler';
export {AnimationParser} from './src/animation/animation_parser'; export {AnimationParser} from './src/animation/animation_parser';
export {SyntaxError} from './src/util';
// This file only reexports content of the `src` folder. Keep it that way. // This file only reexports content of the `src` folder. Keep it that way.

View File

@ -6,11 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Injectable} from '@angular/core';
import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata, identifierName} from '../compile_metadata'; import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata, identifierName} from '../compile_metadata';
import {StringMapWrapper} from '../facade/collection'; import {StringMapWrapper} from '../facade/collection';
import {isBlank, isPresent} from '../facade/lang'; import {isBlank, isPresent} from '../facade/lang';
import {CompilerInjectable} from '../injectable';
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';
import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {ElementSchemaRegistry} from '../schema/element_schema_registry';
@ -35,7 +34,7 @@ export class AnimationEntryParseResult {
constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {} constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {}
} }
@Injectable() @CompilerInjectable()
export class AnimationParser { export class AnimationParser {
constructor(private _schema: ElementSchemaRegistry) {} constructor(private _schema: ElementSchemaRegistry) {}

View File

@ -20,34 +20,34 @@ import {NgModuleCompiler} from '../ng_module_compiler';
import {OutputEmitter} from '../output/abstract_emitter'; import {OutputEmitter} from '../output/abstract_emitter';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {CompiledStylesheet, StyleCompiler} from '../style_compiler'; import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {SummaryResolver} from '../summary_resolver';
import {TemplateParser} from '../template_parser/template_parser'; import {TemplateParser} from '../template_parser/template_parser';
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler'; import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
import {AotCompilerOptions} from './compiler_options'; import {AotCompilerHost} from './compiler_host';
import {GeneratedFile} from './generated_file'; import {GeneratedFile} from './generated_file';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol'; import {StaticSymbol} from './static_symbol';
import {AotSummaryResolver} from './summary_resolver'; import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
import {filterFileByPatterns} from './utils'; import {serializeSummaries, summaryFileName} from './summary_serializer';
export class AotCompiler { export class AotCompiler {
private _animationCompiler = new AnimationCompiler(); private _animationCompiler = new AnimationCompiler();
constructor( constructor(
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser, private _host: AotCompilerHost, private _metadataResolver: CompileMetadataResolver,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
private _dirWrapperCompiler: DirectiveWrapperCompiler, private _viewCompiler: ViewCompiler, private _dirWrapperCompiler: DirectiveWrapperCompiler,
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter, private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
private _summaryResolver: AotSummaryResolver, private _localeId: string, private _summaryResolver: SummaryResolver<StaticSymbol>, private _localeId: string,
private _translationFormat: string, private _animationParser: AnimationParser, private _translationFormat: string, private _animationParser: AnimationParser,
private _staticReflector: StaticReflector, private _options: AotCompilerOptions) {} private _symbolResolver: StaticSymbolResolver) {}
clearCache() { this._metadataResolver.clearCache(); } clearCache() { this._metadataResolver.clearCache(); }
compileAll(rootFiles: string[]): Promise<GeneratedFile[]> { compileAll(rootFiles: string[]): Promise<GeneratedFile[]> {
const programSymbols = extractProgramSymbols(this._staticReflector, rootFiles, this._options); const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host);
const {ngModuleByPipeOrDirective, files, ngModules} = const {ngModuleByPipeOrDirective, files, ngModules} =
analyzeAndValidateNgModules(programSymbols, this._options, this._metadataResolver); analyzeAndValidateNgModules(programSymbols, this._host, this._metadataResolver);
return Promise return Promise
.all(ngModules.map( .all(ngModules.map(
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata( ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
@ -56,27 +56,21 @@ export class AotCompiler {
const sourceModules = files.map( const sourceModules = files.map(
file => this._compileSrcFile( file => this._compileSrcFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes, file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes,
file.ngModules)); file.ngModules, file.injectables));
return ListWrapper.flatten(sourceModules); return ListWrapper.flatten(sourceModules);
}); });
} }
private _compileSrcFile( private _compileSrcFile(
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>, srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
directives: StaticSymbol[], pipes: StaticSymbol[], directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: StaticSymbol[],
ngModules: StaticSymbol[]): GeneratedFile[] { injectables: StaticSymbol[]): GeneratedFile[] {
const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1]; const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1];
const statements: o.Statement[] = []; const statements: o.Statement[] = [];
const exportedVars: string[] = []; const exportedVars: string[] = [];
const generatedFiles: GeneratedFile[] = []; const generatedFiles: GeneratedFile[] = [];
// write summary files generatedFiles.push(this._createSummary(srcFileUrl, directives, pipes, ngModules, injectables));
const summaries: CompileTypeSummary[] = [
...ngModules.map(ref => this._metadataResolver.getNgModuleSummary(ref)),
...directives.map(ref => this._metadataResolver.getDirectiveSummary(ref)),
...pipes.map(ref => this._metadataResolver.getPipeSummary(ref))
];
generatedFiles.push(this._summaryResolver.serializeSummaries(srcFileUrl, summaries));
// compile all ng modules // compile all ng modules
exportedVars.push( exportedVars.push(
@ -121,6 +115,22 @@ export class AotCompiler {
return generatedFiles; return generatedFiles;
} }
private _createSummary(
srcFileUrl: string, directives: StaticSymbol[], pipes: StaticSymbol[],
ngModules: StaticSymbol[], injectables: StaticSymbol[]): GeneratedFile {
const symbolSummaries = this._symbolResolver.getSymbolsOf(srcFileUrl)
.map(symbol => this._symbolResolver.resolveSymbol(symbol));
const typeSummaries = [
...ngModules.map(ref => this._metadataResolver.getNgModuleSummary(ref)),
...directives.map(ref => this._metadataResolver.getDirectiveSummary(ref)),
...pipes.map(ref => this._metadataResolver.getPipeSummary(ref)),
...injectables.map(ref => this._metadataResolver.getInjectableSummary(ref))
];
const json = serializeSummaries(
this._host, this._summaryResolver, this._symbolResolver, symbolSummaries, typeSummaries);
return new GeneratedFile(srcFileUrl, summaryFileName(srcFileUrl), json);
}
private _compileModule(ngModuleType: StaticSymbol, targetStatements: o.Statement[]): string { private _compileModule(ngModuleType: StaticSymbol, targetStatements: o.Statement[]): string {
const ngModule = this._metadataResolver.getNgModuleMetadata(ngModuleType); const ngModule = this._metadataResolver.getNgModuleMetadata(ngModuleType);
const providers: CompileProviderMetadata[] = []; const providers: CompileProviderMetadata[] = [];
@ -142,7 +152,7 @@ export class AotCompiler {
const appCompileResult = this._ngModuleCompiler.compile(ngModule, providers); const appCompileResult = this._ngModuleCompiler.compile(ngModule, providers);
appCompileResult.dependencies.forEach((dep) => { appCompileResult.dependencies.forEach((dep) => {
dep.placeholder.reference = this._staticReflector.getStaticSymbol( dep.placeholder.reference = this._symbolResolver.getStaticSymbol(
_ngfactoryModuleUrl(identifierModuleUrl(dep.comp)), _componentFactoryName(dep.comp)); _ngfactoryModuleUrl(identifierModuleUrl(dep.comp)), _componentFactoryName(dep.comp));
}); });
@ -163,7 +173,7 @@ export class AotCompiler {
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, fileSuffix: string, compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, fileSuffix: string,
targetStatements: o.Statement[]): string { targetStatements: o.Statement[]): string {
const hostMeta = createHostComponentMeta( const hostMeta = createHostComponentMeta(
this._staticReflector.getStaticSymbol( this._symbolResolver.getStaticSymbol(
identifierModuleUrl(compMeta.type), `${identifierName(compMeta.type)}_Host`), identifierModuleUrl(compMeta.type), `${identifierName(compMeta.type)}_Host`),
compMeta); compMeta);
const hostViewFactoryVar = this._compileComponent( const hostViewFactoryVar = this._compileComponent(
@ -206,16 +216,16 @@ export class AotCompiler {
compMeta, parsedTemplate, stylesExpr, pipes, compiledAnimations); compMeta, parsedTemplate, stylesExpr, pipes, compiledAnimations);
if (componentStyles) { if (componentStyles) {
targetStatements.push( targetStatements.push(
..._resolveStyleStatements(this._staticReflector, componentStyles, fileSuffix)); ..._resolveStyleStatements(this._symbolResolver, componentStyles, fileSuffix));
} }
compiledAnimations.forEach(entry => targetStatements.push(...entry.statements)); compiledAnimations.forEach(entry => targetStatements.push(...entry.statements));
targetStatements.push(..._resolveViewStatements(this._staticReflector, viewResult)); targetStatements.push(..._resolveViewStatements(this._symbolResolver, viewResult));
return viewResult.viewClassVar; return viewResult.viewClassVar;
} }
private _codgenStyles( private _codgenStyles(
fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): GeneratedFile { fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): GeneratedFile {
_resolveStyleStatements(this._staticReflector, stylesCompileResult, fileSuffix); _resolveStyleStatements(this._symbolResolver, stylesCompileResult, fileSuffix);
return this._codegenSourceModule( return this._codegenSourceModule(
fileUrl, _stylesModuleUrl( fileUrl, _stylesModuleUrl(
stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix), stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix),
@ -232,7 +242,7 @@ export class AotCompiler {
} }
function _resolveViewStatements( function _resolveViewStatements(
reflector: StaticReflector, compileResult: ViewCompileResult): o.Statement[] { reflector: StaticSymbolResolver, compileResult: ViewCompileResult): o.Statement[] {
compileResult.dependencies.forEach((dep) => { compileResult.dependencies.forEach((dep) => {
if (dep instanceof ViewClassDependency) { if (dep instanceof ViewClassDependency) {
const vfd = <ViewClassDependency>dep; const vfd = <ViewClassDependency>dep;
@ -253,7 +263,7 @@ function _resolveViewStatements(
function _resolveStyleStatements( function _resolveStyleStatements(
reflector: StaticReflector, compileResult: CompiledStylesheet, reflector: StaticSymbolResolver, compileResult: CompiledStylesheet,
fileSuffix: string): o.Statement[] { fileSuffix: string): o.Statement[] {
compileResult.dependencies.forEach((dep) => { compileResult.dependencies.forEach((dep) => {
dep.valuePlaceholder.reference = reflector.getStaticSymbol( dep.valuePlaceholder.reference = reflector.getStaticSymbol(
@ -303,26 +313,27 @@ export interface NgAnalyzedModules {
srcUrl: string, srcUrl: string,
directives: StaticSymbol[], directives: StaticSymbol[],
pipes: StaticSymbol[], pipes: StaticSymbol[],
ngModules: StaticSymbol[] ngModules: StaticSymbol[],
injectables: StaticSymbol[]
}>; }>;
symbolsMissingModule?: StaticSymbol[]; symbolsMissingModule?: StaticSymbol[];
} }
export interface NgAnalyzeModulesHost { isSourceFile(filePath: string): boolean; }
// Returns all the source files and a mapping from modules to directives // Returns all the source files and a mapping from modules to directives
export function analyzeNgModules( export function analyzeNgModules(
programStaticSymbols: StaticSymbol[], programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
metadataResolver: CompileMetadataResolver): NgAnalyzedModules { metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const {ngModules, symbolsMissingModule} = const {ngModules, symbolsMissingModule} =
_createNgModules(programStaticSymbols, options, metadataResolver); _createNgModules(programStaticSymbols, host, metadataResolver);
return _analyzeNgModules(ngModules, symbolsMissingModule); return _analyzeNgModules(programStaticSymbols, ngModules, symbolsMissingModule, metadataResolver);
} }
export function analyzeAndValidateNgModules( export function analyzeAndValidateNgModules(
programStaticSymbols: StaticSymbol[], programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
metadataResolver: CompileMetadataResolver): NgAnalyzedModules { metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const result = analyzeNgModules(programStaticSymbols, options, metadataResolver); const result = analyzeNgModules(programStaticSymbols, host, metadataResolver);
if (result.symbolsMissingModule && result.symbolsMissingModule.length) { if (result.symbolsMissingModule && result.symbolsMissingModule.length) {
const messages = result.symbolsMissingModule.map( const messages = result.symbolsMissingModule.map(
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`); s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
@ -332,16 +343,27 @@ export function analyzeAndValidateNgModules(
} }
function _analyzeNgModules( function _analyzeNgModules(
ngModuleMetas: CompileNgModuleMetadata[], programSymbols: StaticSymbol[], ngModuleMetas: CompileNgModuleMetadata[],
symbolsMissingModule: StaticSymbol[]): NgAnalyzedModules { symbolsMissingModule: StaticSymbol[],
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>(); const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule)); ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule));
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>(); const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
const ngModulesByFile = new Map<string, StaticSymbol[]>(); const ngModulesByFile = new Map<string, StaticSymbol[]>();
const ngDirectivesByFile = new Map<string, StaticSymbol[]>(); const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
const ngPipesByFile = new Map<string, StaticSymbol[]>(); const ngPipesByFile = new Map<string, StaticSymbol[]>();
const ngInjectablesByFile = new Map<string, StaticSymbol[]>();
const filePaths = new Set<string>(); const filePaths = new Set<string>();
// Make sure we produce an analyzed file for each input file
programSymbols.forEach((symbol) => {
const filePath = symbol.filePath;
filePaths.add(filePath);
if (metadataResolver.isInjectable(symbol)) {
ngInjectablesByFile.set(filePath, (ngInjectablesByFile.get(filePath) || []).concat(symbol));
}
});
// Looping over all modules to construct: // Looping over all modules to construct:
// - a map from file to modules `ngModulesByFile`, // - a map from file to modules `ngModulesByFile`,
// - a map from file to directives `ngDirectivesByFile`, // - a map from file to directives `ngDirectivesByFile`,
@ -369,17 +391,20 @@ function _analyzeNgModules(
}); });
}); });
const files: const files: {
{srcUrl: string, srcUrl: string,
directives: StaticSymbol[], directives: StaticSymbol[],
pipes: StaticSymbol[], pipes: StaticSymbol[],
ngModules: StaticSymbol[]}[] = []; ngModules: StaticSymbol[],
injectables: StaticSymbol[]
}[] = [];
filePaths.forEach((srcUrl) => { filePaths.forEach((srcUrl) => {
const directives = ngDirectivesByFile.get(srcUrl) || []; const directives = ngDirectivesByFile.get(srcUrl) || [];
const pipes = ngPipesByFile.get(srcUrl) || []; const pipes = ngPipesByFile.get(srcUrl) || [];
const ngModules = ngModulesByFile.get(srcUrl) || []; const ngModules = ngModulesByFile.get(srcUrl) || [];
files.push({srcUrl, directives, pipes, ngModules}); const injectables = ngInjectablesByFile.get(srcUrl) || [];
files.push({srcUrl, directives, pipes, ngModules, injectables});
}); });
return { return {
@ -392,30 +417,21 @@ function _analyzeNgModules(
} }
export function extractProgramSymbols( export function extractProgramSymbols(
staticReflector: StaticReflector, files: string[], staticSymbolResolver: StaticSymbolResolver, files: string[],
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}): StaticSymbol[] { host: NgAnalyzeModulesHost): StaticSymbol[] {
const staticSymbols: StaticSymbol[] = []; const staticSymbols: StaticSymbol[] = [];
files.filter(fileName => filterFileByPatterns(fileName, options)).forEach(sourceFile => { files.filter(fileName => host.isSourceFile(fileName)).forEach(sourceFile => {
const moduleMetadata = staticReflector.getModuleMetadata(sourceFile); staticSymbolResolver.getSymbolsOf(sourceFile).forEach((symbol) => {
if (!moduleMetadata) { const resolvedSymbol = staticSymbolResolver.resolveSymbol(symbol);
console.error(`WARNING: no metadata found for ${sourceFile}`); const symbolMeta = resolvedSymbol.metadata;
return; if (symbolMeta) {
} if (symbolMeta.__symbolic != 'error') {
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. // Ignore symbols that are only included to record error information.
continue; staticSymbols.push(resolvedSymbol.symbol);
} }
staticSymbols.push(staticReflector.getStaticSymbol(sourceFile, symbol));
} }
}); });
});
return staticSymbols; return staticSymbols;
} }
@ -424,8 +440,7 @@ export function extractProgramSymbols(
// that all directives / pipes that are present in the program // that all directives / pipes that are present in the program
// are also declared by a module. // are also declared by a module.
function _createNgModules( function _createNgModules(
programStaticSymbols: StaticSymbol[], programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
metadataResolver: CompileMetadataResolver): metadataResolver: CompileMetadataResolver):
{ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} { {ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} {
const ngModules = new Map<any, CompileNgModuleMetadata>(); const ngModules = new Map<any, CompileNgModuleMetadata>();
@ -433,7 +448,7 @@ function _createNgModules(
const ngModulePipesAndDirective = new Set<StaticSymbol>(); const ngModulePipesAndDirective = new Set<StaticSymbol>();
const addNgModule = (staticSymbol: any) => { const addNgModule = (staticSymbol: any) => {
if (ngModules.has(staticSymbol) || !filterFileByPatterns(staticSymbol.filePath, options)) { if (ngModules.has(staticSymbol) || !host.isSourceFile(staticSymbol.filePath)) {
return false; return false;
} }
const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false); const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false);

View File

@ -34,8 +34,11 @@ import {AotCompilerHost} from './compiler_host';
import {AotCompilerOptions} from './compiler_options'; import {AotCompilerOptions} from './compiler_options';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities'; import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector} from './static_reflector'; import {StaticReflector} from './static_reflector';
import {StaticSymbolCache} from './static_symbol';
import {StaticSymbolResolver} from './static_symbol_resolver';
import {AotSummaryResolver} from './summary_resolver'; import {AotSummaryResolver} from './summary_resolver';
/** /**
* Creates a new AotCompiler based on options and a host. * Creates a new AotCompiler based on options and a host.
*/ */
@ -44,7 +47,10 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
let translations: string = options.translations || ''; let translations: string = options.translations || '';
const urlResolver = createOfflineCompileUrlResolver(); const urlResolver = createOfflineCompileUrlResolver();
const staticReflector = new StaticReflector(compilerHost); const symbolCache = new StaticSymbolCache();
const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache);
const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver);
const staticReflector = new StaticReflector(symbolResolver);
StaticAndDynamicReflectionCapabilities.install(staticReflector); StaticAndDynamicReflectionCapabilities.install(staticReflector);
const htmlParser = new I18NHtmlParser(new HtmlParser(), translations, options.i18nFormat); const htmlParser = new I18NHtmlParser(new HtmlParser(), translations, options.i18nFormat);
const config = new CompilerConfig({ const config = new CompilerConfig({
@ -60,17 +66,16 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
const console = new Console(); const console = new Console();
const tmplParser = const tmplParser =
new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []); new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []);
const summaryResolver = new AotSummaryResolver(compilerHost, staticReflector, options);
const resolver = new CompileMetadataResolver( const resolver = new CompileMetadataResolver(
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector), new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer, new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer,
staticReflector); staticReflector);
// TODO(vicb): do not pass options.i18nFormat here // TODO(vicb): do not pass options.i18nFormat here
const compiler = new AotCompiler( const compiler = new AotCompiler(
resolver, tmplParser, new StyleCompiler(urlResolver), compilerHost, resolver, tmplParser, new StyleCompiler(urlResolver),
new ViewCompiler(config, elementSchemaRegistry), new ViewCompiler(config, elementSchemaRegistry),
new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console), new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console),
new NgModuleCompiler(), new TypeScriptEmitter(compilerHost), summaryResolver, options.locale, new NgModuleCompiler(), new TypeScriptEmitter(compilerHost), summaryResolver, options.locale,
options.i18nFormat, new AnimationParser(elementSchemaRegistry), staticReflector, options); options.i18nFormat, new AnimationParser(elementSchemaRegistry), symbolResolver);
return {compiler, reflector: staticReflector}; return {compiler, reflector: staticReflector};
} }

View File

@ -8,16 +8,17 @@
import {ImportResolver} from '../output/path_util'; import {ImportResolver} from '../output/path_util';
import {StaticReflectorHost} from './static_reflector';
import {StaticSymbol} from './static_symbol'; import {StaticSymbol} from './static_symbol';
import {StaticSymbolResolverHost} from './static_symbol_resolver';
import {AotSummaryResolverHost} from './summary_resolver'; import {AotSummaryResolverHost} from './summary_resolver';
import {AotSummarySerializerHost} from './summary_serializer';
/** /**
* The host of the AotCompiler disconnects the implementation from TypeScript / other language * The host of the AotCompiler disconnects the implementation from TypeScript / other language
* services and from underlying file systems. * services and from underlying file systems.
*/ */
export interface AotCompilerHost extends StaticReflectorHost, ImportResolver, export interface AotCompilerHost extends StaticSymbolResolverHost, ImportResolver,
AotSummaryResolverHost { AotSummaryResolverHost, AotSummarySerializerHost {
/** /**
* Loads a resource (e.g. html / css) * Loads a resource (e.g. html / css)
*/ */

View File

@ -11,6 +11,4 @@ export interface AotCompilerOptions {
locale?: string; locale?: string;
i18nFormat?: string; i18nFormat?: string;
translations?: string; translations?: string;
includeFilePattern?: RegExp;
excludeFilePattern?: RegExp;
} }

View File

@ -8,6 +8,7 @@
import {GetterFn, MethodFn, ReflectionCapabilities, SetterFn, reflector} from '../private_import_core'; import {GetterFn, MethodFn, ReflectionCapabilities, SetterFn, reflector} from '../private_import_core';
import {StaticReflector} from './static_reflector'; import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
export class StaticAndDynamicReflectionCapabilities { export class StaticAndDynamicReflectionCapabilities {
static install(staticDelegate: StaticReflector) { static install(staticDelegate: StaticReflector) {
@ -42,7 +43,7 @@ export class StaticAndDynamicReflectionCapabilities {
method(name: string): MethodFn { return this.dynamicDelegate.method(name); } method(name: string): MethodFn { return this.dynamicDelegate.method(name); }
importUri(type: any): string { return this.staticDelegate.importUri(type); } importUri(type: any): string { return this.staticDelegate.importUri(type); }
resolveIdentifier(name: string, moduleUrl: string, runtime: any) { resolveIdentifier(name: string, moduleUrl: string, runtime: any) {
return this.staticDelegate.resolveIdentifier(name, moduleUrl, runtime); return this.staticDelegate.resolveIdentifier(name, moduleUrl);
} }
resolveEnum(enumIdentifier: any, name: string): any { resolveEnum(enumIdentifier: any, name: string): any {
if (isStaticType(enumIdentifier)) { if (isStaticType(enumIdentifier)) {

View File

@ -7,10 +7,12 @@
*/ */
import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {ReflectorReader} from '../private_import_core';
import {StaticSymbol} from './static_symbol';
const SUPPORTED_SCHEMA_VERSION = 3; import {ReflectorReader} from '../private_import_core';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
const ANGULAR_IMPORT_LOCATIONS = { const ANGULAR_IMPORT_LOCATIONS = {
coreDecorators: '@angular/core/src/metadata', coreDecorators: '@angular/core/src/metadata',
diDecorators: '@angular/core/src/di/metadata', diDecorators: '@angular/core/src/di/metadata',
@ -22,66 +24,20 @@ const ANGULAR_IMPORT_LOCATIONS = {
const HIDDEN_KEY = /^\$.*\$$/; const HIDDEN_KEY = /^\$.*\$$/;
/**
* The host of the StaticReflector disconnects the implementation from TypeScript / other language
* services and from underlying file systems.
*/
export interface StaticReflectorHost {
/**
* Return a ModuleMetadata for the given module.
* Angular 2 CLI will produce this metadata for a module whenever a .d.ts files is
* produced and the module has exported variables or classes with decorators. Module metadata can
* also be produced directly from TypeScript sources by using MetadataCollector in tools/metadata.
*
* @param modulePath is a string identifier for a module as an absolute path.
* @returns the metadata for the given module.
*/
getMetadataFor(modulePath: string): {[key: string]: any}[];
/**
* Converts a module name that is used in an `import` to a file path.
* I.e.
* `path/to/containingFile.ts` containing `import {...} from 'module-name'`.
*/
moduleNameToFileName(moduleName: string, containingFile: string): string;
}
/**
* A cache of static symbol used by the StaticReflector to return the same symbol for the
* same symbol values.
*/
export class StaticSymbolCache {
private cache = new Map<string, StaticSymbol>();
get(declarationFile: string, name: string, members?: string[]): StaticSymbol {
const memberSuffix = members ? `.${ members.join('.')}` : '';
const key = `"${declarationFile}".${name}${memberSuffix}`;
let result = this.cache.get(key);
if (!result) {
result = new StaticSymbol(declarationFile, name, members);
this.cache.set(key, result);
}
return result;
}
}
/** /**
* A static reflector implements enough of the Reflector API that is necessary to compile * A static reflector implements enough of the Reflector API that is necessary to compile
* templates statically. * templates statically.
*/ */
export class StaticReflector implements ReflectorReader { export class StaticReflector implements ReflectorReader {
private declarationCache = new Map<string, StaticSymbol>();
private annotationCache = new Map<StaticSymbol, any[]>(); private annotationCache = new Map<StaticSymbol, any[]>();
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>(); private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
private parameterCache = new Map<StaticSymbol, any[]>(); private parameterCache = new Map<StaticSymbol, any[]>();
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>(); private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
private metadataCache = new Map<string, {[key: string]: any}>();
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>(); private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
private opaqueToken: StaticSymbol; private opaqueToken: StaticSymbol;
constructor( constructor(
private host: StaticReflectorHost, private symbolResolver: StaticSymbolResolver,
private staticSymbolCache: StaticSymbolCache = new StaticSymbolCache(),
knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [], knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [],
knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = [], knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = [],
private errorRecorder?: (error: any, fileName: string) => void) { private errorRecorder?: (error: any, fileName: string) => void) {
@ -94,12 +50,26 @@ export class StaticReflector implements ReflectorReader {
} }
importUri(typeOrFunc: StaticSymbol): string { importUri(typeOrFunc: StaticSymbol): string {
const staticSymbol = this.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, ''); const staticSymbol = this.findSymbolDeclaration(typeOrFunc);
return staticSymbol ? staticSymbol.filePath : null; return staticSymbol ? staticSymbol.filePath : null;
} }
resolveIdentifier(name: string, moduleUrl: string, runtime: any): any { resolveIdentifier(name: string, moduleUrl: string): StaticSymbol {
return this.findDeclaration(moduleUrl, name, ''); return this.findDeclaration(moduleUrl, name);
}
findDeclaration(moduleUrl: string, name: string, containingFile?: string): StaticSymbol {
return this.findSymbolDeclaration(
this.symbolResolver.getSymbolByModule(moduleUrl, name, containingFile));
}
findSymbolDeclaration(symbol: StaticSymbol): StaticSymbol {
const resolvedSymbol = this.symbolResolver.resolveSymbol(symbol);
if (resolvedSymbol && resolvedSymbol.metadata instanceof StaticSymbol) {
return this.findSymbolDeclaration(resolvedSymbol.metadata);
} else {
return symbol;
}
} }
resolveEnum(enumIdentifier: any, name: string): any { resolveEnum(enumIdentifier: any, name: string): any {
@ -128,7 +98,7 @@ export class StaticReflector implements ReflectorReader {
public propMetadata(type: StaticSymbol): {[key: string]: any[]} { public propMetadata(type: StaticSymbol): {[key: string]: any[]} {
let propMetadata = this.propertyCache.get(type); let propMetadata = this.propertyCache.get(type);
if (!propMetadata) { if (!propMetadata) {
const classMetadata = this.getTypeMetadata(type) || {}; const classMetadata = this.getTypeMetadata(type);
propMetadata = {}; propMetadata = {};
if (classMetadata['extends']) { if (classMetadata['extends']) {
const parentPropMetadata = this.propMetadata(this.simplify(type, classMetadata['extends'])); const parentPropMetadata = this.propMetadata(this.simplify(type, classMetadata['extends']));
@ -203,7 +173,7 @@ export class StaticReflector implements ReflectorReader {
private _methodNames(type: any): {[key: string]: boolean} { private _methodNames(type: any): {[key: string]: boolean} {
let methodNames = this.methodCache.get(type); let methodNames = this.methodCache.get(type);
if (!methodNames) { if (!methodNames) {
const classMetadata = this.getTypeMetadata(type) || {}; const classMetadata = this.getTypeMetadata(type);
methodNames = {}; methodNames = {};
if (classMetadata['extends']) { if (classMetadata['extends']) {
const parentMethodNames = this._methodNames(this.simplify(type, classMetadata['extends'])); const parentMethodNames = this._methodNames(this.simplify(type, classMetadata['extends']));
@ -306,7 +276,7 @@ export class StaticReflector implements ReflectorReader {
* @param name the name of the type. * @param name the name of the type.
*/ */
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol { getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
return this.staticSymbolCache.get(declarationFile, name, members); return this.symbolResolver.getStaticSymbol(declarationFile, name, members);
} }
private reportError(error: Error, context: StaticSymbol, path?: string) { private reportError(error: Error, context: StaticSymbol, path?: string) {
@ -317,96 +287,6 @@ export class StaticReflector implements ReflectorReader {
} }
} }
private resolveExportedSymbol(filePath: string, symbolName: string): StaticSymbol {
const resolveModule = (moduleName: string): string => {
const resolvedModulePath = this.host.moduleNameToFileName(moduleName, filePath);
if (!resolvedModulePath) {
this.reportError(
new Error(`Could not resolve module '${moduleName}' relative to file ${filePath}`),
null, filePath);
}
return resolvedModulePath;
};
const cacheKey = `${filePath}|${symbolName}`;
let staticSymbol = this.declarationCache.get(cacheKey);
if (staticSymbol) {
return staticSymbol;
}
const metadata = this.getModuleMetadata(filePath);
if (metadata) {
// If we have metadata for the symbol, this is the original exporting location.
if (metadata['metadata'][symbolName]) {
staticSymbol = this.getStaticSymbol(filePath, symbolName);
}
// If no, try to find the symbol in one of the re-export location
if (!staticSymbol && metadata['exports']) {
// Try and find the symbol in the list of explicitly re-exported symbols.
for (const moduleExport of metadata['exports']) {
if (moduleExport.export) {
const exportSymbol = moduleExport.export.find((symbol: any) => {
if (typeof symbol === 'string') {
return symbol == symbolName;
} else {
return symbol.as == symbolName;
}
});
if (exportSymbol) {
let symName = symbolName;
if (typeof exportSymbol !== 'string') {
symName = exportSymbol.name;
}
const resolvedModule = resolveModule(moduleExport.from);
if (resolvedModule) {
staticSymbol =
this.resolveExportedSymbol(resolveModule(moduleExport.from), symName);
break;
}
}
}
}
if (!staticSymbol) {
// Try to find the symbol via export * directives.
for (const moduleExport of metadata['exports']) {
if (!moduleExport.export) {
const resolvedModule = resolveModule(moduleExport.from);
if (resolvedModule) {
const candidateSymbol = this.resolveExportedSymbol(resolvedModule, symbolName);
if (candidateSymbol) {
staticSymbol = candidateSymbol;
break;
}
}
}
}
}
}
}
this.declarationCache.set(cacheKey, staticSymbol);
return staticSymbol;
}
findDeclaration(module: string, symbolName: string, containingFile?: string): StaticSymbol {
try {
const filePath = this.host.moduleNameToFileName(module, containingFile);
let symbol: StaticSymbol;
if (!filePath) {
// If the file cannot be found the module is probably referencing a declared module
// for which there is no disambiguating file and we also don't need to track
// re-exports. Just use the module name.
symbol = this.getStaticSymbol(module, symbolName);
} else {
symbol = this.resolveExportedSymbol(filePath, symbolName) ||
this.getStaticSymbol(filePath, symbolName);
}
return symbol;
} catch (e) {
console.error(`can't resolve module ${module} from ${containingFile}`);
throw e;
}
}
/** @internal */ /** @internal */
public simplify(context: StaticSymbol, value: any): any { public simplify(context: StaticSymbol, value: any): any {
const self = this; const self = this;
@ -414,60 +294,12 @@ export class StaticReflector implements ReflectorReader {
const calling = new Map<StaticSymbol, boolean>(); const 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 {
let staticSymbol: StaticSymbol;
if (expression['module']) {
staticSymbol =
self.findDeclaration(expression['module'], expression['name'], context.filePath);
} else {
staticSymbol = self.getStaticSymbol(context.filePath, expression['name']);
}
return staticSymbol;
}
function resolveReferenceValue(staticSymbol: StaticSymbol): any { function resolveReferenceValue(staticSymbol: StaticSymbol): any {
const moduleMetadata = self.getModuleMetadata(staticSymbol.filePath); const resolvedSymbol = self.symbolResolver.resolveSymbol(staticSymbol);
const declarationValue = return resolvedSymbol ? resolvedSymbol.metadata : null;
moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null;
return declarationValue;
} }
function isOpaqueToken(context: StaticSymbol, value: any): boolean { function simplifyCall(functionSymbol: StaticSymbol, targetFunction: any, args: any[]) {
if (value && value.__symbolic === 'new' && value.expression) {
const target = value.expression;
if (target.__symbolic == 'reference') {
return sameSymbol(resolveReference(context, target), self.opaqueToken);
}
}
return false;
}
function simplifyCall(expression: any) {
let callContext: {[name: string]: string}|undefined = undefined;
if (expression['__symbolic'] == 'call') {
const target = expression['expression'];
let functionSymbol: StaticSymbol;
let targetFunction: any;
if (target) {
switch (target.__symbolic) {
case 'reference':
// Find the function to call.
callContext = {name: target.name};
functionSymbol = resolveReference(context, target);
targetFunction = resolveReferenceValue(functionSymbol);
break;
case 'select':
// Find the static method to call
if (target.expression.__symbolic == 'reference') {
functionSymbol = resolveReference(context, target.expression);
const classData = resolveReferenceValue(functionSymbol);
if (classData && classData.statics) {
targetFunction = classData.statics[target.member];
}
}
break;
}
}
if (targetFunction && targetFunction['__symbolic'] == 'function') { if (targetFunction && targetFunction['__symbolic'] == 'function') {
if (calling.get(functionSymbol)) { if (calling.get(functionSymbol)) {
throw new Error('Recursion not supported'); throw new Error('Recursion not supported');
@ -476,9 +308,6 @@ export class StaticReflector implements ReflectorReader {
try { try {
const value = targetFunction['value']; const value = targetFunction['value'];
if (value && (depth != 0 || value.__symbolic != 'error')) { if (value && (depth != 0 || value.__symbolic != 'error')) {
// Determine the arguments
const args: any[] =
(expression['arguments'] || []).map((arg: any) => simplify(arg));
const parameters: string[] = targetFunction['parameters']; const parameters: string[] = targetFunction['parameters'];
const defaults: any[] = targetFunction.defaults; const defaults: any[] = targetFunction.defaults;
if (defaults && defaults.length > args.length) { if (defaults && defaults.length > args.length) {
@ -502,7 +331,6 @@ export class StaticReflector implements ReflectorReader {
calling.delete(functionSymbol); calling.delete(functionSymbol);
} }
} }
}
if (depth === 0) { if (depth === 0) {
// If depth is 0 we are evaluating the top level expression that is describing element // If depth is 0 we are evaluating the top level expression that is describing element
@ -511,7 +339,7 @@ export class StaticReflector implements ReflectorReader {
return {__symbolic: 'ignore'}; return {__symbolic: 'ignore'};
} }
return simplify( return simplify(
{__symbolic: 'error', message: 'Function call not supported', context: callContext}); {__symbolic: 'error', message: 'Function call not supported', context: functionSymbol});
} }
function simplify(expression: any): any { function simplify(expression: any): any {
@ -540,7 +368,18 @@ export class StaticReflector implements ReflectorReader {
return result; return result;
} }
if (expression instanceof StaticSymbol) { if (expression instanceof StaticSymbol) {
// Stop simplification at builtin symbols
if (expression === self.opaqueToken || self.conversionMap.has(expression)) {
return expression; return expression;
} else {
const staticSymbol = expression;
const declarationValue = resolveReferenceValue(staticSymbol);
if (declarationValue) {
return simplifyInContext(staticSymbol, declarationValue, depth + 1);
} else {
return staticSymbol;
}
}
} }
if (expression) { if (expression) {
if (expression['__symbolic']) { if (expression['__symbolic']) {
@ -618,50 +457,33 @@ export class StaticReflector implements ReflectorReader {
if (indexTarget && isPrimitive(index)) return indexTarget[index]; if (indexTarget && isPrimitive(index)) return indexTarget[index];
return null; return null;
case 'select': case 'select':
const member = expression['member'];
let selectContext = context; let selectContext = context;
let selectTarget = simplify(expression['expression']); let selectTarget = simplify(expression['expression']);
if (selectTarget instanceof StaticSymbol) { if (selectTarget instanceof StaticSymbol) {
// Access to a static instance variable const members = selectTarget.members.concat(member);
const member: string = expression['member'];
const members = selectTarget.members ?
(selectTarget.members as string[]).concat(member) :
[member];
const declarationValue = resolveReferenceValue(selectTarget);
selectContext = selectContext =
self.getStaticSymbol(selectTarget.filePath, selectTarget.name, members); self.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
if (declarationValue && declarationValue.statics) { const declarationValue = resolveReferenceValue(selectContext);
selectTarget = declarationValue.statics; if (declarationValue) {
return simplifyInContext(selectContext, declarationValue, depth + 1);
} else { } else {
return selectContext; return selectContext;
} }
} }
const member = simplifyInContext(selectContext, expression['member'], depth + 1);
if (selectTarget && isPrimitive(member)) if (selectTarget && isPrimitive(member))
return simplifyInContext(selectContext, selectTarget[member], depth + 1); return simplifyInContext(selectContext, selectTarget[member], depth + 1);
return null; return null;
case 'reference': case 'reference':
if (!expression['name']) { // Note: This only has to deal with variable references,
return context; // as symbol references have been converted into StaticSymbols already
} // in the StaticSymbolResolver!
if (!expression.module) {
const name: string = expression['name']; const name: string = expression['name'];
const localValue = scope.resolve(name); const localValue = scope.resolve(name);
if (localValue != BindingScope.missing) { if (localValue != BindingScope.missing) {
return localValue; return localValue;
} }
} break;
staticSymbol = resolveReference(context, expression);
let result: any = staticSymbol;
let declarationValue = resolveReferenceValue(result);
if (declarationValue) {
if (isOpaqueToken(staticSymbol, declarationValue)) {
// If the referenced symbol is initalized by a new OpaqueToken we can keep the
// reference to the symbol.
return staticSymbol;
}
result = simplifyInContext(staticSymbol, declarationValue, depth + 1);
}
return result;
case 'class': case 'class':
return context; return context;
case 'function': case 'function':
@ -669,26 +491,26 @@ export class StaticReflector implements ReflectorReader {
case 'new': case 'new':
case 'call': case 'call':
// Determine if the function is a built-in conversion // Determine if the function is a built-in conversion
let target = expression['expression']; staticSymbol = simplifyInContext(context, expression['expression'], depth + 1);
if (target['module']) { if (staticSymbol instanceof StaticSymbol) {
staticSymbol = if (staticSymbol === self.opaqueToken) {
self.findDeclaration(target['module'], target['name'], context.filePath); // if somebody calls new OpaqueToken, don't create an OpaqueToken,
} else { // but rather return the symbol to which the OpaqueToken is assigned to.
staticSymbol = self.getStaticSymbol(context.filePath, target['name']); return context;
} }
const argExpressions: any[] = expression['arguments'] || [];
const args =
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
let converter = self.conversionMap.get(staticSymbol); let converter = self.conversionMap.get(staticSymbol);
if (converter) { if (converter) {
let args: any[] = expression['arguments']; return converter(context, args);
if (!args) { } else {
args = [];
}
return converter(
context, args.map(arg => simplifyInContext(context, arg, depth + 1)));
}
// Determine if the function is one we can simplify. // Determine if the function is one we can simplify.
return simplifyCall(expression); const targetFunction = resolveReferenceValue(staticSymbol);
return simplifyCall(staticSymbol, targetFunction, args);
}
}
break;
case 'error': case 'error':
let message = produceErrorMessage(expression); let message = produceErrorMessage(expression);
if (expression['line']) { if (expression['line']) {
@ -709,7 +531,9 @@ export class StaticReflector implements ReflectorReader {
try { try {
return simplify(value); return simplify(value);
} catch (e) { } catch (e) {
const message = `${e.message}, resolving symbol ${context.name} in ${context.filePath}`; const members = context.members.length ? `.${context.members.join('.')}` : '';
const message =
`${e.message}, resolving symbol ${context.name}${members} in ${context.filePath}`;
if (e.fileName) { if (e.fileName) {
throw positionalError(message, e.fileName, e.line, e.column); throw positionalError(message, e.fileName, e.line, e.column);
} }
@ -733,40 +557,10 @@ export class StaticReflector implements ReflectorReader {
return result; return result;
} }
/**
* @param module an absolute path to a module file.
*/
public getModuleMetadata(module: string): {[key: string]: any} {
let moduleMetadata = this.metadataCache.get(module);
if (!moduleMetadata) {
const moduleMetadatas = this.host.getMetadataFor(module);
if (moduleMetadatas) {
let maxVersion = -1;
moduleMetadatas.forEach((md) => {
if (md['version'] > maxVersion) {
maxVersion = md['version'];
moduleMetadata = md;
}
});
}
if (!moduleMetadata) {
moduleMetadata =
{__symbolic: 'module', version: SUPPORTED_SCHEMA_VERSION, module: module, metadata: {}};
}
if (moduleMetadata['version'] != SUPPORTED_SCHEMA_VERSION) {
const errorMessage = moduleMetadata['version'] == 2 ?
`Unsupported metadata version ${moduleMetadata['version']} for module ${module}. This module should be compiled with a newer version of ngc` :
`Metadata version mismatch for module ${module}, found version ${moduleMetadata['version']}, expected ${SUPPORTED_SCHEMA_VERSION}`;
this.reportError(new Error(errorMessage), null);
}
this.metadataCache.set(module, moduleMetadata);
}
return moduleMetadata;
}
private getTypeMetadata(type: StaticSymbol): {[key: string]: any} { private getTypeMetadata(type: StaticSymbol): {[key: string]: any} {
const moduleMetadata = this.getModuleMetadata(type.filePath); const resolvedSymbol = this.symbolResolver.resolveSymbol(type);
return moduleMetadata['metadata'][type.name] || {__symbolic: 'class'}; return resolvedSymbol && resolvedSymbol.metadata ? resolvedSymbol.metadata :
{__symbolic: 'class'};
} }
} }
@ -858,7 +652,7 @@ class PopulatedScope extends BindingScope {
} }
function sameSymbol(a: StaticSymbol, b: StaticSymbol): boolean { function sameSymbol(a: StaticSymbol, b: StaticSymbol): boolean {
return a === b || (a.name == b.name && a.filePath == b.filePath); return a === b;
} }
function shouldIgnore(value: any): boolean { function shouldIgnore(value: any): boolean {

View File

@ -14,3 +14,23 @@
export class StaticSymbol { export class StaticSymbol {
constructor(public filePath: string, public name: string, public members?: string[]) {} constructor(public filePath: string, public name: string, public members?: string[]) {}
} }
/**
* A cache of static symbol used by the StaticReflector to return the same symbol for the
* same symbol values.
*/
export class StaticSymbolCache {
private cache = new Map<string, StaticSymbol>();
get(declarationFile: string, name: string, members?: string[]): StaticSymbol {
members = members || [];
const memberSuffix = members.length ? `.${ members.join('.')}` : '';
const key = `"${declarationFile}".${name}${memberSuffix}`;
let result = this.cache.get(key);
if (!result) {
result = new StaticSymbol(declarationFile, name, members);
this.cache.set(key, result);
}
return result;
}
}

View File

@ -0,0 +1,289 @@
/**
* @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 {SummaryResolver} from '../summary_resolver';
import {ValueTransformer, visitValue} from '../util';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
export class ResolvedStaticSymbol {
constructor(public symbol: StaticSymbol, public metadata: any) {}
}
/**
* The host of the SymbolResolverHost disconnects the implementation from TypeScript / other
* language
* services and from underlying file systems.
*/
export interface StaticSymbolResolverHost {
/**
* Return a ModuleMetadata for the given module.
* Angular 2 CLI will produce this metadata for a module whenever a .d.ts files is
* produced and the module has exported variables or classes with decorators. Module metadata can
* also be produced directly from TypeScript sources by using MetadataCollector in tools/metadata.
*
* @param modulePath is a string identifier for a module as an absolute path.
* @returns the metadata for the given module.
*/
getMetadataFor(modulePath: string): {[key: string]: any}[];
/**
* Converts a module name that is used in an `import` to a file path.
* I.e.
* `path/to/containingFile.ts` containing `import {...} from 'module-name'`.
*/
moduleNameToFileName(moduleName: string, containingFile: string): string /*|null*/;
}
const SUPPORTED_SCHEMA_VERSION = 3;
/**
* This class is responsible for loading metadata per symbol,
* and normalizing references between symbols.
*/
export class StaticSymbolResolver {
private metadataCache = new Map<string, {[key: string]: any}>();
private resolvedSymbols = new Map<StaticSymbol, ResolvedStaticSymbol>();
private resolvedFilePaths = new Set<string>();
constructor(
private host: StaticSymbolResolverHost, private staticSymbolCache: StaticSymbolCache,
private summaryResolver: SummaryResolver<StaticSymbol>,
private errorRecorder?: (error: any, fileName: string) => void) {}
resolveSymbol(staticSymbol: StaticSymbol): ResolvedStaticSymbol {
if (staticSymbol.members.length > 0) {
return this._resolveSymbolMembers(staticSymbol);
}
let result = this._resolveSymbolFromSummary(staticSymbol);
if (!result) {
// Note: Some users use libraries that were not compiled with ngc, i.e. they don't
// have summaries, only .d.ts files. So we always need to check both, the summary
// and metadata.
this._createSymbolsOf(staticSymbol.filePath);
result = this.resolvedSymbols.get(staticSymbol);
}
return result;
}
private _resolveSymbolMembers(staticSymbol: StaticSymbol): ResolvedStaticSymbol {
const members = staticSymbol.members;
const baseResolvedSymbol =
this.resolveSymbol(this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name));
if (!baseResolvedSymbol) {
return null;
}
const baseMetadata = baseResolvedSymbol.metadata;
if (baseMetadata instanceof StaticSymbol) {
return new ResolvedStaticSymbol(
staticSymbol, this.getStaticSymbol(baseMetadata.filePath, baseMetadata.name, members));
} else if (baseMetadata && baseMetadata.__symbolic === 'class') {
if (baseMetadata.statics && members.length === 1) {
return new ResolvedStaticSymbol(staticSymbol, baseMetadata.statics[members[0]]);
}
} else {
let value = baseMetadata;
for (let i = 0; i < members.length && value; i++) {
value = value[members[i]];
}
return new ResolvedStaticSymbol(staticSymbol, value);
}
return null;
}
private _resolveSymbolFromSummary(staticSymbol: StaticSymbol): ResolvedStaticSymbol {
const summary = this.summaryResolver.resolveSummary(staticSymbol);
return summary ? new ResolvedStaticSymbol(staticSymbol, summary.metadata) : null;
}
/**
* getStaticSymbol produces a Type whose metadata is known but whose implementation is not loaded.
* All types passed to the StaticResolver should be pseudo-types returned by this method.
*
* @param declarationFile the absolute path of the file where the symbol is declared
* @param name the name of the type.
*/
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
return this.staticSymbolCache.get(declarationFile, name, members);
}
getSymbolsOf(filePath: string): StaticSymbol[] {
// Note: Some users use libraries that were not compiled with ngc, i.e. they don't
// have summaries, only .d.ts files. So we always need to check both, the summary
// and metadata.
let symbols = new Set<StaticSymbol>(this.summaryResolver.getSymbolsOf(filePath));
this._createSymbolsOf(filePath);
this.resolvedSymbols.forEach((resolvedSymbol) => {
if (resolvedSymbol.symbol.filePath === filePath) {
symbols.add(resolvedSymbol.symbol);
}
});
return Array.from(symbols);
}
private _createSymbolsOf(filePath: string) {
if (this.resolvedFilePaths.has(filePath)) {
return;
}
this.resolvedFilePaths.add(filePath);
const resolvedSymbols: ResolvedStaticSymbol[] = [];
const metadata = this.getModuleMetadata(filePath);
if (metadata['metadata']) {
// handle direct declarations of the symbol
Object.keys(metadata['metadata']).forEach((symbolName) => {
const symbolMeta = metadata['metadata'][symbolName];
resolvedSymbols.push(
this.createResolvedSymbol(this.getStaticSymbol(filePath, symbolName), symbolMeta));
});
}
// handle the symbols in one of the re-export location
if (metadata['exports']) {
for (const moduleExport of metadata['exports']) {
// handle the symbols in the list of explicitly re-exported symbols.
if (moduleExport.export) {
moduleExport.export.forEach((exportSymbol: any) => {
let symbolName: string;
if (typeof exportSymbol === 'string') {
symbolName = exportSymbol;
} else {
symbolName = exportSymbol.as;
}
let symName = symbolName;
if (typeof exportSymbol !== 'string') {
symName = exportSymbol.name;
}
const resolvedModule = this.resolveModule(moduleExport.from, filePath);
if (resolvedModule) {
const targetSymbol = this.getStaticSymbol(resolvedModule, symName);
const sourceSymbol = this.getStaticSymbol(filePath, symbolName);
resolvedSymbols.push(new ResolvedStaticSymbol(sourceSymbol, targetSymbol));
}
});
} else {
// handle the symbols via export * directives.
const resolvedModule = this.resolveModule(moduleExport.from, filePath);
if (resolvedModule) {
const nestedExports = this.getSymbolsOf(resolvedModule);
nestedExports.forEach((targetSymbol) => {
const sourceSymbol = this.getStaticSymbol(filePath, targetSymbol.name);
resolvedSymbols.push(new ResolvedStaticSymbol(sourceSymbol, targetSymbol));
});
}
}
}
}
resolvedSymbols.forEach(
(resolvedSymbol) => this.resolvedSymbols.set(resolvedSymbol.symbol, resolvedSymbol));
}
private createResolvedSymbol(sourceSymbol: StaticSymbol, metadata: any): ResolvedStaticSymbol {
const self = this;
class ReferenceTransformer extends ValueTransformer {
visitStringMap(map: {[key: string]: any}, functionParams: string[]): any {
const symbolic = map['__symbolic'];
if (symbolic === 'function') {
const oldLen = functionParams.length;
functionParams.push(...(map['parameters'] || []));
const result = super.visitStringMap(map, functionParams);
functionParams.length = oldLen;
return result;
} else if (symbolic === 'reference') {
const module = map['module'];
const name = map['name'];
if (!name) {
return null;
}
let filePath: string;
if (module) {
filePath = self.resolveModule(module, sourceSymbol.filePath);
if (!filePath) {
return {
__symbolic: 'error',
message: `Could not resolve ${module} relative to ${sourceSymbol.filePath}.`
};
}
} else {
const isFunctionParam = functionParams.indexOf(name) >= 0;
if (!isFunctionParam) {
filePath = sourceSymbol.filePath;
}
}
if (filePath) {
return self.getStaticSymbol(filePath, name);
} else {
// reference to a function parameter
return {__symbolic: 'reference', name: name};
}
} else {
return super.visitStringMap(map, functionParams);
}
}
}
const transformedMeta = visitValue(metadata, new ReferenceTransformer(), []);
return new ResolvedStaticSymbol(sourceSymbol, transformedMeta);
}
private reportError(error: Error, context: StaticSymbol, path?: string) {
if (this.errorRecorder) {
this.errorRecorder(error, (context && context.filePath) || path);
} else {
throw error;
}
}
/**
* @param module an absolute path to a module file.
*/
private getModuleMetadata(module: string): {[key: string]: any} {
let moduleMetadata = this.metadataCache.get(module);
if (!moduleMetadata) {
const moduleMetadatas = this.host.getMetadataFor(module);
if (moduleMetadatas) {
let maxVersion = -1;
moduleMetadatas.forEach((md) => {
if (md['version'] > maxVersion) {
maxVersion = md['version'];
moduleMetadata = md;
}
});
}
if (!moduleMetadata) {
moduleMetadata =
{__symbolic: 'module', version: SUPPORTED_SCHEMA_VERSION, module: module, metadata: {}};
}
if (moduleMetadata['version'] != SUPPORTED_SCHEMA_VERSION) {
const errorMessage = moduleMetadata['version'] == 2 ?
`Unsupported metadata version ${moduleMetadata['version']} for module ${module}. This module should be compiled with a newer version of ngc` :
`Metadata version mismatch for module ${module}, found version ${moduleMetadata['version']}, expected ${SUPPORTED_SCHEMA_VERSION}`;
this.reportError(new Error(errorMessage), null);
}
this.metadataCache.set(module, moduleMetadata);
}
return moduleMetadata;
}
getSymbolByModule(module: string, symbolName: string, containingFile?: string): StaticSymbol {
const filePath = this.resolveModule(module, containingFile);
if (!filePath) {
throw new Error(`Could not resolve module ${module} relative to ${containingFile}`);
}
return this.getStaticSymbol(filePath, symbolName);
}
private resolveModule(module: string, containingFile: string): string {
try {
return this.host.moduleNameToFileName(module, containingFile);
} catch (e) {
console.error(`Could not resolve module '${module}' relative to file ${containingFile}`);
this.reportError(new e, null, containingFile);
}
}
}

View File

@ -5,13 +5,12 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleSummary, CompilePipeSummary, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, identifierModuleUrl, identifierName} from '../compile_metadata'; import {CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
import {SummaryResolver} from '../summary_resolver'; import {Summary, SummaryResolver} from '../summary_resolver';
import {GeneratedFile} from './generated_file'; import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {StaticReflector} from './static_reflector'; import {ResolvedStaticSymbol} from './static_symbol_resolver';
import {StaticSymbol} from './static_symbol'; import {deserializeSummaries, summaryFileName} from './summary_serializer';
import {filterFileByPatterns} from './utils';
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
@ -19,106 +18,60 @@ export interface AotSummaryResolverHost {
/** /**
* Loads an NgModule/Directive/Pipe summary file * Loads an NgModule/Directive/Pipe summary file
*/ */
loadSummary(filePath: string): string; loadSummary(filePath: string): string /*|null*/;
/** /**
* Returns the output file path of a source file. * Returns whether a file is a source file or not.
* E.g.
* `some_file.ts` -> `some_file.d.ts`
*/ */
getOutputFileName(sourceFilePath: string): string; isSourceFile(sourceFilePath: string): boolean;
} }
export interface AotSummaryResolverOptions { export class AotSummaryResolver implements SummaryResolver<StaticSymbol> {
includeFilePattern?: RegExp; private summaryCache = new Map<StaticSymbol, Summary<StaticSymbol>>();
excludeFilePattern?: RegExp; private loadedFilePaths = new Set<string>();
}
export class AotSummaryResolver implements SummaryResolver { constructor(private host: AotSummaryResolverHost, private staticSymbolCache: StaticSymbolCache) {}
private summaryCache: {[cacheKey: string]: CompileTypeSummary} = {};
constructor( private _assertNoMembers(symbol: StaticSymbol) {
private host: AotSummaryResolverHost, private staticReflector: StaticReflector, if (symbol.members.length) {
private options: AotSummaryResolverOptions) {} throw new Error(
`Internal state: StaticSymbols in summaries can't have members! ${JSON.stringify(symbol)}`);
serializeSummaries(srcFileUrl: string, summaries: CompileTypeSummary[]): GeneratedFile {
const jsonReplacer = (key: string, value: any) => {
if (value instanceof StaticSymbol) {
// We convert the source filenames into output filenames,
// as the generated summary file will be used when the current
// compilation unit is used as a library
return {
'__symbolic__': 'symbol',
'name': value.name,
'path': this.host.getOutputFileName(value.filePath),
'members': value.members
};
} }
return value;
};
const allSummaries = summaries.slice();
summaries.forEach((summary) => {
if (summary.summaryKind === CompileSummaryKind.NgModule) {
const moduleMeta = <CompileNgModuleSummary>summary;
moduleMeta.exportedDirectives.concat(moduleMeta.exportedPipes).forEach((id) => {
if (!filterFileByPatterns(id.reference.filePath, this.options)) {
allSummaries.push(this.resolveSummary(id.reference));
}
});
}
});
return new GeneratedFile(
srcFileUrl, summaryFileName(srcFileUrl), JSON.stringify(allSummaries, jsonReplacer));
} }
private _cacheKey(symbol: StaticSymbol) { return `${symbol.filePath}|${symbol.name}`; } resolveSummary(staticSymbol: StaticSymbol): Summary<StaticSymbol> {
this._assertNoMembers(staticSymbol);
resolveSummary(staticSymbol: StaticSymbol): any { let summary = this.summaryCache.get(staticSymbol);
const filePath = staticSymbol.filePath;
const name = staticSymbol.name;
const cacheKey = this._cacheKey(staticSymbol);
if (!filterFileByPatterns(filePath, this.options)) {
let summary = this.summaryCache[cacheKey];
const summaryFilePath = summaryFileName(filePath);
if (!summary) { if (!summary) {
try { this._loadSummaryFile(staticSymbol.filePath);
const jsonReviver = (key: string, value: any) => { summary = this.summaryCache.get(staticSymbol);
if (value && value['__symbolic__'] === 'symbol') {
// Note: We can't use staticReflector.findDeclaration here:
// Summary files can contain symbols of transitive compilation units
// (via the providers), and findDeclaration needs .metadata.json / .d.ts files,
// but we don't want to depend on these for transitive dependencies.
return this.staticReflector.getStaticSymbol(
value['path'], value['name'], value['members']);
} else {
return value;
} }
}; return summary;
const readSummaries: CompileTypeSummary[] = }
JSON.parse(this.host.loadSummary(summaryFilePath), jsonReviver);
readSummaries.forEach((summary) => { getSymbolsOf(filePath: string): StaticSymbol[] {
const filePath = summary.type.reference.filePath; this._loadSummaryFile(filePath);
this.summaryCache[this._cacheKey(summary.type.reference)] = summary; return Array.from(this.summaryCache.keys()).filter((symbol) => symbol.filePath === filePath);
}); }
summary = this.summaryCache[cacheKey];
private _loadSummaryFile(filePath: string) {
if (this.loadedFilePaths.has(filePath)) {
return;
}
this.loadedFilePaths.add(filePath);
if (!this.host.isSourceFile(filePath)) {
const summaryFilePath = summaryFileName(filePath);
let json: string;
try {
json = this.host.loadSummary(summaryFilePath);
} catch (e) { } catch (e) {
console.error(`Error loading summary file ${summaryFilePath}`); console.error(`Error loading summary file ${summaryFilePath}`);
throw e; throw e;
} }
if (json) {
const readSummaries = deserializeSummaries(this.staticSymbolCache, json);
readSummaries.forEach((summary) => { this.summaryCache.set(summary.symbol, summary); });
} }
if (!summary) {
throw new Error(
`Could not find the symbol ${name} in the summary file ${summaryFilePath}!`);
}
return summary;
} else {
return null;
} }
} }
} }
function summaryFileName(fileName: string): string {
const fileNameWithoutSuffix = fileName.replace(STRIP_SRC_FILE_SUFFIXES, '');
return `${fileNameWithoutSuffix}.ngsummary.json`;
}

View File

@ -0,0 +1,183 @@
/**
* @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 {CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleSummary, CompilePipeSummary, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, identifierModuleUrl, identifierName} from '../compile_metadata';
import {Summary, SummaryResolver} from '../summary_resolver';
import {ValueTransformer, visitValue} from '../util';
import {GeneratedFile} from './generated_file';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
export interface AotSummarySerializerHost {
/**
* Returns the output file path of a source file.
* E.g.
* `some_file.ts` -> `some_file.d.ts`
*/
getOutputFileName(sourceFilePath: string): string;
/**
* Returns whether a file is a source file or not.
*/
isSourceFile(sourceFilePath: string): boolean;
}
export function serializeSummaries(
host: AotSummarySerializerHost, summaryResolver: SummaryResolver<StaticSymbol>,
symbolResolver: StaticSymbolResolver,
symbols: ResolvedStaticSymbol[], types: CompileTypeSummary[]): string {
const serializer = new Serializer(host);
// for symbols, we use everything except for the class metadata itself
// (we keep the statics though), as the class metadata is contained in the
// CompileTypeSummary.
symbols.forEach(
(resolvedSymbol) => serializer.addOrMergeSummary(
{symbol: resolvedSymbol.symbol, metadata: resolvedSymbol.metadata}));
// Add summaries that are referenced by the given symbols (transitively)
// Note: the serializer.symbols array might be growing while
// we execute the loop!
for (let processedIndex = 0; processedIndex < serializer.symbols.length; processedIndex++) {
const symbol = serializer.symbols[processedIndex];
if (!host.isSourceFile(symbol.filePath)) {
let summary = summaryResolver.resolveSummary(symbol);
if (!summary) {
// some symbols might originate from a plain typescript library
// that just exported .d.ts and .metadata.json files, i.e. where no summary
// files were created.
const resolvedSymbol = symbolResolver.resolveSymbol(symbol);
if (resolvedSymbol) {
summary = {symbol: resolvedSymbol.symbol, metadata: resolvedSymbol.metadata};
}
}
if (summary) {
serializer.addOrMergeSummary(summary);
}
}
}
// Add type summaries.
// Note: We don't add the summaries of all referenced symbols as for the ResolvedSymbols,
// as the type summaries already contain the transitive data that they require
// (in a minimal way).
types.forEach((typeSummary) => {
serializer.addOrMergeSummary(
{symbol: typeSummary.type.reference, metadata: {__symbolic: 'class'}, type: typeSummary});
if (typeSummary.summaryKind === CompileSummaryKind.NgModule) {
const ngModuleSummary = <CompileNgModuleSummary>typeSummary;
ngModuleSummary.exportedDirectives.concat(ngModuleSummary.exportedPipes).forEach((id) => {
const symbol: StaticSymbol = id.reference;
if (!host.isSourceFile(symbol.filePath)) {
serializer.addOrMergeSummary(summaryResolver.resolveSummary(symbol));
}
});
}
});
return serializer.serialize();
}
export function deserializeSummaries(
symbolCache: StaticSymbolCache, json: string): Summary<StaticSymbol>[] {
const deserializer = new Deserializer(symbolCache);
return deserializer.deserialize(json);
}
export function summaryFileName(fileName: string): string {
const fileNameWithoutSuffix = fileName.replace(STRIP_SRC_FILE_SUFFIXES, '');
return `${fileNameWithoutSuffix}.ngsummary.json`;
}
class Serializer extends ValueTransformer {
symbols: StaticSymbol[] = [];
private indexBySymbol = new Map<StaticSymbol, number>();
// This now contains a `__symbol: number` in the place of
// StaticSymbols, but otherwise has the same shape as the original objects.
private processedSummaryBySymbol = new Map<StaticSymbol, any>();
private processedSummaries: any[] = [];
constructor(private host: AotSummarySerializerHost) { super(); }
addOrMergeSummary(summary: Summary<StaticSymbol>) {
let symbolMeta = summary.metadata;
if (symbolMeta && symbolMeta.__symbolic === 'class') {
// For classes, we only keep their statics, but not the metadata
// of the class itself as that has been captured already via other summaries
// (e.g. DirectiveSummary, ...).
symbolMeta = {__symbolic: 'class', statics: symbolMeta.statics};
}
let processedSummary = this.processedSummaryBySymbol.get(summary.symbol);
if (!processedSummary) {
processedSummary = this.processValue({symbol: summary.symbol});
this.processedSummaries.push(processedSummary);
this.processedSummaryBySymbol.set(summary.symbol, processedSummary);
}
// Note: == by purpose to compare with undefined!
if (processedSummary.metadata == null && symbolMeta != null) {
processedSummary.metadata = this.processValue(symbolMeta);
}
// Note: == by purpose to compare with undefined!
if (processedSummary.type == null && summary.type != null) {
processedSummary.type = this.processValue(summary.type);
}
}
serialize(): string {
return JSON.stringify({
summaries: this.processedSummaries,
symbols: this.symbols.map((symbol, index) => {
return {
__symbol: index,
name: symbol.name,
// We convert the source filenames tinto output filenames,
// as the generated summary file will be used when teh current
// compilation unit is used as a library
filePath: this.host.getOutputFileName(symbol.filePath)
};
})
});
}
private processValue(value: any): any { return visitValue(value, this, null); }
visitOther(value: any, context: any): any {
if (value instanceof StaticSymbol) {
let index = this.indexBySymbol.get(value);
// Note: == by purpose to compare with undefined!
if (index == null) {
index = this.indexBySymbol.size;
this.indexBySymbol.set(value, index);
this.symbols.push(value);
}
return {__symbol: index};
}
}
}
class Deserializer extends ValueTransformer {
private symbols: StaticSymbol[];
constructor(private symbolCache: StaticSymbolCache) { super(); }
deserialize(json: string): Summary<StaticSymbol>[] {
const data: {summaries: any[], symbols: any[]} = JSON.parse(json);
this.symbols = data.symbols.map(
serializedSymbol => this.symbolCache.get(serializedSymbol.filePath, serializedSymbol.name));
return visitValue(data.summaries, this, null);
}
visitStringMap(map: {[key: string]: any}, context: any): any {
if ('__symbol' in map) {
return this.symbols[map['__symbol']];
} else {
return super.visitStringMap(map, context);
}
}
}

View File

@ -1,19 +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
*/
export function filterFileByPatterns(
fileName: string, options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}) {
let match = true;
if (options.includeFilePattern) {
match = match && !!options.includeFilePattern.exec(fileName);
}
if (options.excludeFilePattern) {
match = match && !options.excludeFilePattern.exec(fileName);
}
return match;
}

View File

@ -115,10 +115,10 @@ export function identifierModuleUrl(compileIdentifier: CompileIdentifierMetadata
export interface CompileIdentifierMetadata { reference: any; } export interface CompileIdentifierMetadata { reference: any; }
export enum CompileSummaryKind { export enum CompileSummaryKind {
Template,
Pipe, Pipe,
Directive, Directive,
NgModule NgModule,
Injectable
} }
/** /**
@ -126,9 +126,10 @@ export enum CompileSummaryKind {
* in other modules / components. However, this data is not enough to compile * in other modules / components. However, this data is not enough to compile
* the directive / module itself. * the directive / module itself.
*/ */
export interface CompileSummary { summaryKind: CompileSummaryKind; } export interface CompileTypeSummary {
summaryKind: CompileSummaryKind;
export interface CompileTypeSummary extends CompileSummary { type: CompileTypeMetadata; } type: CompileTypeMetadata;
}
export interface CompileDiDependencyMetadata { export interface CompileDiDependencyMetadata {
isAttribute?: boolean; isAttribute?: boolean;
@ -210,7 +211,7 @@ export class CompileStylesheetMetadata {
/** /**
* Summary Metadata regarding compilation of a template. * Summary Metadata regarding compilation of a template.
*/ */
export interface CompileTemplateSummary extends CompileSummary { export interface CompileTemplateSummary {
animations: string[]; animations: string[];
ngContentSelectors: string[]; ngContentSelectors: string[];
encapsulation: ViewEncapsulation; encapsulation: ViewEncapsulation;
@ -258,7 +259,6 @@ export class CompileTemplateMetadata {
toSummary(): CompileTemplateSummary { toSummary(): CompileTemplateSummary {
return { return {
summaryKind: CompileSummaryKind.Template,
animations: this.animations.map(anim => anim.name), animations: this.animations.map(anim => anim.name),
ngContentSelectors: this.ngContentSelectors, ngContentSelectors: this.ngContentSelectors,
encapsulation: this.encapsulation encapsulation: this.encapsulation

View File

@ -6,11 +6,12 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Component, Injectable, ViewEncapsulation} from '@angular/core'; import {Component, ViewEncapsulation} from '@angular/core';
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata'; import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata';
import {CompilerConfig} from './config'; import {CompilerConfig} from './config';
import {isBlank, isPresent, stringify} from './facade/lang'; import {isBlank, isPresent, stringify} from './facade/lang';
import {CompilerInjectable} from './injectable';
import * as html from './ml_parser/ast'; import * as html from './ml_parser/ast';
import {HtmlParser} from './ml_parser/html_parser'; import {HtmlParser} from './ml_parser/html_parser';
import {InterpolationConfig} from './ml_parser/interpolation_config'; import {InterpolationConfig} from './ml_parser/interpolation_config';
@ -18,7 +19,7 @@ import {ResourceLoader} from './resource_loader';
import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver'; import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver';
import {PreparsedElementType, preparseElement} from './template_parser/template_preparser'; import {PreparsedElementType, preparseElement} from './template_parser/template_preparser';
import {UrlResolver} from './url_resolver'; import {UrlResolver} from './url_resolver';
import {SyncAsyncResult} from './util'; import {SyncAsyncResult, SyntaxError} from './util';
export interface PrenormalizedTemplateMetadata { export interface PrenormalizedTemplateMetadata {
componentType: any; componentType: any;
@ -32,7 +33,7 @@ export interface PrenormalizedTemplateMetadata {
animations?: CompileAnimationEntryMetadata[]; animations?: CompileAnimationEntryMetadata[];
} }
@Injectable() @CompilerInjectable()
export class DirectiveNormalizer { export class DirectiveNormalizer {
private _resourceLoaderCache = new Map<string, Promise<string>>(); private _resourceLoaderCache = new Map<string, Promise<string>>();
@ -70,7 +71,7 @@ export class DirectiveNormalizer {
} else if (prenormData.templateUrl) { } else if (prenormData.templateUrl) {
normalizedTemplateAsync = this.normalizeTemplateAsync(prenormData); normalizedTemplateAsync = this.normalizeTemplateAsync(prenormData);
} else { } else {
throw new Error( throw new SyntaxError(
`No template specified for component ${stringify(prenormData.componentType)}`); `No template specified for component ${stringify(prenormData.componentType)}`);
} }
@ -104,7 +105,7 @@ export class DirectiveNormalizer {
template, stringify(prenomData.componentType), false, interpolationConfig); template, stringify(prenomData.componentType), false, interpolationConfig);
if (rootNodesAndErrors.errors.length > 0) { if (rootNodesAndErrors.errors.length > 0) {
const errorString = rootNodesAndErrors.errors.join('\n'); const errorString = rootNodesAndErrors.errors.join('\n');
throw new Error(`Template parse errors:\n${errorString}`); throw new SyntaxError(`Template parse errors:\n${errorString}`);
} }
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({ const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
styles: prenomData.styles, styles: prenomData.styles,

View File

@ -6,14 +6,16 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Component, Directive, HostBinding, HostListener, Injectable, Input, Output, Query, Type, resolveForwardRef} from '@angular/core'; import {Component, Directive, HostBinding, HostListener, Input, Output, Query, Type, resolveForwardRef} from '@angular/core';
import {ListWrapper, StringMapWrapper} from './facade/collection'; import {ListWrapper, StringMapWrapper} from './facade/collection';
import {stringify} from './facade/lang'; import {stringify} from './facade/lang';
import {CompilerInjectable} from './injectable';
import {ReflectorReader, reflector} from './private_import_core'; import {ReflectorReader, reflector} from './private_import_core';
import {splitAtColon} from './util'; import {splitAtColon} from './util';
/* /*
* Resolve a `Type` for {@link Directive}. * Resolve a `Type` for {@link Directive}.
* *
@ -21,7 +23,7 @@ import {splitAtColon} from './util';
* *
* See {@link Compiler} * See {@link Compiler}
*/ */
@Injectable() @CompilerInjectable()
export class DirectiveResolver { export class DirectiveResolver {
constructor(private _reflector: ReflectorReader = reflector) {} constructor(private _reflector: ReflectorReader = reflector) {}

View File

@ -6,8 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Injectable} from '@angular/core';
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, identifierModuleUrl, identifierName} from './compile_metadata'; import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util'; import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util';
import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter'; import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter';
@ -15,6 +13,7 @@ import {triggerAnimation, writeToRenderer} from './compiler_util/render_util';
import {CompilerConfig} from './config'; import {CompilerConfig} from './config';
import {Parser} from './expression_parser/parser'; import {Parser} from './expression_parser/parser';
import {Identifiers, createIdentifier} from './identifiers'; import {Identifiers, createIdentifier} from './identifiers';
import {CompilerInjectable} from './injectable';
import {DEFAULT_INTERPOLATION_CONFIG} from './ml_parser/interpolation_config'; import {DEFAULT_INTERPOLATION_CONFIG} from './ml_parser/interpolation_config';
import {ClassBuilder, createClassStmt} from './output/class_builder'; import {ClassBuilder, createClassStmt} from './output/class_builder';
import * as o from './output/output_ast'; import * as o from './output/output_ast';
@ -51,7 +50,7 @@ const RESET_CHANGES_STMT = o.THIS_EXPR.prop(CHANGES_FIELD_NAME).set(o.literalMap
* *
* So far, only `@Input` and the lifecycle hooks have been implemented. * So far, only `@Input` and the lifecycle hooks have been implemented.
*/ */
@Injectable() @CompilerInjectable()
export class DirectiveWrapperCompiler { export class DirectiveWrapperCompiler {
static dirWrapperClassName(id: CompileIdentifierMetadata) { static dirWrapperClassName(id: CompileIdentifierMetadata) {
return `Wrapper_${identifierName(id)}`; return `Wrapper_${identifierName(id)}`;

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Injectable} from '@angular/core';
import * as chars from '../chars'; import * as chars from '../chars';
import {NumberWrapper, isPresent} from '../facade/lang'; import {NumberWrapper, isPresent} from '../facade/lang';
import {CompilerInjectable} from '../injectable';
export enum TokenType { export enum TokenType {
Character, Character,
@ -22,7 +22,7 @@ export enum TokenType {
const KEYWORDS = ['var', 'let', 'null', 'undefined', 'true', 'false', 'if', 'else', 'this']; const KEYWORDS = ['var', 'let', 'null', 'undefined', 'true', 'false', 'if', 'else', 'this'];
@Injectable() @CompilerInjectable()
export class Lexer { export class Lexer {
tokenize(text: string): Token[] { tokenize(text: string): Token[] {
const scanner = new _Scanner(text); const scanner = new _Scanner(text);

View File

@ -6,10 +6,9 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Injectable} from '@angular/core';
import * as chars from '../chars'; import * as chars from '../chars';
import {escapeRegExp, isBlank, isPresent} from '../facade/lang'; import {escapeRegExp, isBlank, isPresent} from '../facade/lang';
import {CompilerInjectable} from '../injectable';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config';
import {AST, ASTWithSource, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, ParserError, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead, TemplateBinding} from './ast'; import {AST, ASTWithSource, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, ParserError, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead, TemplateBinding} from './ast';
@ -31,7 +30,7 @@ function _createInterpolateRegExp(config: InterpolationConfig): RegExp {
return new RegExp(pattern, 'g'); return new RegExp(pattern, 'g');
} }
@Injectable() @CompilerInjectable()
export class Parser { export class Parser {
private errors: ParserError[] = []; private errors: ParserError[] = [];

View File

@ -14,7 +14,9 @@ import {ViewEncapsulation} from '@angular/core';
import {analyzeAndValidateNgModules, extractProgramSymbols} from '../aot/compiler'; import {analyzeAndValidateNgModules, extractProgramSymbols} from '../aot/compiler';
import {StaticAndDynamicReflectionCapabilities} from '../aot/static_reflection_capabilities'; import {StaticAndDynamicReflectionCapabilities} from '../aot/static_reflection_capabilities';
import {StaticReflector, StaticReflectorHost} from '../aot/static_reflector'; import {StaticReflector} from '../aot/static_reflector';
import {StaticSymbolCache} from '../aot/static_symbol';
import {StaticSymbolResolver, StaticSymbolResolverHost} from '../aot/static_symbol_resolver';
import {AotSummaryResolver, AotSummaryResolverHost} from '../aot/summary_resolver'; import {AotSummaryResolver, AotSummaryResolverHost} from '../aot/summary_resolver';
import {CompileDirectiveMetadata} from '../compile_metadata'; import {CompileDirectiveMetadata} from '../compile_metadata';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
@ -26,23 +28,17 @@ import {InterpolationConfig} from '../ml_parser/interpolation_config';
import {NgModuleResolver} from '../ng_module_resolver'; import {NgModuleResolver} from '../ng_module_resolver';
import {ParseError} from '../parse_util'; import {ParseError} from '../parse_util';
import {PipeResolver} from '../pipe_resolver'; import {PipeResolver} from '../pipe_resolver';
import {Console} from '../private_import_core';
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry'; import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
import {createOfflineCompileUrlResolver} from '../url_resolver'; import {createOfflineCompileUrlResolver} from '../url_resolver';
import {I18NHtmlParser} from './i18n_html_parser'; import {I18NHtmlParser} from './i18n_html_parser';
import {MessageBundle} from './message_bundle'; import {MessageBundle} from './message_bundle';
export interface ExtractorOptions {
includeFilePattern?: RegExp;
excludeFilePattern?: RegExp;
}
/** /**
* The host of the Extractor disconnects the implementation from TypeScript / other language * The host of the Extractor disconnects the implementation from TypeScript / other language
* services and from underlying file systems. * services and from underlying file systems.
*/ */
export interface ExtractorHost extends StaticReflectorHost, AotSummaryResolverHost { export interface ExtractorHost extends StaticSymbolResolverHost, AotSummaryResolverHost {
/** /**
* Loads a resource (e.g. html / css) * Loads a resource (e.g. html / css)
*/ */
@ -51,14 +47,13 @@ export interface ExtractorHost extends StaticReflectorHost, AotSummaryResolverHo
export class Extractor { export class Extractor {
constructor( constructor(
private options: ExtractorOptions, public host: ExtractorHost, public host: ExtractorHost, private staticSymbolResolver: StaticSymbolResolver,
private staticReflector: StaticReflector, private messageBundle: MessageBundle, private messageBundle: MessageBundle, private metadataResolver: CompileMetadataResolver) {}
private metadataResolver: CompileMetadataResolver) {}
extract(rootFiles: string[]): Promise<MessageBundle> { extract(rootFiles: string[]): Promise<MessageBundle> {
const programSymbols = extractProgramSymbols(this.staticReflector, rootFiles, this.options); const programSymbols = extractProgramSymbols(this.staticSymbolResolver, rootFiles, this.host);
const {ngModuleByPipeOrDirective, files, ngModules} = const {ngModuleByPipeOrDirective, files, ngModules} =
analyzeAndValidateNgModules(programSymbols, this.options, this.metadataResolver); analyzeAndValidateNgModules(programSymbols, this.host, this.metadataResolver);
return Promise return Promise
.all(ngModules.map( .all(ngModules.map(
ngModule => this.metadataResolver.loadNgModuleDirectiveAndPipeMetadata( ngModule => this.metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
@ -91,12 +86,14 @@ export class Extractor {
}); });
} }
static create(host: ExtractorHost, options: ExtractorOptions): static create(host: ExtractorHost): {extractor: Extractor, staticReflector: StaticReflector} {
{extractor: Extractor, staticReflector: StaticReflector} {
const htmlParser = new I18NHtmlParser(new HtmlParser()); const htmlParser = new I18NHtmlParser(new HtmlParser());
const urlResolver = createOfflineCompileUrlResolver(); const urlResolver = createOfflineCompileUrlResolver();
const staticReflector = new StaticReflector(host); const symbolCache = new StaticSymbolCache();
const summaryResolver = new AotSummaryResolver(host, symbolCache);
const staticSymbolResolver = new StaticSymbolResolver(host, symbolCache, summaryResolver);
const staticReflector = new StaticReflector(staticSymbolResolver);
StaticAndDynamicReflectionCapabilities.install(staticReflector); StaticAndDynamicReflectionCapabilities.install(staticReflector);
const config = new CompilerConfig({ const config = new CompilerConfig({
@ -111,13 +108,13 @@ export class Extractor {
const elementSchemaRegistry = new DomElementSchemaRegistry(); const elementSchemaRegistry = new DomElementSchemaRegistry();
const resolver = new CompileMetadataResolver( const resolver = new CompileMetadataResolver(
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector), new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
new PipeResolver(staticReflector), new AotSummaryResolver(host, staticReflector, options), new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer,
elementSchemaRegistry, normalizer, staticReflector); staticReflector);
// TODO(vicb): implicit tags & attributes // TODO(vicb): implicit tags & attributes
const messageBundle = new MessageBundle(htmlParser, [], {}); const messageBundle = new MessageBundle(htmlParser, [], {});
const extractor = new Extractor(options, host, staticReflector, messageBundle, resolver); const extractor = new Extractor(host, staticSymbolResolver, messageBundle, resolver);
return {extractor, staticReflector}; return {extractor, staticReflector};
} }
} }

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
*/ */
export {Extractor, ExtractorHost, ExtractorOptions} from './extractor'; export {Extractor, ExtractorHost} from './extractor';
export {I18NHtmlParser} from './i18n_html_parser'; export {I18NHtmlParser} from './i18n_html_parser';
export {MessageBundle} from './message_bundle'; export {MessageBundle} from './message_bundle';
export {Serializer} from './serializers/serializer'; export {Serializer} from './serializers/serializer';

View File

@ -39,6 +39,7 @@ const _DOCTYPE = `<!ELEMENT messagebundle (msg)*>
export class Xmb implements Serializer { export class Xmb implements Serializer {
write(messages: i18n.Message[]): string { write(messages: i18n.Message[]): string {
const exampleVisitor = new ExampleVisitor();
const visitor = new _Visitor(); const visitor = new _Visitor();
const visited: {[id: string]: boolean} = {}; const visited: {[id: string]: boolean} = {};
let rootNode = new xml.Tag(_MESSAGES_TAG); let rootNode = new xml.Tag(_MESSAGES_TAG);
@ -71,7 +72,7 @@ export class Xmb implements Serializer {
new xml.CR(), new xml.CR(),
new xml.Doctype(_MESSAGES_TAG, _DOCTYPE), new xml.Doctype(_MESSAGES_TAG, _DOCTYPE),
new xml.CR(), new xml.CR(),
rootNode, exampleVisitor.addDefaultExamples(rootNode),
new xml.CR(), new xml.CR(),
]); ]);
} }
@ -134,3 +135,26 @@ class _Visitor implements i18n.Visitor {
export function digest(message: i18n.Message): string { export function digest(message: i18n.Message): string {
return decimalDigest(message); return decimalDigest(message);
} }
// TC requires at least one non-empty example on placeholders
class ExampleVisitor implements xml.IVisitor {
addDefaultExamples(node: xml.Node): xml.Node {
node.visit(this);
return node;
}
visitTag(tag: xml.Tag): void {
if (tag.name === _PLACEHOLDER_TAG) {
if (!tag.children || tag.children.length == 0) {
const exText = new xml.Text(tag.attrs['name'] || '...');
tag.children = [new xml.Tag(_EXEMPLE_TAG, {}, [exText])];
}
} else if (tag.children) {
tag.children.forEach(node => node.visit(this));
}
}
visitText(text: xml.Text): void {}
visitDeclaration(decl: xml.Declaration): void {}
visitDoctype(doctype: xml.Doctype): void {}
}

View File

@ -0,0 +1,16 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* A replacement for @Injectable to be used in the compiler, so that
* we don't try to evaluate the metadata in the compiler during AoT.
* This decorator is enough to make the compiler work with the ReflectiveInjector though.
*/
export function CompilerInjectable(): (data: any) => any {
return (x) => x;
}

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 {Compiler, ComponentFactory, Injectable, Injector, ModuleWithComponentFactories, NgModuleFactory, SchemaMetadata, Type} from '@angular/core'; import {Compiler, ComponentFactory, Injector, ModuleWithComponentFactories, NgModuleFactory, SchemaMetadata, Type} from '@angular/core';
import {AnimationCompiler} from '../animation/animation_compiler'; import {AnimationCompiler} from '../animation/animation_compiler';
import {AnimationParser} from '../animation/animation_parser'; import {AnimationParser} from '../animation/animation_parser';
@ -15,6 +15,7 @@ import {CompilerConfig} from '../config';
import {DirectiveNormalizer} from '../directive_normalizer'; import {DirectiveNormalizer} from '../directive_normalizer';
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler'; import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
import {stringify} from '../facade/lang'; import {stringify} from '../facade/lang';
import {CompilerInjectable} from '../injectable';
import {CompileMetadataResolver} from '../metadata_resolver'; import {CompileMetadataResolver} from '../metadata_resolver';
import {NgModuleCompiler} from '../ng_module_compiler'; import {NgModuleCompiler} from '../ng_module_compiler';
import * as ir from '../output/output_ast'; import * as ir from '../output/output_ast';
@ -36,7 +37,7 @@ import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDepende
* from a trusted source. Attacker-controlled data introduced by a template could expose your * from a trusted source. Attacker-controlled data introduced by a template could expose your
* application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security). * application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security).
*/ */
@Injectable() @CompilerInjectable()
export class JitCompiler implements Compiler { export class JitCompiler implements Compiler {
private _compiledTemplateCache = new Map<Type<any>, CompiledTemplate>(); private _compiledTemplateCache = new Map<Type<any>, CompiledTemplate>();
private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>(); private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>();

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 {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Injectable, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core'; import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
import {AnimationParser} from '../animation/animation_parser'; import {AnimationParser} from '../animation/animation_parser';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
@ -16,6 +16,7 @@ import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
import {Lexer} from '../expression_parser/lexer'; import {Lexer} from '../expression_parser/lexer';
import {Parser} from '../expression_parser/parser'; import {Parser} from '../expression_parser/parser';
import * as i18n from '../i18n/index'; import * as i18n from '../i18n/index';
import {CompilerInjectable} from '../injectable';
import {CompileMetadataResolver} from '../metadata_resolver'; import {CompileMetadataResolver} from '../metadata_resolver';
import {HtmlParser} from '../ml_parser/html_parser'; import {HtmlParser} from '../ml_parser/html_parser';
import {NgModuleCompiler} from '../ng_module_compiler'; import {NgModuleCompiler} from '../ng_module_compiler';
@ -83,7 +84,7 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
]; ];
@Injectable() @CompilerInjectable()
export class JitCompilerFactory implements CompilerFactory { export class JitCompilerFactory implements CompilerFactory {
private _defaultOptions: CompilerOptions[]; private _defaultOptions: CompilerOptions[];
constructor(@Inject(COMPILER_OPTIONS) defaultOptions: CompilerOptions[]) { constructor(@Inject(COMPILER_OPTIONS) defaultOptions: CompilerOptions[]) {

View File

@ -16,6 +16,7 @@ import {DirectiveResolver} from './directive_resolver';
import {ListWrapper, StringMapWrapper} from './facade/collection'; import {ListWrapper, StringMapWrapper} from './facade/collection';
import {isBlank, isPresent, stringify} from './facade/lang'; import {isBlank, isPresent, stringify} from './facade/lang';
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers'; import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers';
import {CompilerInjectable} from './injectable';
import {hasLifecycleHook} from './lifecycle_reflector'; import {hasLifecycleHook} from './lifecycle_reflector';
import {NgModuleResolver} from './ng_module_resolver'; import {NgModuleResolver} from './ng_module_resolver';
import {PipeResolver} from './pipe_resolver'; import {PipeResolver} from './pipe_resolver';
@ -23,7 +24,7 @@ import {ComponentStillLoadingError, LIFECYCLE_HOOKS_VALUES, ReflectorReader, ref
import {ElementSchemaRegistry} from './schema/element_schema_registry'; import {ElementSchemaRegistry} from './schema/element_schema_registry';
import {SummaryResolver} from './summary_resolver'; import {SummaryResolver} from './summary_resolver';
import {getUrlScheme} from './url_resolver'; import {getUrlScheme} from './url_resolver';
import {MODULE_SUFFIX, SyncAsyncResult, ValueTransformer, visitValue} from './util'; import {MODULE_SUFFIX, SyncAsyncResult, SyntaxError, ValueTransformer, visitValue} from './util';
export type ErrorCollector = (error: any, type?: any) => void; export type ErrorCollector = (error: any, type?: any) => void;
export const ERROR_COLLECTOR_TOKEN = new OpaqueToken('ErrorCollector'); export const ERROR_COLLECTOR_TOKEN = new OpaqueToken('ErrorCollector');
@ -35,7 +36,7 @@ export const ERROR_COLLECTOR_TOKEN = new OpaqueToken('ErrorCollector');
// But we want to report errors even when the async work is // But we want to report errors even when the async work is
// not required to check that the user would have been able // not required to check that the user would have been able
// to wait correctly. // to wait correctly.
@Injectable() @CompilerInjectable()
export class CompileMetadataResolver { export class CompileMetadataResolver {
private _directiveCache = new Map<Type<any>, cpl.CompileDirectiveMetadata>(); private _directiveCache = new Map<Type<any>, cpl.CompileDirectiveMetadata>();
private _summaryCache = new Map<Type<any>, cpl.CompileTypeSummary>(); private _summaryCache = new Map<Type<any>, cpl.CompileTypeSummary>();
@ -45,7 +46,7 @@ export class CompileMetadataResolver {
constructor( constructor(
private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver, private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver,
private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver, private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver<any>,
private _schemaRegistry: ElementSchemaRegistry, private _schemaRegistry: ElementSchemaRegistry,
private _directiveNormalizer: DirectiveNormalizer, private _directiveNormalizer: DirectiveNormalizer,
private _reflector: ReflectorReader = reflector, private _reflector: ReflectorReader = reflector,
@ -128,12 +129,13 @@ export class CompileMetadataResolver {
} }
private _loadSummary(type: any, kind: cpl.CompileSummaryKind): cpl.CompileTypeSummary { private _loadSummary(type: any, kind: cpl.CompileSummaryKind): cpl.CompileTypeSummary {
let summary = this._summaryCache.get(type); let typeSummary = this._summaryCache.get(type);
if (!summary) { if (!typeSummary) {
summary = this._summaryResolver.resolveSummary(type); const summary = this._summaryResolver.resolveSummary(type);
this._summaryCache.set(type, summary); typeSummary = summary ? summary.type : null;
this._summaryCache.set(type, typeSummary);
} }
return summary && summary.summaryKind === kind ? summary : null; return typeSummary && typeSummary.summaryKind === kind ? typeSummary : null;
} }
private _loadDirectiveMetadata(directiveType: any, isSync: boolean): Promise<any> { private _loadDirectiveMetadata(directiveType: any, isSync: boolean): Promise<any> {
@ -237,7 +239,7 @@ export class CompileMetadataResolver {
if (dirMeta.viewProviders) { if (dirMeta.viewProviders) {
viewProviders = this._getProvidersMetadata( viewProviders = this._getProvidersMetadata(
dirMeta.viewProviders, entryComponentMetadata, dirMeta.viewProviders, entryComponentMetadata,
`viewProviders for "${stringify(directiveType)}"`, [], directiveType); `viewProviders for "${stringifyType(directiveType)}"`, [], directiveType);
} }
if (dirMeta.entryComponents) { if (dirMeta.entryComponents) {
entryComponentMetadata = flattenAndDedupeArray(dirMeta.entryComponents) entryComponentMetadata = flattenAndDedupeArray(dirMeta.entryComponents)
@ -251,7 +253,8 @@ export class CompileMetadataResolver {
// Directive // Directive
if (!selector) { if (!selector) {
this._reportError( this._reportError(
new Error(`Directive ${stringify(directiveType)} has no selector, please add it!`), new SyntaxError(
`Directive ${stringifyType(directiveType)} has no selector, please add it!`),
directiveType); directiveType);
selector = 'error'; selector = 'error';
} }
@ -260,8 +263,8 @@ export class CompileMetadataResolver {
let providers: cpl.CompileProviderMetadata[] = []; let providers: cpl.CompileProviderMetadata[] = [];
if (isPresent(dirMeta.providers)) { if (isPresent(dirMeta.providers)) {
providers = this._getProvidersMetadata( providers = this._getProvidersMetadata(
dirMeta.providers, entryComponentMetadata, `providers for "${stringify(directiveType)}"`, dirMeta.providers, entryComponentMetadata,
[], directiveType); `providers for "${stringifyType(directiveType)}"`, [], directiveType);
} }
let queries: cpl.CompileQueryMetadata[] = []; let queries: cpl.CompileQueryMetadata[] = [];
let viewQueries: cpl.CompileQueryMetadata[] = []; let viewQueries: cpl.CompileQueryMetadata[] = [];
@ -297,8 +300,8 @@ export class CompileMetadataResolver {
const dirMeta = this._directiveCache.get(directiveType); const dirMeta = this._directiveCache.get(directiveType);
if (!dirMeta) { if (!dirMeta) {
this._reportError( this._reportError(
new Error( new SyntaxError(
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringify(directiveType)}.`), `Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringifyType(directiveType)}.`),
directiveType); directiveType);
} }
return dirMeta; return dirMeta;
@ -309,8 +312,8 @@ export class CompileMetadataResolver {
<cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive); <cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
if (!dirSummary) { if (!dirSummary) {
this._reportError( this._reportError(
new Error( new SyntaxError(
`Illegal state: Could not load the summary for directive ${stringify(dirType)}.`), `Illegal state: Could not load the summary for directive ${stringifyType(dirType)}.`),
dirType); dirType);
} }
return dirSummary; return dirSummary;
@ -383,7 +386,8 @@ export class CompileMetadataResolver {
if (moduleWithProviders.providers) { if (moduleWithProviders.providers) {
providers.push(...this._getProvidersMetadata( providers.push(...this._getProvidersMetadata(
moduleWithProviders.providers, entryComponents, moduleWithProviders.providers, entryComponents,
`provider for the NgModule '${stringify(importedModuleType)}'`, [], importedType)); `provider for the NgModule '${stringifyType(importedModuleType)}'`, [],
importedType));
} }
} }
@ -391,16 +395,16 @@ export class CompileMetadataResolver {
const importedModuleSummary = this.getNgModuleSummary(importedModuleType); const importedModuleSummary = this.getNgModuleSummary(importedModuleType);
if (!importedModuleSummary) { if (!importedModuleSummary) {
this._reportError( this._reportError(
new Error( new SyntaxError(
`Unexpected ${this._getTypeDescriptor(importedType)} '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`), `Unexpected ${this._getTypeDescriptor(importedType)} '${stringifyType(importedType)}' imported by the module '${stringifyType(moduleType)}'`),
moduleType); moduleType);
return; return;
} }
importedModules.push(importedModuleSummary); importedModules.push(importedModuleSummary);
} else { } else {
this._reportError( this._reportError(
new Error( new SyntaxError(
`Unexpected value '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`), `Unexpected value '${stringifyType(importedType)}' imported by the module '${stringifyType(moduleType)}'`),
moduleType); moduleType);
return; return;
} }
@ -411,8 +415,8 @@ export class CompileMetadataResolver {
flattenAndDedupeArray(meta.exports).forEach((exportedType) => { flattenAndDedupeArray(meta.exports).forEach((exportedType) => {
if (!isValidType(exportedType)) { if (!isValidType(exportedType)) {
this._reportError( this._reportError(
new Error( new SyntaxError(
`Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`), `Unexpected value '${stringifyType(exportedType)}' exported by the module '${stringifyType(moduleType)}'`),
moduleType); moduleType);
return; return;
} }
@ -432,8 +436,8 @@ export class CompileMetadataResolver {
flattenAndDedupeArray(meta.declarations).forEach((declaredType) => { flattenAndDedupeArray(meta.declarations).forEach((declaredType) => {
if (!isValidType(declaredType)) { if (!isValidType(declaredType)) {
this._reportError( this._reportError(
new Error( new SyntaxError(
`Unexpected value '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`), `Unexpected value '${stringifyType(declaredType)}' declared by the module '${stringifyType(moduleType)}'`),
moduleType); moduleType);
return; return;
} }
@ -449,8 +453,8 @@ export class CompileMetadataResolver {
this._addTypeToModule(declaredType, moduleType); this._addTypeToModule(declaredType, moduleType);
} else { } else {
this._reportError( this._reportError(
new Error( new SyntaxError(
`Unexpected ${this._getTypeDescriptor(declaredType)} '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`), `Unexpected ${this._getTypeDescriptor(declaredType)} '${stringifyType(declaredType)}' declared by the module '${stringifyType(moduleType)}'`),
moduleType); moduleType);
return; return;
} }
@ -468,8 +472,8 @@ export class CompileMetadataResolver {
transitiveModule.addExportedPipe(exportedId); transitiveModule.addExportedPipe(exportedId);
} else { } else {
this._reportError( this._reportError(
new Error( new SyntaxError(
`Can't export ${this._getTypeDescriptor(exportedId.reference)} ${stringify(exportedId.reference)} from ${stringify(moduleType)} as it was neither declared nor imported!`), `Can't export ${this._getTypeDescriptor(exportedId.reference)} ${stringifyType(exportedId.reference)} from ${stringifyType(moduleType)} as it was neither declared nor imported!`),
moduleType); moduleType);
} }
}); });
@ -478,25 +482,25 @@ export class CompileMetadataResolver {
// so that they overwrite any other provider we already added. // so that they overwrite any other provider we already added.
if (meta.providers) { if (meta.providers) {
providers.push(...this._getProvidersMetadata( providers.push(...this._getProvidersMetadata(
meta.providers, entryComponents, `provider for the NgModule '${stringify(moduleType)}'`, meta.providers, entryComponents,
[], moduleType)); `provider for the NgModule '${stringifyType(moduleType)}'`, [], moduleType));
} }
if (meta.entryComponents) { if (meta.entryComponents) {
entryComponents.push( entryComponents.push(...flattenAndDedupeArray(meta.entryComponents)
...flattenAndDedupeArray(meta.entryComponents).map(type => this._getTypeMetadata(type))); .map(type => this._getIdentifierMetadata(type)));
} }
if (meta.bootstrap) { if (meta.bootstrap) {
flattenAndDedupeArray(meta.bootstrap).forEach(type => { flattenAndDedupeArray(meta.bootstrap).forEach(type => {
if (!isValidType(type)) { if (!isValidType(type)) {
this._reportError( this._reportError(
new Error( new SyntaxError(
`Unexpected value '${stringify(type)}' used in the bootstrap property of module '${stringify(moduleType)}'`), `Unexpected value '${stringifyType(type)}' used in the bootstrap property of module '${stringifyType(moduleType)}'`),
moduleType); moduleType);
return; return;
} }
bootstrapComponents.push(this._getTypeMetadata(type)); bootstrapComponents.push(this._getIdentifierMetadata(type));
}); });
} }
@ -554,10 +558,10 @@ export class CompileMetadataResolver {
const oldModule = this._ngModuleOfTypes.get(type); const oldModule = this._ngModuleOfTypes.get(type);
if (oldModule && oldModule !== moduleType) { if (oldModule && oldModule !== moduleType) {
this._reportError( this._reportError(
new Error( new SyntaxError(
`Type ${stringify(type)} is part of the declarations of 2 modules: ${stringify(oldModule)} and ${stringify(moduleType)}! ` + `Type ${stringifyType(type)} is part of the declarations of 2 modules: ${stringifyType(oldModule)} and ${stringifyType(moduleType)}! ` +
`Please consider moving ${stringify(type)} to a higher module that imports ${stringify(oldModule)} and ${stringify(moduleType)}. ` + `Please consider moving ${stringifyType(type)} to a higher module that imports ${stringifyType(oldModule)} and ${stringifyType(moduleType)}. ` +
`You can also create a new NgModule that exports and includes ${stringify(type)} then import that NgModule in ${stringify(oldModule)} and ${stringify(moduleType)}.`), `You can also create a new NgModule that exports and includes ${stringifyType(type)} then import that NgModule in ${stringifyType(oldModule)} and ${stringifyType(moduleType)}.`),
moduleType); moduleType);
} }
this._ngModuleOfTypes.set(type, moduleType); this._ngModuleOfTypes.set(type, moduleType);
@ -606,6 +610,26 @@ export class CompileMetadataResolver {
return {reference: type}; return {reference: type};
} }
isInjectable(type: any): boolean {
const annotations = this._reflector.annotations(type);
// Note: We need an exact check here as @Component / @Directive / ... inherit
// from @CompilerInjectable!
return annotations.some(ann => ann.constructor === Injectable);
}
getInjectableSummary(type: any): cpl.CompileTypeSummary {
return {summaryKind: cpl.CompileSummaryKind.Injectable, type: this._getTypeMetadata(type)};
}
private _getInjectableMetadata(type: Type<any>, dependencies: any[] = null):
cpl.CompileTypeMetadata {
const typeSummary = this._loadSummary(type, cpl.CompileSummaryKind.Injectable);
if (typeSummary) {
return typeSummary.type;
}
return this._getTypeMetadata(type, dependencies);
}
private _getTypeMetadata(type: Type<any>, dependencies: any[] = null): cpl.CompileTypeMetadata { private _getTypeMetadata(type: Type<any>, dependencies: any[] = null): cpl.CompileTypeMetadata {
const identifier = this._getIdentifierMetadata(type); const identifier = this._getIdentifierMetadata(type);
return { return {
@ -630,8 +654,8 @@ export class CompileMetadataResolver {
const pipeMeta = this._pipeCache.get(pipeType); const pipeMeta = this._pipeCache.get(pipeType);
if (!pipeMeta) { if (!pipeMeta) {
this._reportError( this._reportError(
new Error( new SyntaxError(
`Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringify(pipeType)}.`), `Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringifyType(pipeType)}.`),
pipeType); pipeType);
} }
return pipeMeta; return pipeMeta;
@ -642,7 +666,8 @@ export class CompileMetadataResolver {
<cpl.CompilePipeSummary>this._loadSummary(pipeType, cpl.CompileSummaryKind.Pipe); <cpl.CompilePipeSummary>this._loadSummary(pipeType, cpl.CompileSummaryKind.Pipe);
if (!pipeSummary) { if (!pipeSummary) {
this._reportError( this._reportError(
new Error(`Illegal state: Could not load the summary for pipe ${stringify(pipeType)}.`), new SyntaxError(
`Illegal state: Could not load the summary for pipe ${stringifyType(pipeType)}.`),
pipeType); pipeType);
} }
return pipeSummary; return pipeSummary;
@ -722,9 +747,10 @@ export class CompileMetadataResolver {
if (hasUnknownDeps) { if (hasUnknownDeps) {
const depsTokens = const depsTokens =
dependenciesMetadata.map((dep) => dep ? stringify(dep.token) : '?').join(', '); dependenciesMetadata.map((dep) => dep ? stringifyType(dep.token) : '?').join(', ');
this._reportError( this._reportError(
new Error(`Can't resolve all parameters for ${stringify(typeOrFunc)}: (${depsTokens}).`), new SyntaxError(
`Can't resolve all parameters for ${stringifyType(typeOrFunc)}: (${depsTokens}).`),
typeOrFunc); typeOrFunc);
} }
@ -761,9 +787,9 @@ export class CompileMetadataResolver {
(<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(`${stringifyType(seenProvider)}`);
} else if (seenProviderIdx == providerIdx) { } else if (seenProviderIdx == providerIdx) {
soFar.push(`?${stringify(seenProvider)}?`); soFar.push(`?${stringifyType(seenProvider)}?`);
} else if (seenProviderIdx == providerIdx + 1) { } else if (seenProviderIdx == providerIdx + 1) {
soFar.push('...'); soFar.push('...');
} }
@ -772,7 +798,7 @@ export class CompileMetadataResolver {
[])) []))
.join(', '); .join(', ');
this._reportError( this._reportError(
new Error( new SyntaxError(
`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}]`),
type); type);
} }
@ -793,19 +819,21 @@ export class CompileMetadataResolver {
if (provider.useFactory || provider.useExisting || provider.useClass) { if (provider.useFactory || provider.useExisting || provider.useClass) {
this._reportError( this._reportError(
new Error(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports useValue!`), type); new SyntaxError(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports useValue!`), type);
return []; return [];
} }
if (!provider.multi) { if (!provider.multi) {
this._reportError( this._reportError(
new Error(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports 'multi = true'!`), type); new SyntaxError(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports 'multi = true'!`),
type);
return []; return [];
} }
extractIdentifiers(provider.useValue, collectedIdentifiers); extractIdentifiers(provider.useValue, collectedIdentifiers);
collectedIdentifiers.forEach((identifier) => { collectedIdentifiers.forEach((identifier) => {
if (this._directiveResolver.isDirective(identifier.reference)) { if (this._directiveResolver.isDirective(identifier.reference) ||
this._loadSummary(identifier.reference, cpl.CompileSummaryKind.Directive)) {
components.push(identifier); components.push(identifier);
} }
}); });
@ -819,7 +847,7 @@ export class CompileMetadataResolver {
let token: cpl.CompileTokenMetadata = this._getTokenMetadata(provider.token); let token: cpl.CompileTokenMetadata = this._getTokenMetadata(provider.token);
if (provider.useClass) { if (provider.useClass) {
compileTypeMetadata = this._getTypeMetadata(provider.useClass, provider.dependencies); compileTypeMetadata = this._getInjectableMetadata(provider.useClass, provider.dependencies);
compileDeps = compileTypeMetadata.diDeps; compileDeps = compileTypeMetadata.diDeps;
if (provider.token === provider.useClass) { if (provider.token === provider.useClass) {
// use the compileTypeMetadata as it contains information about lifecycleHooks... // use the compileTypeMetadata as it contains information about lifecycleHooks...
@ -867,8 +895,8 @@ export class CompileMetadataResolver {
} else { } else {
if (!q.selector) { if (!q.selector) {
this._reportError( this._reportError(
new Error( new SyntaxError(
`Can't construct a query for the property "${propertyName}" of "${stringify(typeOrFunc)}" since the query selector wasn't defined.`), `Can't construct a query for the property "${propertyName}" of "${stringifyType(typeOrFunc)}" since the query selector wasn't defined.`),
typeOrFunc); typeOrFunc);
} }
selectors = [this._getTokenMetadata(q.selector)]; selectors = [this._getTokenMetadata(q.selector)];
@ -935,8 +963,8 @@ export function componentModuleUrl(
const scheme = getUrlScheme(moduleId); const scheme = getUrlScheme(moduleId);
return scheme ? moduleId : `package:${moduleId}${MODULE_SUFFIX}`; return scheme ? moduleId : `package:${moduleId}${MODULE_SUFFIX}`;
} else if (moduleId !== null && moduleId !== void 0) { } else if (moduleId !== null && moduleId !== void 0) {
throw new Error( throw new SyntaxError(
`moduleId should be a string in "${stringify(type)}". See https://goo.gl/wIDDiL for more information.\n` + `moduleId should be a string in "${stringifyType(type)}". See https://goo.gl/wIDDiL for more information.\n` +
`If you're using Webpack you should inline the template and the styles, see https://goo.gl/X2J8zc.`); `If you're using Webpack you should inline the template and the styles, see https://goo.gl/X2J8zc.`);
} }
@ -952,3 +980,11 @@ class _CompileValueConverter extends ValueTransformer {
targetIdentifiers.push({reference: value}); targetIdentifiers.push({reference: value});
} }
} }
function stringifyType(type: any): string {
if (type instanceof StaticSymbol) {
return `${type.name} in ${type.filePath}`;
} else {
return stringify(type);
}
}

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 {Injectable} from '@angular/core'; import {CompilerInjectable} from '../injectable';
import {getHtmlTagDefinition} from './html_tags'; import {getHtmlTagDefinition} from './html_tags';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation_config'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation_config';
@ -14,7 +14,7 @@ import {ParseTreeResult, Parser} from './parser';
export {ParseTreeResult, TreeError} from './parser'; export {ParseTreeResult, TreeError} from './parser';
@Injectable() @CompilerInjectable()
export class HtmlParser extends Parser { export class HtmlParser extends Parser {
constructor() { super(getHtmlTagDefinition); } constructor() { super(getHtmlTagDefinition); }

View File

@ -30,9 +30,9 @@ const PLURAL_CASES: string[] = ['zero', 'one', 'two', 'few', 'many', 'other'];
* *
* ``` * ```
* <ng-container [ngPlural]="messages.length"> * <ng-container [ngPlural]="messages.length">
* <template ngPluralCase="=0">zero</ng-container> * <template ngPluralCase="=0">zero</template>
* <template ngPluralCase="=1">one</ng-container> * <template ngPluralCase="=1">one</template>
* <template ngPluralCase="other">more than one</ng-container> * <template ngPluralCase="other">more than one</template>
* </ng-container> * </ng-container>
* ``` * ```
*/ */

View File

@ -133,7 +133,7 @@ class _Tokenizer {
} else { } else {
this._consumeTagOpen(start); this._consumeTagOpen(start);
} }
} else if (!this._tokenizeIcu || !this._tokenizeExpansionForm()) { } else if (!(this._tokenizeIcu && this._tokenizeExpansionForm())) {
this._consumeText(); this._consumeText();
} }
} catch (e) { } catch (e) {
@ -586,8 +586,8 @@ class _Tokenizer {
parts.push(this._interpolationConfig.start); parts.push(this._interpolationConfig.start);
this._inInterpolation = true; this._inInterpolation = true;
} else if ( } else if (
this._interpolationConfig && this._attemptStr(this._interpolationConfig.end) && this._interpolationConfig && this._inInterpolation &&
this._inInterpolation) { this._attemptStr(this._interpolationConfig.end)) {
parts.push(this._interpolationConfig.end); parts.push(this._interpolationConfig.end);
this._inInterpolation = false; this._inInterpolation = false;
} else { } else {

View File

@ -6,12 +6,11 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Injectable} from '@angular/core';
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenName, tokenReference} from './compile_metadata'; import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenName, tokenReference} from './compile_metadata';
import {createDiTokenExpression} from './compiler_util/identifier_util'; import {createDiTokenExpression} from './compiler_util/identifier_util';
import {isPresent} from './facade/lang'; import {isPresent} from './facade/lang';
import {Identifiers, createIdentifier, createIdentifierToken, resolveIdentifier} from './identifiers'; import {Identifiers, createIdentifier, createIdentifierToken, resolveIdentifier} from './identifiers';
import {CompilerInjectable} from './injectable';
import {ClassBuilder, createClassStmt} from './output/class_builder'; 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';
@ -31,7 +30,7 @@ export class NgModuleCompileResult {
public dependencies: ComponentFactoryDependency[]) {} public dependencies: ComponentFactoryDependency[]) {}
} }
@Injectable() @CompilerInjectable()
export class NgModuleCompiler { export class NgModuleCompiler {
compile(ngModuleMeta: CompileNgModuleMetadata, extraProviders: CompileProviderMetadata[]): compile(ngModuleMeta: CompileNgModuleMetadata, extraProviders: CompileProviderMetadata[]):
NgModuleCompileResult { NgModuleCompileResult {

View File

@ -6,10 +6,11 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Injectable, NgModule, Type} from '@angular/core'; import {NgModule, Type} from '@angular/core';
import {ListWrapper} from './facade/collection'; import {ListWrapper} from './facade/collection';
import {isPresent, stringify} from './facade/lang'; import {isPresent, stringify} from './facade/lang';
import {CompilerInjectable} from './injectable';
import {ReflectorReader, reflector} from './private_import_core'; import {ReflectorReader, reflector} from './private_import_core';
function _isNgModuleMetadata(obj: any): obj is NgModule { function _isNgModuleMetadata(obj: any): obj is NgModule {
@ -19,7 +20,7 @@ function _isNgModuleMetadata(obj: any): obj is NgModule {
/** /**
* Resolves types to {@link NgModule}. * Resolves types to {@link NgModule}.
*/ */
@Injectable() @CompilerInjectable()
export class NgModuleResolver { export class NgModuleResolver {
constructor(private _reflector: ReflectorReader = reflector) {} constructor(private _reflector: ReflectorReader = reflector) {}

View File

@ -14,5 +14,6 @@ export abstract class ImportResolver {
* Converts a file path to a module name that can be used as an `import. * Converts a file path to a module name that can be used as an `import.
* I.e. `path/to/importedFile.ts` should be imported by `path/to/containingFile.ts`. * I.e. `path/to/importedFile.ts` should be imported by `path/to/containingFile.ts`.
*/ */
abstract fileNameToModuleName(importedFilePath: string, containingFilePath: string): string; abstract fileNameToModuleName(importedFilePath: string, containingFilePath: string): string
/*|null*/;
} }

View File

@ -335,7 +335,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor
} }
ctx.print(`${prefix}.`); ctx.print(`${prefix}.`);
} }
if (value.reference && value.reference.members) { if (value.reference && value.reference.members && value.reference.members.length) {
ctx.print(value.reference.name); ctx.print(value.reference.name);
ctx.print('.'); ctx.print('.');
ctx.print(value.reference.members.join('.')); ctx.print(value.reference.members.join('.'));

View File

@ -6,10 +6,11 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Injectable, Pipe, Type, resolveForwardRef} from '@angular/core'; import {Pipe, Type, resolveForwardRef} from '@angular/core';
import {ListWrapper} from './facade/collection'; import {ListWrapper} from './facade/collection';
import {isPresent, stringify} from './facade/lang'; import {isPresent, stringify} from './facade/lang';
import {CompilerInjectable} from './injectable';
import {ReflectorReader, reflector} from './private_import_core'; import {ReflectorReader, reflector} from './private_import_core';
function _isPipeMetadata(type: any): boolean { function _isPipeMetadata(type: any): boolean {
@ -23,7 +24,7 @@ function _isPipeMetadata(type: any): boolean {
* *
* See {@link Compiler} * See {@link Compiler}
*/ */
@Injectable() @CompilerInjectable()
export class PipeResolver { export class PipeResolver {
constructor(private _reflector: ReflectorReader = reflector) {} constructor(private _reflector: ReflectorReader = reflector) {}

View File

@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AUTO_STYLE, CUSTOM_ELEMENTS_SCHEMA, Injectable, NO_ERRORS_SCHEMA, SchemaMetadata, SecurityContext} from '@angular/core'; import {AUTO_STYLE, CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata, SecurityContext} from '@angular/core';
import {CompilerInjectable} from '../injectable';
import {dashCaseToCamelCase} from '../util'; import {dashCaseToCamelCase} from '../util';
@ -238,7 +239,7 @@ const _ATTR_TO_PROP: {[name: string]: string} = {
'tabindex': 'tabIndex', 'tabindex': 'tabIndex',
}; };
@Injectable() @CompilerInjectable()
export class DomElementSchemaRegistry extends ElementSchemaRegistry { export class DomElementSchemaRegistry extends ElementSchemaRegistry {
private _schema: {[element: string]: {[property: string]: string}} = {}; private _schema: {[element: string]: {[property: string]: string}} = {};

View File

@ -6,9 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Injectable, ViewEncapsulation} from '@angular/core'; import {ViewEncapsulation} from '@angular/core';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileStylesheetMetadata, identifierModuleUrl, identifierName} from './compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileStylesheetMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
import {CompilerInjectable} from './injectable';
import * as o from './output/output_ast'; import * as o from './output/output_ast';
import {ShadowCss} from './shadow_css'; import {ShadowCss} from './shadow_css';
import {UrlResolver} from './url_resolver'; import {UrlResolver} from './url_resolver';
@ -36,7 +37,7 @@ export class CompiledStylesheet {
public meta: CompileStylesheetMetadata) {} public meta: CompileStylesheetMetadata) {}
} }
@Injectable() @CompilerInjectable()
export class StyleCompiler { export class StyleCompiler {
private _shadowCss: ShadowCss = new ShadowCss(); private _shadowCss: ShadowCss = new ShadowCss();

View File

@ -5,10 +5,17 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {Injectable} from '@angular/core';
import {CompileTypeSummary} from './compile_metadata'; import {CompileTypeSummary} from './compile_metadata';
import {CompilerInjectable} from './injectable';
@Injectable() export interface Summary<T> {
export class SummaryResolver { symbol: T;
resolveSummary(reference: any): CompileTypeSummary { return null; } metadata: any;
type?: CompileTypeSummary;
}
@CompilerInjectable()
export class SummaryResolver<T> {
resolveSummary(reference: T): Summary<T> { return null; };
getSymbolsOf(filePath: string): T[] { return []; }
} }

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Inject, Injectable, OpaqueToken, Optional, SchemaMetadata} from '@angular/core'; import {Inject, OpaqueToken, Optional, SchemaMetadata} from '@angular/core';
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileTemplateSummary, CompileTokenMetadata, CompileTypeMetadata, identifierName} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileTemplateSummary, CompileTokenMetadata, CompileTypeMetadata, identifierName} from '../compile_metadata';
import {Parser} from '../expression_parser/parser'; import {Parser} from '../expression_parser/parser';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {I18NHtmlParser} from '../i18n/i18n_html_parser'; import {I18NHtmlParser} from '../i18n/i18n_html_parser';
import {Identifiers, createIdentifierToken, identifierToken} from '../identifiers'; import {Identifiers, createIdentifierToken, identifierToken} from '../identifiers';
import {CompilerInjectable} from '../injectable';
import * as html from '../ml_parser/ast'; import * as html from '../ml_parser/ast';
import {ParseTreeResult} from '../ml_parser/html_parser'; import {ParseTreeResult} from '../ml_parser/html_parser';
import {expandNodes} from '../ml_parser/icu_ast_expander'; import {expandNodes} from '../ml_parser/icu_ast_expander';
@ -24,7 +24,7 @@ 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 {SyntaxError} from '../util';
import {BindingParser, BoundProperty} from './binding_parser'; 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';
@ -79,7 +79,7 @@ export class TemplateParseResult {
constructor(public templateAst?: TemplateAst[], public errors?: ParseError[]) {} constructor(public templateAst?: TemplateAst[], public errors?: ParseError[]) {}
} }
@Injectable() @CompilerInjectable()
export class TemplateParser { export class TemplateParser {
constructor( constructor(
private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry, private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry,
@ -99,7 +99,7 @@ export class TemplateParser {
if (errors.length > 0) { if (errors.length > 0) {
const errorString = errors.join('\n'); const errorString = errors.join('\n');
throw new Error(`Template parse errors:\n${errorString}`); throw new SyntaxError(`Template parse errors:\n${errorString}`);
} }
return result.templateAst; return result.templateAst;

View File

@ -6,9 +6,11 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Inject, Injectable, PACKAGE_ROOT_URL} from '@angular/core'; import {Inject, PACKAGE_ROOT_URL} from '@angular/core';
import {isBlank, isPresent} from './facade/lang'; import {isBlank, isPresent} from './facade/lang';
import {CompilerInjectable} from './injectable';
/** /**
* Create a {@link UrlResolver} with no package prefix. * Create a {@link UrlResolver} with no package prefix.
@ -45,7 +47,7 @@ export var DEFAULT_PACKAGE_URL_PROVIDER = {
* Attacker-controlled data introduced by a template could expose your * Attacker-controlled data introduced by a template could expose your
* application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security). * application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security).
*/ */
@Injectable() @CompilerInjectable()
export class UrlResolver { export class UrlResolver {
constructor(@Inject(PACKAGE_ROOT_URL) private _packagePrefix: string = null) {} constructor(@Inject(PACKAGE_ROOT_URL) private _packagePrefix: string = null) {}

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {BaseError} from './facade/errors';
import {isPrimitive, isStrictStringMap} from './facade/lang'; import {isPrimitive, isStrictStringMap} from './facade/lang';
export const MODULE_SUFFIX = ''; export const MODULE_SUFFIX = '';
const CAMEL_CASE_REGEXP = /([A-Z])/g; const CAMEL_CASE_REGEXP = /([A-Z])/g;
@ -78,3 +78,5 @@ export class SyncAsyncResult<T> {
} }
} }
} }
export class SyntaxError extends BaseError {}

View File

@ -576,7 +576,8 @@ function generateDetectChangesMethod(view: CompileView): o.Statement[] {
} }
stmts.push(...view.detectChangesRenderPropertiesMethod.finish()); stmts.push(...view.detectChangesRenderPropertiesMethod.finish());
view.viewChildren.forEach((viewChild) => { view.viewChildren.forEach((viewChild) => {
stmts.push(viewChild.callMethod('internalDetectChanges', [DetectChangesVars.throwOnChange]).toStmt()); stmts.push(
viewChild.callMethod('internalDetectChanges', [DetectChangesVars.throwOnChange]).toStmt());
}); });
const afterViewStmts = const afterViewStmts =
view.updateViewQueriesMethod.finish().concat(view.afterViewLifecycleCallbacksMethod.finish()); view.updateViewQueriesMethod.finish().concat(view.afterViewLifecycleCallbacksMethod.finish());

View File

@ -6,11 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Injectable} from '@angular/core';
import {AnimationEntryCompileResult} from '../animation/animation_compiler'; import {AnimationEntryCompileResult} from '../animation/animation_compiler';
import {CompileDirectiveMetadata, CompilePipeSummary} from '../compile_metadata'; import {CompileDirectiveMetadata, CompilePipeSummary} from '../compile_metadata';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
import {CompilerInjectable} from '../injectable';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {TemplateAst} from '../template_parser/template_ast'; import {TemplateAst} from '../template_parser/template_ast';
@ -30,7 +29,7 @@ export class ViewCompileResult {
Array<ViewClassDependency|ComponentFactoryDependency|DirectiveWrapperDependency>) {} Array<ViewClassDependency|ComponentFactoryDependency|DirectiveWrapperDependency>) {}
} }
@Injectable() @CompilerInjectable()
export class ViewCompiler { export class ViewCompiler {
constructor(private _genConfig: CompilerConfig, private _schemaRegistry: ElementSchemaRegistry) {} constructor(private _genConfig: CompilerConfig, private _schemaRegistry: ElementSchemaRegistry) {}

View File

@ -6,26 +6,25 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler'; import {StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost} from '@angular/compiler';
import {HostListener, Inject, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; import {HostListener, Inject, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {MetadataCollector} from '@angular/tsc-wrapped';
import * as ts from 'typescript';
import {MockStaticSymbolResolverHost, MockSummaryResolver} from './static_symbol_resolver_spec';
// This matches .ts files but not .d.ts files.
const TS_EXT = /(^.|(?!\.d)..)\.ts$/;
describe('StaticReflector', () => { describe('StaticReflector', () => {
const noContext = new StaticSymbol('', ''); let noContext: StaticSymbol;
let host: StaticReflectorHost; let host: StaticSymbolResolverHost;
let symbolResolver: StaticSymbolResolver;
let reflector: StaticReflector; let reflector: StaticReflector;
function init( function init(
testData: {[key: string]: any} = DEFAULT_TEST_DATA, testData: {[key: string]: any} = DEFAULT_TEST_DATA,
decorators: {name: string, filePath: string, ctor: any}[] = []) { decorators: {name: string, filePath: string, ctor: any}[] = []) {
host = new MockStaticReflectorHost(testData); const symbolCache = new StaticSymbolCache();
reflector = new StaticReflector(host, undefined, decorators); host = new MockStaticSymbolResolverHost(testData);
symbolResolver = new StaticSymbolResolver(host, symbolCache, new MockSummaryResolver([]));
reflector = new StaticReflector(symbolResolver, decorators);
noContext = reflector.getStaticSymbol('', '');
} }
beforeEach(() => init()); beforeEach(() => init());
@ -77,24 +76,22 @@ describe('StaticReflector', () => {
])]); ])]);
}); });
it('should throw an exception for unsupported metadata versions', () => {
expect(() => reflector.findDeclaration('src/version-error', 'e'))
.toThrow(new Error(
'Metadata version mismatch for module /tmp/src/version-error.d.ts, found version 100, expected 3'));
});
it('should throw an exception for version 2 metadata', () => {
expect(() => reflector.findDeclaration('src/version-2-error', 'e'))
.toThrowError(
'Unsupported metadata version 2 for module /tmp/src/version-2-error.d.ts. This module should be compiled with a newer version of ngc');
});
it('should get and empty annotation list for an unknown class', () => { it('should get and empty annotation list for an unknown class', () => {
const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass'); const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass');
const annotations = reflector.annotations(UnknownClass); const annotations = reflector.annotations(UnknownClass);
expect(annotations).toEqual([]); expect(annotations).toEqual([]);
}); });
it('should get and empty annotation list for a symbol with null value', () => {
init({
'/tmp/test.ts': `
export var x = null;
`
});
const annotations = reflector.annotations(reflector.getStaticSymbol('/tmp/test.ts', 'x'));
expect(annotations).toEqual([]);
});
it('should get propMetadata for HeroDetailComponent', () => { it('should get propMetadata for HeroDetailComponent', () => {
const HeroDetailComponent = const HeroDetailComponent =
reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent'); reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
@ -129,7 +126,7 @@ describe('StaticReflector', () => {
}); });
it('should simplify a static symbol into itself', () => { it('should simplify a static symbol into itself', () => {
const staticSymbol = new StaticSymbol('', ''); const staticSymbol = reflector.getStaticSymbol('', '');
expect(simplify(noContext, staticSymbol)).toBe(staticSymbol); expect(simplify(noContext, staticSymbol)).toBe(staticSymbol);
}); });
@ -306,49 +303,43 @@ describe('StaticReflector', () => {
expect(simplify(noContext, expr)).toBe(2); expect(simplify(noContext, expr)).toBe(2);
}); });
it('should simplify a module reference', () => { it('should simplify a file reference', () => {
expect(simplify( expect(simplify(
new StaticSymbol('/src/cases', ''), reflector.getStaticSymbol('/src/cases', ''),
({__symbolic: 'reference', module: './extern', name: 's'}))) reflector.getStaticSymbol('/src/extern.d.ts', 's')))
.toEqual('s'); .toEqual('s');
}); });
it('should not simplify a module reference without a name', () => {
const staticSymbol = new StaticSymbol('/src/cases', '');
expect(simplify(staticSymbol, ({__symbolic: 'reference', module: './extern', name: ''})))
.toEqual(staticSymbol);
});
it('should simplify a non existing reference as a static symbol', () => { it('should simplify a non existing reference as a static symbol', () => {
expect(simplify( expect(simplify(
new StaticSymbol('/src/cases', ''), reflector.getStaticSymbol('/src/cases', ''),
({__symbolic: 'reference', module: './extern', name: 'nonExisting'}))) reflector.getStaticSymbol('/src/extern.d.ts', 'nonExisting')))
.toEqual(reflector.getStaticSymbol('/src/extern.d.ts', 'nonExisting')); .toEqual(reflector.getStaticSymbol('/src/extern.d.ts', 'nonExisting'));
}); });
it('should simplify a function reference as a static symbol', () => { it('should simplify a function reference as a static symbol', () => {
expect(simplify( expect(simplify(
new StaticSymbol('/src/cases', 'myFunction'), reflector.getStaticSymbol('/src/cases', 'myFunction'),
({__symbolic: 'function', parameters: ['a'], value: []}))) ({__symbolic: 'function', parameters: ['a'], value: []})))
.toEqual(reflector.getStaticSymbol('/src/cases', 'myFunction')); .toEqual(reflector.getStaticSymbol('/src/cases', 'myFunction'));
}); });
it('should simplify values initialized with a function call', () => { it('should simplify values initialized with a function call', () => {
expect(simplify(new StaticSymbol('/tmp/src/function-reference.ts', ''), { expect(simplify(
__symbolic: 'reference', reflector.getStaticSymbol('/tmp/src/function-reference.ts', ''),
name: 'one' reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'one')))
})).toEqual(['some-value']); .toEqual(['some-value']);
expect(simplify(new StaticSymbol('/tmp/src/function-reference.ts', ''), { expect(simplify(
__symbolic: 'reference', reflector.getStaticSymbol('/tmp/src/function-reference.ts', ''),
name: 'three' reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'three')))
})).toEqual(3); .toEqual(3);
}); });
it('should error on direct recursive calls', () => { it('should error on direct recursive calls', () => {
expect( expect(
() => simplify( () => simplify(
new StaticSymbol('/tmp/src/function-reference.ts', ''), reflector.getStaticSymbol('/tmp/src/function-reference.ts', ''),
{__symbolic: 'reference', name: 'recursion'})) reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'recursion')))
.toThrow(new Error( .toThrow(new Error(
'Recursion not supported, resolving symbol recursive in /tmp/src/function-recursive.d.ts, resolving symbol recursion in /tmp/src/function-reference.ts, resolving symbol in /tmp/src/function-reference.ts')); 'Recursion not supported, resolving symbol recursive in /tmp/src/function-recursive.d.ts, resolving symbol recursion in /tmp/src/function-reference.ts, resolving symbol in /tmp/src/function-reference.ts'));
}); });
@ -362,7 +353,8 @@ describe('StaticReflector', () => {
expect(moduleMetadata).toBeDefined(); expect(moduleMetadata).toBeDefined();
const classData: any = moduleMetadata['InvalidMetadata']; const classData: any = moduleMetadata['InvalidMetadata'];
expect(classData).toBeDefined(); expect(classData).toBeDefined();
simplify(new StaticSymbol('/tmp/src/invalid-metadata.ts', ''), classData.decorators[0]); simplify(
reflector.getStaticSymbol('/tmp/src/invalid-metadata.ts', ''), classData.decorators[0]);
} catch (e) { } catch (e) {
expect(e.fileName).toBe('/tmp/src/invalid-metadata.ts'); expect(e.fileName).toBe('/tmp/src/invalid-metadata.ts');
threw = true; threw = true;
@ -373,48 +365,17 @@ describe('StaticReflector', () => {
it('should error on indirect recursive calls', () => { it('should error on indirect recursive calls', () => {
expect( expect(
() => simplify( () => simplify(
new StaticSymbol('/tmp/src/function-reference.ts', ''), reflector.getStaticSymbol('/tmp/src/function-reference.ts', ''),
{__symbolic: 'reference', name: 'indirectRecursion'})) reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'indirectRecursion')))
.toThrow(new Error( .toThrow(new Error(
'Recursion not supported, resolving symbol indirectRecursion2 in /tmp/src/function-recursive.d.ts, resolving symbol indirectRecursion1 in /tmp/src/function-recursive.d.ts, resolving symbol indirectRecursion in /tmp/src/function-reference.ts, resolving symbol in /tmp/src/function-reference.ts')); 'Recursion not supported, resolving symbol indirectRecursion2 in /tmp/src/function-recursive.d.ts, resolving symbol indirectRecursion1 in /tmp/src/function-recursive.d.ts, resolving symbol indirectRecursion in /tmp/src/function-reference.ts, resolving symbol in /tmp/src/function-reference.ts'));
}); });
it('should simplify a spread expression', () => { it('should simplify a spread expression', () => {
expect(simplify(new StaticSymbol('/tmp/src/spread.ts', ''), { expect(simplify(
__symbolic: 'reference', reflector.getStaticSymbol('/tmp/src/spread.ts', ''),
name: 'spread' reflector.getStaticSymbol('/tmp/src/spread.ts', 'spread')))
})).toEqual([0, 1, 2, 3, 4, 5]); .toEqual([0, 1, 2, 3, 4, 5]);
});
it('should be able to get metadata from a ts file', () => {
const metadata = reflector.getModuleMetadata('/tmp/src/custom-decorator-reference.ts');
expect(metadata).toEqual({
__symbolic: 'module',
version: 3,
metadata: {
Foo: {
__symbolic: 'class',
decorators: [{
__symbolic: 'call',
expression:
{__symbolic: 'reference', module: './custom-decorator', name: 'CustomDecorator'}
}],
members: {
foo: [{
__symbolic: 'property',
decorators: [{
__symbolic: 'call',
expression: {
__symbolic: 'reference',
module: './custom-decorator',
name: 'CustomDecorator'
}
}]
}]
}
}
}
});
}); });
it('should be able to get metadata for a class containing a custom decorator', () => { it('should be able to get metadata for a class containing a custom decorator', () => {
@ -488,62 +449,6 @@ describe('StaticReflector', () => {
expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod'); expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod');
}); });
it('should be able to produce a symbol for an exported symbol', () => {
expect(reflector.findDeclaration('@angular/router', 'foo', 'main.ts')).toBeDefined();
});
it('should be able to produce a symbol for values space only reference', () => {
expect(reflector.findDeclaration('@angular/router/src/providers', 'foo', 'main.ts'))
.toBeDefined();
});
it('should be produce the same symbol if asked twice', () => {
const foo1 = reflector.getStaticSymbol('main.ts', 'foo');
const foo2 = reflector.getStaticSymbol('main.ts', 'foo');
expect(foo1).toBe(foo2);
});
it('should be able to produce a symbol for a module with no file',
() => { expect(reflector.getStaticSymbol('angularjs', 'SomeAngularSymbol')).toBeDefined(); });
it('should be able to trace a named export', () => {
const symbol = reflector.findDeclaration('./reexport/reexport', 'One', '/tmp/src/main.ts');
expect(symbol.name).toEqual('One');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
});
it('should be able to trace a renamed export', () => {
const symbol = reflector.findDeclaration('./reexport/reexport', 'Four', '/tmp/src/main.ts');
expect(symbol.name).toEqual('Three');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
});
it('should be able to trace an export * export', () => {
const symbol = reflector.findDeclaration('./reexport/reexport', 'Five', '/tmp/src/main.ts');
expect(symbol.name).toEqual('Five');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin5.d.ts');
});
it('should be able to trace a multi-level re-export', () => {
const symbol = reflector.findDeclaration('./reexport/reexport', 'Thirty', '/tmp/src/main.ts');
expect(symbol.name).toEqual('Thirty');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin30.d.ts');
});
it('should cache tracing a named export', () => {
const moduleNameToFileNameSpy = spyOn(host, 'moduleNameToFileName').and.callThrough();
const getMetadataForSpy = spyOn(host, 'getMetadataFor').and.callThrough();
reflector.findDeclaration('./reexport/reexport', 'One', '/tmp/src/main.ts');
moduleNameToFileNameSpy.calls.reset();
getMetadataForSpy.calls.reset();
const symbol = reflector.findDeclaration('./reexport/reexport', 'One', '/tmp/src/main.ts');
expect(moduleNameToFileNameSpy.calls.count()).toBe(1);
expect(getMetadataForSpy.calls.count()).toBe(0);
expect(symbol.name).toEqual('One');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
});
describe('inheritance', () => { describe('inheritance', () => {
class ClassDecorator { class ClassDecorator {
constructor(public value: any) {} constructor(public value: any) {}
@ -706,78 +611,6 @@ describe('StaticReflector', () => {
}); });
export class MockStaticReflectorHost implements StaticReflectorHost {
private collector = new MetadataCollector();
constructor(private data: {[key: string]: any}) {}
// In tests, assume that symbols are not re-exported
moduleNameToFileName(modulePath: string, containingFile?: string): string {
function splitPath(path: string): string[] { return path.split(/\/|\\/g); }
function resolvePath(pathParts: string[]): string {
const result: string[] = [];
pathParts.forEach((part, index) => {
switch (part) {
case '':
case '.':
if (index > 0) return;
break;
case '..':
if (index > 0 && result.length != 0) result.pop();
return;
}
result.push(part);
});
return result.join('/');
}
function pathTo(from: string, to: string): string {
let result = to;
if (to.startsWith('.')) {
const fromParts = splitPath(from);
fromParts.pop(); // remove the file name.
const toParts = splitPath(to);
result = resolvePath(fromParts.concat(toParts));
}
return result;
}
if (modulePath.indexOf('.') === 0) {
const baseName = pathTo(containingFile, modulePath);
const tsName = baseName + '.ts';
if (this._getMetadataFor(tsName)) {
return tsName;
}
return baseName + '.d.ts';
}
return '/tmp/' + modulePath + '.d.ts';
}
getMetadataFor(moduleId: string): any { return this._getMetadataFor(moduleId); }
private _getMetadataFor(moduleId: string): any {
if (this.data[moduleId] && moduleId.match(TS_EXT)) {
const text = this.data[moduleId];
if (typeof text === 'string') {
const sf = ts.createSourceFile(
moduleId, this.data[moduleId], ts.ScriptTarget.ES5, /* setParentNodes */ true);
const diagnostics: ts.Diagnostic[] = (<any>sf).parseDiagnostics;
if (diagnostics && diagnostics.length) {
throw Error(`Error encountered during parse of file ${moduleId}`);
}
return [this.collector.getMetadata(sf)];
}
}
const result = this.data[moduleId];
if (result) {
return Array.isArray(result) ? result : [result];
} else {
return null;
}
}
}
const DEFAULT_TEST_DATA: {[key: string]: any} = { const DEFAULT_TEST_DATA: {[key: string]: any} = {
'/tmp/@angular/common/src/forms-deprecated/directives.d.ts': [{ '/tmp/@angular/common/src/forms-deprecated/directives.d.ts': [{
'__symbolic': 'module', '__symbolic': 'module',
@ -1008,8 +841,6 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
} }
}, },
'/src/extern.d.ts': {'__symbolic': 'module', 'version': 3, metadata: {s: 's'}}, '/src/extern.d.ts': {'__symbolic': 'module', 'version': 3, metadata: {s: 's'}},
'/tmp/src/version-error.d.ts': {'__symbolic': 'module', 'version': 100, metadata: {e: 's'}},
'/tmp/src/version-2-error.d.ts': {'__symbolic': 'module', 'version': 2, metadata: {e: 's'}},
'/tmp/src/error-reporting.d.ts': { '/tmp/src/error-reporting.d.ts': {
__symbolic: 'module', __symbolic: 'module',
version: 3, version: 3,
@ -1343,47 +1174,4 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
@Input f: Forward; @Input f: Forward;
} }
`, `,
'/tmp/src/reexport/reexport.d.ts': {
__symbolic: 'module',
version: 3,
metadata: {},
exports: [
{from: './src/origin1', export: ['One', 'Two', {name: 'Three', as: 'Four'}]},
{from: './src/origin5'}, {from: './src/reexport2'}
]
},
'/tmp/src/reexport/src/origin1.d.ts': {
__symbolic: 'module',
version: 3,
metadata: {
One: {__symbolic: 'class'},
Two: {__symbolic: 'class'},
Three: {__symbolic: 'class'},
},
},
'/tmp/src/reexport/src/origin5.d.ts': {
__symbolic: 'module',
version: 3,
metadata: {
Five: {__symbolic: 'class'},
},
},
'/tmp/src/reexport/src/origin30.d.ts': {
__symbolic: 'module',
version: 3,
metadata: {
Thirty: {__symbolic: 'class'},
},
},
'/tmp/src/reexport/src/originNone.d.ts': {
__symbolic: 'module',
version: 3,
metadata: {},
},
'/tmp/src/reexport/src/reexport2.d.ts': {
__symbolic: 'module',
version: 3,
metadata: {},
exports: [{from: './originNone'}, {from: './origin30'}]
}
}; };

View File

@ -0,0 +1,372 @@
/**
* @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 {StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost, Summary, SummaryResolver} from '@angular/compiler';
import {MetadataCollector} from '@angular/tsc-wrapped';
import * as ts from 'typescript';
// This matches .ts files but not .d.ts files.
const TS_EXT = /(^.|(?!\.d)..)\.ts$/;
describe('StaticSymbolResolver', () => {
const noContext = new StaticSymbol('', '');
let host: StaticSymbolResolverHost;
let symbolResolver: StaticSymbolResolver;
let symbolCache: StaticSymbolCache;
beforeEach(() => { symbolCache = new StaticSymbolCache(); });
function init(
testData: {[key: string]: any} = DEFAULT_TEST_DATA, summaries: Summary<StaticSymbol>[] = []) {
host = new MockStaticSymbolResolverHost(testData);
symbolResolver =
new StaticSymbolResolver(host, symbolCache, new MockSummaryResolver(summaries));
}
beforeEach(() => init());
it('should throw an exception for unsupported metadata versions', () => {
expect(
() => symbolResolver.resolveSymbol(
symbolResolver.getSymbolByModule('src/version-error', 'e')))
.toThrow(new Error(
'Metadata version mismatch for module /tmp/src/version-error.d.ts, found version 100, expected 3'));
});
it('should throw an exception for version 2 metadata', () => {
expect(
() => symbolResolver.resolveSymbol(
symbolResolver.getSymbolByModule('src/version-2-error', 'e')))
.toThrowError(
'Unsupported metadata version 2 for module /tmp/src/version-2-error.d.ts. This module should be compiled with a newer version of ngc');
});
it('should be produce the same symbol if asked twice', () => {
const foo1 = symbolResolver.getStaticSymbol('main.ts', 'foo');
const foo2 = symbolResolver.getStaticSymbol('main.ts', 'foo');
expect(foo1).toBe(foo2);
});
it('should be able to produce a symbol for a module with no file', () => {
expect(symbolResolver.getStaticSymbol('angularjs', 'SomeAngularSymbol')).toBeDefined();
});
it('should be able to split the metadata per symbol', () => {
init({
'/tmp/src/test.ts': `
export var a = 1;
export var b = 2;
`
});
expect(symbolResolver.resolveSymbol(symbolResolver.getStaticSymbol('/tmp/src/test.ts', 'a'))
.metadata)
.toBe(1);
expect(symbolResolver.resolveSymbol(symbolResolver.getStaticSymbol('/tmp/src/test.ts', 'b'))
.metadata)
.toBe(2);
});
it('should be able to resolve static symbols with members', () => {
init({
'/tmp/src/test.ts': `
export {exportedObj} from './export';
export var obj = {a: 1};
export class SomeClass {
static someField = 2;
}
`,
'/tmp/src/export.ts': `
export var exportedObj = {};
`,
});
expect(symbolResolver
.resolveSymbol(symbolResolver.getStaticSymbol('/tmp/src/test.ts', 'obj', ['a']))
.metadata)
.toBe(1);
expect(symbolResolver
.resolveSymbol(
symbolResolver.getStaticSymbol('/tmp/src/test.ts', 'SomeClass', ['someField']))
.metadata)
.toBe(2);
expect(symbolResolver
.resolveSymbol(symbolResolver.getStaticSymbol(
'/tmp/src/test.ts', 'exportedObj', ['someMember']))
.metadata)
.toBe(symbolResolver.getStaticSymbol('/tmp/src/export.ts', 'exportedObj', ['someMember']));
});
it('should use summaries in resolveSymbol and prefer them over regular metadata', () => {
const someSymbol = symbolCache.get('/test.ts', 'a');
init({'/test.ts': 'export var a = 2'}, [{symbol: someSymbol, metadata: 1}]);
expect(symbolResolver.resolveSymbol(someSymbol).metadata).toBe(1);
});
it('should be able to get all exported symbols of a file', () => {
expect(symbolResolver.getSymbolsOf('/tmp/src/reexport/src/origin1.d.ts')).toEqual([
symbolResolver.getStaticSymbol('/tmp/src/reexport/src/origin1.d.ts', 'One'),
symbolResolver.getStaticSymbol('/tmp/src/reexport/src/origin1.d.ts', 'Two'),
symbolResolver.getStaticSymbol('/tmp/src/reexport/src/origin1.d.ts', 'Three'),
]);
});
it('should be able to get all reexported symbols of a file', () => {
expect(symbolResolver.getSymbolsOf('/tmp/src/reexport/reexport.d.ts')).toEqual([
symbolResolver.getStaticSymbol('/tmp/src/reexport/reexport.d.ts', 'One'),
symbolResolver.getStaticSymbol('/tmp/src/reexport/reexport.d.ts', 'Two'),
symbolResolver.getStaticSymbol('/tmp/src/reexport/reexport.d.ts', 'Four'),
symbolResolver.getStaticSymbol('/tmp/src/reexport/reexport.d.ts', 'Five'),
symbolResolver.getStaticSymbol('/tmp/src/reexport/reexport.d.ts', 'Thirty')
]);
});
it('should merge the exported symbols of a file with the exported symbols of its summary', () => {
const someSymbol = symbolCache.get('/test.ts', 'a');
init(
{'/test.ts': 'export var b = 2'},
[{symbol: symbolCache.get('/test.ts', 'a'), metadata: 1}]);
expect(symbolResolver.getSymbolsOf('/test.ts')).toEqual([
symbolCache.get('/test.ts', 'a'), symbolCache.get('/test.ts', 'b')
]);
});
it('should replace references by StaticSymbols', () => {
init({
'/test.ts': `
import {b, y} from './test2';
export var a = b;
export var x = [y];
export function simpleFn(fnArg) {
return [a, y, fnArg];
}
`,
'/test2.ts': `
export var b;
export var y;
`
});
expect(symbolResolver.resolveSymbol(symbolCache.get('/test.ts', 'a')).metadata)
.toEqual(symbolCache.get('/test2.ts', 'b'));
expect(symbolResolver.resolveSymbol(symbolCache.get('/test.ts', 'x')).metadata).toEqual([
symbolCache.get('/test2.ts', 'y')
]);
expect(symbolResolver.resolveSymbol(symbolCache.get('/test.ts', 'simpleFn')).metadata).toEqual({
__symbolic: 'function',
parameters: ['fnArg'],
value: [
symbolCache.get('/test.ts', 'a'), symbolCache.get('/test2.ts', 'y'),
Object({__symbolic: 'reference', name: 'fnArg'})
]
});
});
it('should ignore module references without a name', () => {
init({
'/test.ts': `
import Default from './test2';
export {Default};
`
});
expect(symbolResolver.resolveSymbol(symbolCache.get('/test.ts', 'Default')).metadata)
.toBeFalsy();
});
it('should be able to trace a named export', () => {
const symbol = symbolResolver
.resolveSymbol(symbolResolver.getSymbolByModule(
'./reexport/reexport', 'One', '/tmp/src/main.ts'))
.metadata;
expect(symbol.name).toEqual('One');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
});
it('should be able to trace a renamed export', () => {
const symbol = symbolResolver
.resolveSymbol(symbolResolver.getSymbolByModule(
'./reexport/reexport', 'Four', '/tmp/src/main.ts'))
.metadata;
expect(symbol.name).toEqual('Three');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
});
it('should be able to trace an export * export', () => {
const symbol = symbolResolver
.resolveSymbol(symbolResolver.getSymbolByModule(
'./reexport/reexport', 'Five', '/tmp/src/main.ts'))
.metadata;
expect(symbol.name).toEqual('Five');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin5.d.ts');
});
it('should be able to trace a multi-level re-export', () => {
const symbol1 = symbolResolver
.resolveSymbol(symbolResolver.getSymbolByModule(
'./reexport/reexport', 'Thirty', '/tmp/src/main.ts'))
.metadata;
expect(symbol1.name).toEqual('Thirty');
expect(symbol1.filePath).toEqual('/tmp/src/reexport/src/reexport2.d.ts');
const symbol2 = symbolResolver.resolveSymbol(symbol1).metadata;
expect(symbol2.name).toEqual('Thirty');
expect(symbol2.filePath).toEqual('/tmp/src/reexport/src/origin30.d.ts');
});
it('should cache tracing a named export', () => {
const moduleNameToFileNameSpy = spyOn(host, 'moduleNameToFileName').and.callThrough();
const getMetadataForSpy = spyOn(host, 'getMetadataFor').and.callThrough();
symbolResolver.resolveSymbol(
symbolResolver.getSymbolByModule('./reexport/reexport', 'One', '/tmp/src/main.ts'));
moduleNameToFileNameSpy.calls.reset();
getMetadataForSpy.calls.reset();
const symbol = symbolResolver
.resolveSymbol(symbolResolver.getSymbolByModule(
'./reexport/reexport', 'One', '/tmp/src/main.ts'))
.metadata;
expect(moduleNameToFileNameSpy.calls.count()).toBe(1);
expect(getMetadataForSpy.calls.count()).toBe(0);
expect(symbol.name).toEqual('One');
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts');
});
});
export class MockSummaryResolver implements SummaryResolver<StaticSymbol> {
constructor(private summaries: Summary<StaticSymbol>[] = []) {}
resolveSummary(reference: StaticSymbol): Summary<StaticSymbol> {
return this.summaries.find(summary => summary.symbol === reference);
};
getSymbolsOf(filePath: string): StaticSymbol[] {
return this.summaries.filter(summary => summary.symbol.filePath === filePath)
.map(summary => summary.symbol);
}
}
export class MockStaticSymbolResolverHost implements StaticSymbolResolverHost {
private collector = new MetadataCollector();
constructor(private data: {[key: string]: any}) {}
// In tests, assume that symbols are not re-exported
moduleNameToFileName(modulePath: string, containingFile?: string): string {
function splitPath(path: string): string[] { return path.split(/\/|\\/g); }
function resolvePath(pathParts: string[]): string {
const result: string[] = [];
pathParts.forEach((part, index) => {
switch (part) {
case '':
case '.':
if (index > 0) return;
break;
case '..':
if (index > 0 && result.length != 0) result.pop();
return;
}
result.push(part);
});
return result.join('/');
}
function pathTo(from: string, to: string): string {
let result = to;
if (to.startsWith('.')) {
const fromParts = splitPath(from);
fromParts.pop(); // remove the file name.
const toParts = splitPath(to);
result = resolvePath(fromParts.concat(toParts));
}
return result;
}
if (modulePath.indexOf('.') === 0) {
const baseName = pathTo(containingFile, modulePath);
const tsName = baseName + '.ts';
if (this._getMetadataFor(tsName)) {
return tsName;
}
return baseName + '.d.ts';
}
return '/tmp/' + modulePath + '.d.ts';
}
getMetadataFor(moduleId: string): any { return this._getMetadataFor(moduleId); }
private _getMetadataFor(filePath: string): any {
if (this.data[filePath] && filePath.match(TS_EXT)) {
const text = this.data[filePath];
if (typeof text === 'string') {
const sf = ts.createSourceFile(
filePath, this.data[filePath], ts.ScriptTarget.ES5, /* setParentNodes */ true);
const diagnostics: ts.Diagnostic[] = (<any>sf).parseDiagnostics;
if (diagnostics && diagnostics.length) {
throw Error(`Error encountered during parse of file ${filePath}`);
}
return [this.collector.getMetadata(sf)];
}
}
const result = this.data[filePath];
if (result) {
return Array.isArray(result) ? result : [result];
} else {
return null;
}
}
}
const DEFAULT_TEST_DATA: {[key: string]: any} = {
'/tmp/src/version-error.d.ts': {'__symbolic': 'module', 'version': 100, metadata: {e: 's'}},
'/tmp/src/version-2-error.d.ts': {'__symbolic': 'module', 'version': 2, metadata: {e: 's'}},
'/tmp/src/reexport/reexport.d.ts': {
__symbolic: 'module',
version: 3,
metadata: {},
exports: [
{from: './src/origin1', export: ['One', 'Two', {name: 'Three', as: 'Four'}]},
{from: './src/origin5'}, {from: './src/reexport2'}
]
},
'/tmp/src/reexport/src/origin1.d.ts': {
__symbolic: 'module',
version: 3,
metadata: {
One: {__symbolic: 'class'},
Two: {__symbolic: 'class'},
Three: {__symbolic: 'class'},
},
},
'/tmp/src/reexport/src/origin5.d.ts': {
__symbolic: 'module',
version: 3,
metadata: {
Five: {__symbolic: 'class'},
},
},
'/tmp/src/reexport/src/origin30.d.ts': {
__symbolic: 'module',
version: 3,
metadata: {
Thirty: {__symbolic: 'class'},
},
},
'/tmp/src/reexport/src/originNone.d.ts': {
__symbolic: 'module',
version: 3,
metadata: {},
},
'/tmp/src/reexport/src/reexport2.d.ts': {
__symbolic: 'module',
version: 3,
metadata: {},
exports: [{from: './originNone'}, {from: './origin30'}]
}
};

View File

@ -6,128 +6,67 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AotSummaryResolver, AotSummaryResolverHost, CompileNgModuleSummary, CompileSummaryKind, CompileTypeSummary, StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler'; import {AotSummaryResolver, AotSummaryResolverHost, CompileSummaryKind, CompileTypeSummary, ResolvedStaticSymbol, StaticSymbol, StaticSymbolCache, StaticSymbolResolver} from '@angular/compiler';
import {AotSummarySerializerHost, deserializeSummaries, serializeSummaries} from '@angular/compiler/src/aot/summary_serializer';
import * as path from 'path'; import * as path from 'path';
import {MockStaticReflectorHost} from './static_reflector_spec'; import {MockStaticSymbolResolverHost, MockSummaryResolver} from './static_symbol_resolver_spec';
const EXT = /\.ts$|.d.ts$/; const EXT = /\.ts$|.d.ts$/;
export function main() { export function main() {
describe('AotSummaryResolver', () => { describe('AotSummaryResolver', () => {
let resolver: AotSummaryResolver; let summaryResolver: AotSummaryResolver;
let staticReflector: StaticReflector; let symbolCache: StaticSymbolCache;
let host: MockAotSummaryResolverHost;
beforeEach(() => { symbolCache = new StaticSymbolCache(); });
function init(summaries: {[filePath: string]: string} = {}) { function init(summaries: {[filePath: string]: string} = {}) {
// Note: We don't give the static reflector metadata files, host = new MockAotSummaryResolverHost(summaries);
// so that we can test that we can deserialize summary files summaryResolver = new AotSummaryResolver(host, symbolCache);
// without reading metadata files. This is important
// as summary files can contain references to files of transitive compilation
// dependencies, and we don't want to read their metadata files.
staticReflector = new StaticReflector(new MockStaticReflectorHost({}));
const host = new MockAotSummaryResolverHost(summaries);
resolver = new AotSummaryResolver(host, staticReflector, {excludeFilePattern: /\.d\.ts$/});
} }
it('should add .ngsummary.json to the filename', () => { function serialize(symbols: ResolvedStaticSymbol[], types: CompileTypeSummary[]): string {
init(); // Note: Don't use the top level host / summaryResolver as they might not be created yet
expect(resolver.serializeSummaries('a.ts', []).genFileUrl).toBe('a.ngsummary.json'); const mockSummaryResolver = new MockSummaryResolver([]);
expect(resolver.serializeSummaries('a.d.ts', []).genFileUrl).toBe('a.ngsummary.json'); const symbolResolver = new StaticSymbolResolver(
expect(resolver.serializeSummaries('a.js', []).genFileUrl).toBe('a.ngsummary.json'); new MockStaticSymbolResolverHost({}), symbolCache, mockSummaryResolver);
}); return serializeSummaries(
new MockAotSummarySerializerHost(), mockSummaryResolver, symbolResolver, symbols, types);
it('should serialize various data correctly', () => {
init();
const serializedData = resolver.serializeSummaries(
'/tmp/some_pipe.ts', [<any>{
summaryKind: CompileSummaryKind.Pipe,
type: {
reference: staticReflector.getStaticSymbol('/tmp/some_pipe.ts', 'SomePipe'),
},
aNumber: 1,
aString: 'hello',
anArray: [1, 2],
aStaticSymbol:
staticReflector.getStaticSymbol('/tmp/some_symbol.ts', 'someName', ['someMember'])
}]);
// Note: this creates a new staticReflector!
init({[serializedData.genFileUrl]: serializedData.source});
const deserialized = resolver.resolveSummary(
staticReflector.getStaticSymbol('/tmp/some_pipe.d.ts', 'SomePipe'));
expect(deserialized.aNumber).toBe(1);
expect(deserialized.aString).toBe('hello');
expect(deserialized.anArray).toEqual([1, 2]);
expect(deserialized.aStaticSymbol instanceof StaticSymbol).toBe(true);
// Note: change from .ts to .d.ts is expected
expect(deserialized.aStaticSymbol)
.toEqual(
staticReflector.getStaticSymbol('/tmp/some_symbol.d.ts', 'someName', ['someMember']));
});
it('should store reexports in the same file', () => {
init();
const reexportedData = resolver.serializeSummaries(
'/tmp/some_pipe.ts', [{
summaryKind: CompileSummaryKind.Pipe,
type: {
reference: staticReflector.getStaticSymbol('/tmp/some_pipe.ts', 'SomeReexportedPipe'),
diDeps: [],
lifecycleHooks: []
},
}]);
init({[reexportedData.genFileUrl]: reexportedData.source});
const serializedData = resolver.serializeSummaries('/tmp/some_module.ts', [
<CompileNgModuleSummary>{
summaryKind: CompileSummaryKind.NgModule,
type: {
reference: staticReflector.getStaticSymbol('/tmp/some_module.ts', 'SomeModule'),
diDeps: [],
lifecycleHooks: []
},
exportedPipes: [{
reference: staticReflector.getStaticSymbol('/tmp/some_pipe.d.ts', 'SomeReexportedPipe')
}],
exportedDirectives: [],
providers: [],
entryComponents: [],
modules: []
} }
]);
init({[serializedData.genFileUrl]: serializedData.source}); it('should load serialized summary files', () => {
const asymbol = symbolCache.get('/a.d.ts', 'a');
resolver.resolveSummary( init({'/a.ngsummary.json': serialize([{symbol: asymbol, metadata: 1}], [])});
staticReflector.getStaticSymbol('/tmp/some_module.d.ts', 'SomeModule')); expect(summaryResolver.resolveSummary(asymbol)).toEqual({symbol: asymbol, metadata: 1});
expect(resolver.resolveSummary(
staticReflector.getStaticSymbol('/tmp/some_pipe.d.ts', 'SomeReexportedPipe')))
.toEqual({
summaryKind: CompileSummaryKind.Pipe,
type: {
reference:
staticReflector.getStaticSymbol('/tmp/some_pipe.d.ts', 'SomeReexportedPipe'),
diDeps: [],
lifecycleHooks: []
},
});
}); });
it('should not load summaries for source files', () => {
init({});
spyOn(host, 'loadSummary').and.callThrough();
expect(summaryResolver.resolveSummary(symbolCache.get('/a.ts', 'a'))).toBeFalsy();
expect(host.loadSummary).not.toHaveBeenCalled();
});
it('should cache summaries', () => {
const asymbol = symbolCache.get('/a.d.ts', 'a');
init({'/a.ngsummary.json': serialize([{symbol: asymbol, metadata: 1}], [])});
expect(summaryResolver.resolveSummary(asymbol)).toBe(summaryResolver.resolveSummary(asymbol));
});
it('should return all sumbols in a summary', () => {
const asymbol = symbolCache.get('/a.d.ts', 'a');
init({'/a.ngsummary.json': serialize([{symbol: asymbol, metadata: 1}], [])});
expect(summaryResolver.getSymbolsOf('/a.d.ts')).toEqual([asymbol]);
});
}); });
} }
class MockAotSummaryResolverHost implements AotSummaryResolverHost {
constructor(private summaries: {[fileName: string]: string}) {}
loadSummary(filePath: string): string {
const result = this.summaries[filePath];
if (!result) {
throw new Error(`Could not find summary for ${filePath}`);
}
return result;
}
export class MockAotSummarySerializerHost implements AotSummarySerializerHost {
fileNameToModuleName(fileName: string): string { fileNameToModuleName(fileName: string): string {
return './' + path.basename(fileName).replace(EXT, ''); return './' + path.basename(fileName).replace(EXT, '');
} }
@ -135,4 +74,13 @@ class MockAotSummaryResolverHost implements AotSummaryResolverHost {
getOutputFileName(sourceFileName: string): string { getOutputFileName(sourceFileName: string): string {
return sourceFileName.replace(EXT, '') + '.d.ts'; return sourceFileName.replace(EXT, '') + '.d.ts';
} }
isSourceFile(filePath: string) { return !filePath.endsWith('.d.ts'); }
}
export class MockAotSummaryResolverHost extends MockAotSummarySerializerHost implements
AotSummaryResolverHost {
constructor(private summaries: {[fileName: string]: string}) { super(); }
loadSummary(filePath: string): string { return this.summaries[filePath]; }
} }

View File

@ -0,0 +1,199 @@
/**
* @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 {AotSummaryResolver, AotSummaryResolverHost, CompileSummaryKind, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost} from '@angular/compiler';
import {AotSummarySerializerHost, deserializeSummaries, serializeSummaries, summaryFileName} from '@angular/compiler/src/aot/summary_serializer';
import {MockStaticSymbolResolverHost} from './static_symbol_resolver_spec';
import {MockAotSummaryResolverHost} from './summary_resolver_spec';
export function main() {
describe('summary serializer', () => {
let summaryResolver: AotSummaryResolver;
let symbolResolver: StaticSymbolResolver;
let symbolCache: StaticSymbolCache;
let host: MockAotSummaryResolverHost;
beforeEach(() => { symbolCache = new StaticSymbolCache(); });
function init(
summaries: {[filePath: string]: string} = {}, metadata: {[key: string]: any} = {}) {
host = new MockAotSummaryResolverHost(summaries);
summaryResolver = new AotSummaryResolver(host, symbolCache);
symbolResolver = new StaticSymbolResolver(
new MockStaticSymbolResolverHost(metadata), symbolCache, summaryResolver);
}
describe('summaryFileName', () => {
it('should add .ngsummary.json to the filename', () => {
init();
expect(summaryFileName('a.ts')).toBe('a.ngsummary.json');
expect(summaryFileName('a.d.ts')).toBe('a.ngsummary.json');
expect(summaryFileName('a.js')).toBe('a.ngsummary.json');
});
});
it('should serialize various data correctly', () => {
init();
const serializedData = serializeSummaries(
host, summaryResolver, symbolResolver,
[
{
symbol: symbolCache.get('/tmp/some_values.ts', 'Values'),
metadata: {
aNumber: 1,
aString: 'hello',
anArray: [1, 2],
aStaticSymbol: symbolCache.get('/tmp/some_symbol.ts', 'someName')
}
},
{
symbol: symbolCache.get('/tmp/some_service.ts', 'SomeService'),
metadata: {
__symbolic: 'class',
members: {'aMethod': {__symbolic: 'function'}},
statics: {aStatic: true}
}
}
],
[<any>{
summaryKind: CompileSummaryKind.Injectable,
type: {
reference: symbolCache.get('/tmp/some_service.ts', 'SomeService'),
},
}]);
const summaries = deserializeSummaries(symbolCache, serializedData);
expect(summaries.length).toBe(2);
// Note: change from .ts to .d.ts is expected
expect(summaries[0].symbol).toBe(symbolCache.get('/tmp/some_values.d.ts', 'Values'));
expect(summaries[0].metadata).toEqual({
aNumber: 1,
aString: 'hello',
anArray: [1, 2],
aStaticSymbol: symbolCache.get('/tmp/some_symbol.d.ts', 'someName')
});
expect(summaries[1].symbol).toBe(symbolCache.get('/tmp/some_service.d.ts', 'SomeService'));
// serialization should only keep the statics...
expect(summaries[1].metadata).toEqual({__symbolic: 'class', statics: {aStatic: true}});
expect(summaries[1].type.type.reference)
.toBe(symbolCache.get('/tmp/some_service.d.ts', 'SomeService'));
});
it('should automatically add exported directives / pipes of NgModules that are not source files',
() => {
init({});
const externalSerialized = serializeSummaries(host, summaryResolver, symbolResolver, [], [
<any>{
summaryKind: CompileSummaryKind.Pipe,
type: {
reference: symbolCache.get('/tmp/external.ts', 'SomeExternalPipe'),
}
},
<any>{
summaryKind: CompileSummaryKind.Directive,
type: {
reference: symbolCache.get('/tmp/external.ts', 'SomeExternalDir'),
}
}
]);
init({
'/tmp/external.ngsummary.json': externalSerialized,
});
const serialized = serializeSummaries(
host, summaryResolver, symbolResolver, [], [<any>{
summaryKind: CompileSummaryKind.NgModule,
type: {reference: symbolCache.get('/tmp/some_module.ts', 'SomeModule')},
exportedPipes: [
{reference: symbolCache.get('/tmp/some_pipe.ts', 'SomePipe')},
{reference: symbolCache.get('/tmp/external.d.ts', 'SomeExternalPipe')}
],
exportedDirectives: [
{reference: symbolCache.get('/tmp/some_dir.ts', 'SomeDir')},
{reference: symbolCache.get('/tmp/external.d.ts', 'SomeExternalDir')}
]
}]);
const summaries = deserializeSummaries(symbolCache, serialized);
expect(summaries.length).toBe(3);
expect(summaries[0].symbol).toBe(symbolCache.get('/tmp/some_module.d.ts', 'SomeModule'));
expect(summaries[1].symbol).toBe(symbolCache.get('/tmp/external.d.ts', 'SomeExternalDir'));
expect(summaries[2].symbol)
.toBe(symbolCache.get('/tmp/external.d.ts', 'SomeExternalPipe'));
});
it('should automatically add the metadata of referenced symbols that are not in the soure files',
() => {
const externalSerialized = serializeSummaries(
host, summaryResolver, symbolResolver,
[
{
symbol: symbolCache.get('/tmp/external.ts', 'PROVIDERS'),
metadata: [symbolCache.get('/tmp/external_svc.ts', 'SomeService')]
},
{
symbol: symbolCache.get('/tmp/external_svc.ts', 'SomeService'),
metadata: {__symbolic: 'class'}
}
],
[<any>{
summaryKind: CompileSummaryKind.Injectable,
type: {
reference: symbolCache.get('/tmp/external_svc.ts', 'SomeService'),
}
}]);
init(
{
'/tmp/external.ngsummary.json': externalSerialized,
},
{
'/tmp/local.ts': `
export var local = 'a';
`,
'/tmp/non_summary.d.ts':
{__symbolic: 'module', version: 3, metadata: {'external': 'b'}}
});
const serialized = serializeSummaries(
host, summaryResolver, symbolResolver, [{
symbol: symbolCache.get('/tmp/test.ts', 'main'),
metadata: {
local: symbolCache.get('/tmp/local.ts', 'local'),
external: symbolCache.get('/tmp/external.d.ts', 'PROVIDERS'),
externalNonSummary: symbolCache.get('/tmp/non_summary.d.ts', 'external')
}
}],
[]);
const summaries = deserializeSummaries(symbolCache, serialized);
// Note: local should not show up!
expect(summaries.length).toBe(4);
expect(summaries[0].symbol).toBe(symbolCache.get('/tmp/test.d.ts', 'main'));
expect(summaries[0].metadata).toEqual({
local: symbolCache.get('/tmp/local.d.ts', 'local'),
external: symbolCache.get('/tmp/external.d.ts', 'PROVIDERS'),
externalNonSummary: symbolCache.get('/tmp/non_summary.d.ts', 'external')
});
expect(summaries[1].symbol).toBe(symbolCache.get('/tmp/external.d.ts', 'PROVIDERS'));
expect(summaries[1].metadata).toEqual([symbolCache.get(
'/tmp/external_svc.d.ts', 'SomeService')]);
// there was no summary for non_summary, but it should have
// been serialized as well.
expect(summaries[2].symbol).toBe(symbolCache.get('/tmp/non_summary.d.ts', 'external'));
expect(summaries[2].metadata).toEqual('b');
// SomService is a transitive dep, but sould have been serialized as well.
expect(summaries[3].symbol).toBe(symbolCache.get('/tmp/external_svc.d.ts', 'SomeService'));
expect(summaries[3].type.type.reference)
.toBe(symbolCache.get('/tmp/external_svc.d.ts', 'SomeService'));
});
});
}

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {SyntaxError} from '@angular/compiler';
import {CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from '@angular/compiler/src/compile_metadata'; import {CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from '@angular/compiler/src/compile_metadata';
import {CompilerConfig} from '@angular/compiler/src/config'; import {CompilerConfig} from '@angular/compiler/src/config';
import {DirectiveNormalizer} from '@angular/compiler/src/directive_normalizer'; import {DirectiveNormalizer} from '@angular/compiler/src/directive_normalizer';
@ -31,7 +31,7 @@ export function main() {
expect(() => normalizer.normalizeTemplate({ expect(() => normalizer.normalizeTemplate({
componentType: SomeComp, componentType: SomeComp,
moduleUrl: SOME_MODULE_URL, moduleUrl: SOME_MODULE_URL,
})).toThrowError('No template specified for component SomeComp'); })).toThrowError(SyntaxError, 'No template specified for component SomeComp');
})); }));
}); });

View File

@ -43,7 +43,7 @@ export function main(): void {
<!ELEMENT ex (#PCDATA)> <!ELEMENT ex (#PCDATA)>
]> ]>
<messagebundle> <messagebundle>
<msg id="7056919470098446707">translatable element <ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>with placeholders<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph> <ph name="INTERPOLATION"/></msg> <msg id="7056919470098446707">translatable element <ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>with placeholders<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph> <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
<msg id="2981514368455622387">{VAR_PLURAL, plural, =0 {<ph name="START_PARAGRAPH"><ex>&lt;p&gt;</ex></ph>test<ph name="CLOSE_PARAGRAPH"><ex>&lt;/p&gt;</ex></ph>} }</msg> <msg id="2981514368455622387">{VAR_PLURAL, plural, =0 {<ph name="START_PARAGRAPH"><ex>&lt;p&gt;</ex></ph>test<ph name="CLOSE_PARAGRAPH"><ex>&lt;/p&gt;</ex></ph>} }</msg>
<msg id="7999024498831672133" desc="d" meaning="m">foo</msg> <msg id="7999024498831672133" desc="d" meaning="m">foo</msg>
<msg id="2015957479576096115">{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<ph name="START_PARAGRAPH"><ex>&lt;p&gt;</ex></ph>deeply nested<ph name="CLOSE_PARAGRAPH"><ex>&lt;/p&gt;</ex></ph>} } } }</msg> <msg id="2015957479576096115">{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<ph name="START_PARAGRAPH"><ex>&lt;p&gt;</ex></ph>deeply nested<ph name="CLOSE_PARAGRAPH"><ex>&lt;/p&gt;</ex></ph>} } } }</msg>

View File

@ -15,8 +15,8 @@ import {identifierName} from '../src/compile_metadata';
import {stringify} from '../src/facade/lang'; import {stringify} from '../src/facade/lang';
import {CompileMetadataResolver} from '../src/metadata_resolver'; import {CompileMetadataResolver} from '../src/metadata_resolver';
import {ResourceLoader} from '../src/resource_loader'; import {ResourceLoader} from '../src/resource_loader';
import {SyntaxError} from '../src/util';
import {MockResourceLoader} from '../testing/resource_loader_mock'; import {MockResourceLoader} from '../testing/resource_loader_mock';
import {MalformedStylesComponent} from './metadata_resolver_fixture'; import {MalformedStylesComponent} from './metadata_resolver_fixture';
export function main() { export function main() {
@ -34,8 +34,9 @@ export function main() {
} }
expect(() => resolver.getDirectiveMetadata(ComponentWithEverythingInline)) expect(() => resolver.getDirectiveMetadata(ComponentWithEverythingInline))
.toThrowError(/Illegal state/); .toThrowError(SyntaxError, /Illegal state/);
expect(() => resolver.getPipeMetadata(SomePipe)).toThrowError(/Illegal state/); expect(() => resolver.getPipeMetadata(SomePipe))
.toThrowError(SyntaxError, /Illegal state/);
})); }));
it('should read metadata in sync for components with inline resources', it('should read metadata in sync for components with inline resources',
@ -121,7 +122,7 @@ export function main() {
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, true)) expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, true))
.toThrowError( .toThrowError(
`moduleId should be a string in "ComponentWithInvalidModuleId". See` + SyntaxError, `moduleId should be a string in "ComponentWithInvalidModuleId". See` +
` https://goo.gl/wIDDiL for more information.\n` + ` https://goo.gl/wIDDiL for more information.\n` +
`If you're using Webpack you should inline the template and the styles, see` + `If you're using Webpack you should inline the template and the styles, see` +
` https://goo.gl/X2J8zc.`); ` https://goo.gl/X2J8zc.`);
@ -145,7 +146,7 @@ export function main() {
} }
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, true)) expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, true))
.toThrowError(`Can't resolve all parameters for MyBrokenComp1: (?).`); .toThrowError(SyntaxError, `Can't resolve all parameters for MyBrokenComp1: (?).`);
})); }));
it('should throw with descriptive error message when a directive is passed to imports', it('should throw with descriptive error message when a directive is passed to imports',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => { inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
@ -155,6 +156,7 @@ export function main() {
expect( expect(
() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithImportedComponent, true)) () => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithImportedComponent, true))
.toThrowError( .toThrowError(
SyntaxError,
`Unexpected directive 'ComponentWithoutModuleId' imported by the module 'ModuleWithImportedComponent'`); `Unexpected directive 'ComponentWithoutModuleId' imported by the module 'ModuleWithImportedComponent'`);
})); }));
@ -168,6 +170,7 @@ export function main() {
} }
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithImportedPipe, true)) expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithImportedPipe, true))
.toThrowError( .toThrowError(
SyntaxError,
`Unexpected pipe 'SomePipe' imported by the module 'ModuleWithImportedPipe'`); `Unexpected pipe 'SomePipe' imported by the module 'ModuleWithImportedPipe'`);
})); }));
@ -181,6 +184,7 @@ export function main() {
} }
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithDeclaredModule, true)) expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithDeclaredModule, true))
.toThrowError( .toThrowError(
SyntaxError,
`Unexpected module 'SomeModule' declared by the module 'ModuleWithDeclaredModule'`); `Unexpected module 'SomeModule' declared by the module 'ModuleWithDeclaredModule'`);
})); }));
@ -191,6 +195,7 @@ export function main() {
} }
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithNullDeclared, true)) expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithNullDeclared, true))
.toThrowError( .toThrowError(
SyntaxError,
`Unexpected value 'null' declared by the module 'ModuleWithNullDeclared'`); `Unexpected value 'null' declared by the module 'ModuleWithNullDeclared'`);
})); }));
@ -201,6 +206,7 @@ export function main() {
} }
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithNullImported, true)) expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithNullImported, true))
.toThrowError( .toThrowError(
SyntaxError,
`Unexpected value 'null' imported by the module 'ModuleWithNullImported'`); `Unexpected value 'null' imported by the module 'ModuleWithNullImported'`);
})); }));
@ -212,7 +218,8 @@ export function main() {
} }
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, true)) expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, true))
.toThrowError(`Can't resolve all parameters for NonAnnotatedService: (?).`); .toThrowError(
SyntaxError, `Can't resolve all parameters for NonAnnotatedService: (?).`);
})); }));
it('should throw with descriptive error message when one of providers is not present', it('should throw with descriptive error message when one of providers is not present',
@ -223,6 +230,7 @@ export function main() {
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, true)) expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, true))
.toThrowError( .toThrowError(
SyntaxError,
`Invalid providers for "MyBrokenComp3" - only instances of Provider and Type are allowed, got: [SimpleService, ?null?, ...]`); `Invalid providers for "MyBrokenComp3" - only instances of Provider and Type are allowed, got: [SimpleService, ?null?, ...]`);
})); }));
@ -234,6 +242,7 @@ export function main() {
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, true)) expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, true))
.toThrowError( .toThrowError(
SyntaxError,
`Invalid viewProviders for "MyBrokenComp4" - only instances of Provider and Type are allowed, got: [?null?, ...]`); `Invalid viewProviders for "MyBrokenComp4" - only instances of Provider and Type are allowed, got: [?null?, ...]`);
})); }));
@ -248,11 +257,13 @@ export function main() {
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithNullBootstrap, true)) expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithNullBootstrap, true))
.toThrowError( .toThrowError(
SyntaxError,
`Unexpected value 'null' used in the bootstrap property of module 'ModuleWithNullBootstrap'`); `Unexpected value 'null' used in the bootstrap property of module 'ModuleWithNullBootstrap'`);
expect( expect(
() => () =>
resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithUndefinedBootstrap, true)) resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithUndefinedBootstrap, true))
.toThrowError( .toThrowError(
SyntaxError,
`Unexpected value 'undefined' used in the bootstrap property of module 'ModuleWithUndefinedBootstrap'`); `Unexpected value 'undefined' used in the bootstrap property of module 'ModuleWithUndefinedBootstrap'`);
})); }));

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {beforeEach, describe, expect, it} from '../../../core/testing/testing_internal';
import * as html from '../../src/ml_parser/ast'; import * as html from '../../src/ml_parser/ast';
import {HtmlParser, ParseTreeResult, TreeError} from '../../src/ml_parser/html_parser'; import {HtmlParser, ParseTreeResult, TreeError} from '../../src/ml_parser/html_parser';
import {TokenType} from '../../src/ml_parser/lexer'; import {TokenType} from '../../src/ml_parser/lexer';
@ -304,6 +303,18 @@ export function main() {
]))).toEqual([[html.Text, 'One {{message}}', 0]]); ]))).toEqual([[html.Text, 'One {{message}}', 0]]);
}); });
it('should parse out expansion forms', () => {
const parsed =
parser.parse(`<div><span>{a, plural, =0 {b}}</span></div>`, 'TestComp', true);
expect(humanizeDom(parsed)).toEqual([
[html.Element, 'div', 0],
[html.Element, 'span', 1],
[html.Expansion, 'a', 'plural', 2],
[html.ExpansionCase, '=0', 3],
]);
});
it('should parse out nested expansion forms', () => { it('should parse out nested expansion forms', () => {
const parsed = parser.parse( const parsed = parser.parse(
`{messages.length, plural, =0 { {p.gender, select, male {m}} }}`, 'TestComp', true); `{messages.length, plural, =0 { {p.gender, select, male {m}} }}`, 'TestComp', true);

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {describe, expect, it} from '../../../core/testing/testing_internal';
import * as html from '../../src/ml_parser/ast'; import * as html from '../../src/ml_parser/ast';
import {HtmlParser} from '../../src/ml_parser/html_parser'; import {HtmlParser} from '../../src/ml_parser/html_parser';
import {ExpansionResult, expandNodes} from '../../src/ml_parser/icu_ast_expander'; import {ExpansionResult, expandNodes} from '../../src/ml_parser/icu_ast_expander';
@ -96,6 +95,20 @@ export function main() {
]); ]);
}); });
it('should parse an expansion form as a tag single child', () => {
const res = expand(`<div><span>{a, b, =4 {c}}</span></div>`);
expect(humanizeNodes(res.nodes)).toEqual([
[html.Element, 'div', 0],
[html.Element, 'span', 1],
[html.Element, 'ng-container', 2],
[html.Attribute, '[ngSwitch]', 'a'],
[html.Element, 'template', 3],
[html.Attribute, 'ngSwitchCase', '=4'],
[html.Text, 'c', 4],
]);
});
describe('errors', () => { describe('errors', () => {
it('should error on unknown plural cases', () => { it('should error on unknown plural cases', () => {
expect(humanizeErrors(expand('{n, plural, unknown {-}}').errors)).toEqual([ expect(humanizeErrors(expand('{n, plural, unknown {-}}').errors)).toEqual([

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {describe, expect, it} from '../../../core/testing/testing_internal';
import {getHtmlTagDefinition} from '../../src/ml_parser/html_tags'; import {getHtmlTagDefinition} from '../../src/ml_parser/html_tags';
import {InterpolationConfig} from '../../src/ml_parser/interpolation_config'; import {InterpolationConfig} from '../../src/ml_parser/interpolation_config';
import * as lex from '../../src/ml_parser/lexer'; import * as lex from '../../src/ml_parser/lexer';
@ -524,7 +523,15 @@ export function main() {
]); ]);
}); });
it('should treat expansion form as text when they are not parsed', () => {
expect(tokenizeAndHumanizeParts('<span>{a, b, =4 {c}}</span>', false)).toEqual([
[lex.TokenType.TAG_OPEN_START, null, 'span'],
[lex.TokenType.TAG_OPEN_END],
[lex.TokenType.TEXT, '{a, b, =4 {c}}'],
[lex.TokenType.TAG_CLOSE, null, 'span'],
[lex.TokenType.EOF],
]);
});
}); });
describe('raw text', () => { describe('raw text', () => {
@ -672,6 +679,26 @@ export function main() {
]); ]);
}); });
it('should parse an expansion form as a tag single child', () => {
expect(tokenizeAndHumanizeParts('<div><span>{a, b, =4 {c}}</span></div>', true)).toEqual([
[lex.TokenType.TAG_OPEN_START, null, 'div'],
[lex.TokenType.TAG_OPEN_END],
[lex.TokenType.TAG_OPEN_START, null, 'span'],
[lex.TokenType.TAG_OPEN_END],
[lex.TokenType.EXPANSION_FORM_START],
[lex.TokenType.RAW_TEXT, 'a'],
[lex.TokenType.RAW_TEXT, 'b'],
[lex.TokenType.EXPANSION_CASE_VALUE, '=4'],
[lex.TokenType.EXPANSION_CASE_EXP_START],
[lex.TokenType.TEXT, 'c'],
[lex.TokenType.EXPANSION_CASE_EXP_END],
[lex.TokenType.EXPANSION_FORM_END],
[lex.TokenType.TAG_CLOSE, null, 'span'],
[lex.TokenType.TAG_CLOSE, null, 'div'],
[lex.TokenType.EOF],
]);
});
it('should parse an expansion forms with elements in it', () => { it('should parse an expansion forms with elements in it', () => {
expect(tokenizeAndHumanizeParts('{one.two, three, =4 {four <b>a</b>}}', true)).toEqual([ expect(tokenizeAndHumanizeParts('{one.two, three, =4 {four <b>a</b>}}', true)).toEqual([
[lex.TokenType.EXPANSION_FORM_START], [lex.TokenType.EXPANSION_FORM_START],

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 {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler'; import {StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost} from '@angular/compiler';
import * as o from '@angular/compiler/src/output/output_ast'; import * as o from '@angular/compiler/src/output/output_ast';
import {ImportResolver} from '@angular/compiler/src/output/path_util'; import {ImportResolver} from '@angular/compiler/src/output/path_util';
import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter'; import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter';
@ -14,6 +14,8 @@ import {convertValueToOutputAst} from '@angular/compiler/src/output/value_util';
import {MetadataCollector, isClassMetadata, isMetadataSymbolicCallExpression} from '@angular/tsc-wrapped'; import {MetadataCollector, isClassMetadata, isMetadataSymbolicCallExpression} from '@angular/tsc-wrapped';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {MockSummaryResolver} from '../aot/static_symbol_resolver_spec';
describe('TypeScriptEmitter (node only)', () => { describe('TypeScriptEmitter (node only)', () => {
it('should quote identifiers quoted in the source', () => { it('should quote identifiers quoted in the source', () => {
const sourceText = ` const sourceText = `
@ -27,7 +29,10 @@ describe('TypeScriptEmitter (node only)', () => {
const source = ts.createSourceFile('test.ts', sourceText, ts.ScriptTarget.Latest); const source = ts.createSourceFile('test.ts', sourceText, ts.ScriptTarget.Latest);
const collector = new MetadataCollector({quotedNames: true}); const collector = new MetadataCollector({quotedNames: true});
const stubHost = new StubReflectorHost(); const stubHost = new StubReflectorHost();
const reflector = new StaticReflector(stubHost); const symbolCache = new StaticSymbolCache();
const symbolResolver =
new StaticSymbolResolver(stubHost, symbolCache, new MockSummaryResolver());
const reflector = new StaticReflector(symbolResolver);
// Get the metadata from the above source // Get the metadata from the above source
const metadata = collector.getMetadata(source); const metadata = collector.getMetadata(source);
@ -62,9 +67,9 @@ describe('TypeScriptEmitter (node only)', () => {
}); });
}); });
class StubReflectorHost implements StaticReflectorHost { class StubReflectorHost implements StaticSymbolResolverHost {
getMetadataFor(modulePath: string): {[key: string]: any}[] { return []; } getMetadataFor(modulePath: string): {[key: string]: any}[] { return []; }
moduleNameToFileName(moduleName: string, containingFile: string): string { return ''; } moduleNameToFileName(moduleName: string, containingFile: string): string { return 'somePath'; }
} }
class StubImportResolver extends ImportResolver { class StubImportResolver extends ImportResolver {

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {SyntaxError} from '@angular/compiler';
import {CompileAnimationEntryMetadata, CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileTemplateMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenReference} from '@angular/compiler/src/compile_metadata'; import {CompileAnimationEntryMetadata, CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileTemplateMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenReference} from '@angular/compiler/src/compile_metadata';
import {DomElementSchemaRegistry} from '@angular/compiler/src/schema/dom_element_schema_registry'; import {DomElementSchemaRegistry} from '@angular/compiler/src/schema/dom_element_schema_registry';
import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry'; import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry';
@ -374,7 +374,7 @@ export function main() {
describe('errors', () => { describe('errors', () => {
it('should throw error when binding to an unknown property', () => { it('should throw error when binding to an unknown property', () => {
expect(() => parse('<my-component [invalidProp]="bar"></my-component>', [])) expect(() => parse('<my-component [invalidProp]="bar"></my-component>', []))
.toThrowError(`Template parse errors: .toThrowError(SyntaxError, `Template parse errors:
Can't bind to 'invalidProp' since it isn't a known property of 'my-component'. Can't bind to 'invalidProp' since it isn't a known property of 'my-component'.
1. If 'my-component' is an Angular component and it has 'invalidProp' input, then verify that it is part of this module. 1. If 'my-component' is an Angular component and it has 'invalidProp' input, then verify that it is part of this module.
2. If 'my-component' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. 2. If 'my-component' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message.
@ -382,14 +382,16 @@ Can't bind to 'invalidProp' since it isn't a known property of 'my-component'.
}); });
it('should throw error when binding to an unknown element w/o bindings', () => { it('should throw error when binding to an unknown element w/o bindings', () => {
expect(() => parse('<unknown></unknown>', [])).toThrowError(`Template parse errors: expect(() => parse('<unknown></unknown>', []))
.toThrowError(SyntaxError, `Template parse errors:
'unknown' is not a known element: 'unknown' is not a known element:
1. If 'unknown' is an Angular component, then verify that it is part of this module. 1. If 'unknown' is an Angular component, then verify that it is part of this module.
2. If 'unknown' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("[ERROR ->]<unknown></unknown>"): TestComp@0:0`); 2. If 'unknown' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("[ERROR ->]<unknown></unknown>"): TestComp@0:0`);
}); });
it('should throw error when binding to an unknown custom element w/o bindings', () => { it('should throw error when binding to an unknown custom element w/o bindings', () => {
expect(() => parse('<un-known></un-known>', [])).toThrowError(`Template parse errors: expect(() => parse('<un-known></un-known>', []))
.toThrowError(SyntaxError, `Template parse errors:
'un-known' is not a known element: 'un-known' is not a known element:
1. If 'un-known' is an Angular component, then verify that it is part of this module. 1. If 'un-known' is an Angular component, then verify that it is part of this module.
2. If 'un-known' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("[ERROR ->]<un-known></un-known>"): TestComp@0:0`); 2. If 'un-known' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("[ERROR ->]<un-known></un-known>"): TestComp@0:0`);
@ -397,13 +399,13 @@ Can't bind to 'invalidProp' since it isn't a known property of 'my-component'.
it('should throw error when binding to an invalid property', () => { it('should throw error when binding to an invalid property', () => {
expect(() => parse('<my-component [onEvent]="bar"></my-component>', [])) expect(() => parse('<my-component [onEvent]="bar"></my-component>', []))
.toThrowError(`Template parse errors: .toThrowError(SyntaxError, `Template parse errors:
Binding to property 'onEvent' is disallowed for security reasons ("<my-component [ERROR ->][onEvent]="bar"></my-component>"): TestComp@0:14`); Binding to property 'onEvent' is disallowed for security reasons ("<my-component [ERROR ->][onEvent]="bar"></my-component>"): TestComp@0:14`);
}); });
it('should throw error when binding to an invalid attribute', () => { it('should throw error when binding to an invalid attribute', () => {
expect(() => parse('<my-component [attr.onEvent]="bar"></my-component>', [])) expect(() => parse('<my-component [attr.onEvent]="bar"></my-component>', []))
.toThrowError(`Template parse errors: .toThrowError(SyntaxError, `Template parse errors:
Binding to attribute 'onEvent' is disallowed for security reasons ("<my-component [ERROR ->][attr.onEvent]="bar"></my-component>"): TestComp@0:14`); Binding to attribute 'onEvent' is disallowed for security reasons ("<my-component [ERROR ->][attr.onEvent]="bar"></my-component>"): TestComp@0:14`);
}); });
}); });
@ -445,6 +447,7 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
() => { () => {
expect(() => { parse('<div @someAnimation="value2">', [], [], []); }) expect(() => { parse('<div @someAnimation="value2">', [], [], []); })
.toThrowError( .toThrowError(
SyntaxError,
/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. \("<div \[ERROR ->\]@someAnimation="value2">"\): TestComp@0:5/); /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. \("<div \[ERROR ->\]@someAnimation="value2">"\): TestComp@0:5/);
}); });
@ -479,6 +482,7 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
expect(() => { parse('<broken></broken>', [dirA]); }) expect(() => { parse('<broken></broken>', [dirA]); })
.toThrowError( .toThrowError(
SyntaxError,
`Template parse errors:\nValue of the host property binding "class.foo" needs to be a string representing an expression but got "null" (object) ("[ERROR ->]<broken></broken>"): TestComp@0:0, Directive DirA`); `Template parse errors:\nValue of the host property binding "class.foo" needs to be a string representing an expression but got "null" (object) ("[ERROR ->]<broken></broken>"): TestComp@0:0, Directive DirA`);
}); });
@ -494,6 +498,7 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
expect(() => { parse('<broken></broken>', [dirA]); }) expect(() => { parse('<broken></broken>', [dirA]); })
.toThrowError( .toThrowError(
SyntaxError,
`Template parse errors:\nValue of the host listener "click" needs to be a string representing an expression but got "null" (object) ("[ERROR ->]<broken></broken>"): TestComp@0:0, Directive DirA`); `Template parse errors:\nValue of the host listener "click" needs to be a string representing an expression but got "null" (object) ("[ERROR ->]<broken></broken>"): TestComp@0:0, Directive DirA`);
}); });
@ -940,7 +945,7 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
const dirB = createDir('[dirB]', {providers: [provider1]}); const dirB = createDir('[dirB]', {providers: [provider1]});
expect(() => parse('<div dirA dirB>', [dirA, dirB])) expect(() => parse('<div dirA dirB>', [dirA, dirB]))
.toThrowError( .toThrowError(
`Template parse errors:\n` + SyntaxError, `Template parse errors:\n` +
`Mixing multi and non multi provider is not possible for token service0 ("[ERROR ->]<div dirA dirB>"): TestComp@0:0`); `Mixing multi and non multi provider is not possible for token service0 ("[ERROR ->]<div dirA dirB>"): TestComp@0:0`);
}); });
@ -1033,6 +1038,7 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
const dirA = createDir('[dirA]', {deps: ['self:provider0']}); const dirA = createDir('[dirA]', {deps: ['self:provider0']});
expect(() => parse('<div dirA></div>', [dirA])) expect(() => parse('<div dirA></div>', [dirA]))
.toThrowError( .toThrowError(
SyntaxError,
'Template parse errors:\nNo provider for provider0 ("[ERROR ->]<div dirA></div>"): TestComp@0:0'); 'Template parse errors:\nNo provider for provider0 ("[ERROR ->]<div dirA></div>"): TestComp@0:0');
}); });
@ -1047,6 +1053,7 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
const dirA = createDir('[dirA]', {deps: ['host:provider0']}); const dirA = createDir('[dirA]', {deps: ['host:provider0']});
expect(() => parse('<div dirA></div>', [dirA])) expect(() => parse('<div dirA></div>', [dirA]))
.toThrowError( .toThrowError(
SyntaxError,
'Template parse errors:\nNo provider for provider0 ("[ERROR ->]<div dirA></div>"): TestComp@0:0'); 'Template parse errors:\nNo provider for provider0 ("[ERROR ->]<div dirA></div>"): TestComp@0:0');
}); });
@ -1098,23 +1105,26 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
}); });
it('should report references with values that dont match a directive as errors', () => { it('should report references with values that dont match a directive as errors', () => {
expect(() => parse('<div #a="dirA"></div>', [])).toThrowError(`Template parse errors: expect(() => parse('<div #a="dirA"></div>', []))
.toThrowError(SyntaxError, `Template parse errors:
There is no directive with "exportAs" set to "dirA" ("<div [ERROR ->]#a="dirA"></div>"): TestComp@0:5`); There is no directive with "exportAs" set to "dirA" ("<div [ERROR ->]#a="dirA"></div>"): TestComp@0:5`);
}); });
it('should report invalid reference names', () => { it('should report invalid reference names', () => {
expect(() => parse('<div #a-b></div>', [])).toThrowError(`Template parse errors: expect(() => parse('<div #a-b></div>', []))
.toThrowError(SyntaxError, `Template parse errors:
"-" is not allowed in reference names ("<div [ERROR ->]#a-b></div>"): TestComp@0:5`); "-" is not allowed in reference names ("<div [ERROR ->]#a-b></div>"): TestComp@0:5`);
}); });
it('should report variables as errors', () => { it('should report variables as errors', () => {
expect(() => parse('<div let-a></div>', [])).toThrowError(`Template parse errors: expect(() => parse('<div let-a></div>', []))
.toThrowError(SyntaxError, `Template parse errors:
"let-" is only supported on template elements. ("<div [ERROR ->]let-a></div>"): TestComp@0:5`); "let-" is only supported on template elements. ("<div [ERROR ->]let-a></div>"): TestComp@0:5`);
}); });
it('should report duplicate reference names', () => { it('should report duplicate reference names', () => {
expect(() => parse('<div #a></div><div #a></div>', [])) expect(() => parse('<div #a></div><div #a></div>', []))
.toThrowError(`Template parse errors: .toThrowError(SyntaxError, `Template parse errors:
Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>"): TestComp@0:19`); Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>"): TestComp@0:19`);
}); });
@ -1466,7 +1476,7 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
it('should report when ng-content has non WS content', () => { it('should report when ng-content has non WS content', () => {
expect(() => parse('<ng-content>content</ng-content>', [])) expect(() => parse('<ng-content>content</ng-content>', []))
.toThrowError( .toThrowError(
`Template parse errors:\n` + SyntaxError, `Template parse errors:\n` +
`<ng-content> element cannot have content. ("[ERROR ->]<ng-content>content</ng-content>"): TestComp@0:0`); `<ng-content> element cannot have content. ("[ERROR ->]<ng-content>content</ng-content>"): TestComp@0:0`);
}); });
@ -1477,18 +1487,20 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
() => { expect(() => parse('<template template="ngIf">', [])).not.toThrowError(); }); () => { expect(() => parse('<template template="ngIf">', [])).not.toThrowError(); });
it('should report when mutliple *attrs are used on the same element', () => { it('should report when mutliple *attrs are used on the same element', () => {
expect(() => parse('<div *ngIf *ngFor>', [])).toThrowError(`Template parse errors: expect(() => parse('<div *ngIf *ngFor>', []))
.toThrowError(SyntaxError, `Template parse errors:
Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with * ("<div *ngIf [ERROR ->]*ngFor>"): TestComp@0:11`); Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with * ("<div *ngIf [ERROR ->]*ngFor>"): TestComp@0:11`);
}); });
it('should report when mix of template and *attrs are used on the same element', () => { it('should report when mix of template and *attrs are used on the same element', () => {
expect(() => parse('<span template="ngIf" *ngFor>', [])) expect(() => parse('<span template="ngIf" *ngFor>', []))
.toThrowError(`Template parse errors: .toThrowError(SyntaxError, `Template parse errors:
Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with * ("<span template="ngIf" [ERROR ->]*ngFor>"): TestComp@0:22`); Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with * ("<span template="ngIf" [ERROR ->]*ngFor>"): TestComp@0:22`);
}); });
it('should report invalid property names', () => { it('should report invalid property names', () => {
expect(() => parse('<div [invalidProp]></div>', [])).toThrowError(`Template parse errors: expect(() => parse('<div [invalidProp]></div>', []))
.toThrowError(SyntaxError, `Template parse errors:
Can't bind to 'invalidProp' since it isn't a known property of 'div'. ("<div [ERROR ->][invalidProp]></div>"): TestComp@0:5`); Can't bind to 'invalidProp' since it isn't a known property of 'div'. ("<div [ERROR ->][invalidProp]></div>"): TestComp@0:5`);
}); });
@ -1501,12 +1513,13 @@ Can't bind to 'invalidProp' since it isn't a known property of 'div'. ("<div [ER
host: {'[invalidProp]': 'someProp'} host: {'[invalidProp]': 'someProp'}
}) })
.toSummary(); .toSummary();
expect(() => parse('<div></div>', [dirA])).toThrowError(`Template parse errors: expect(() => parse('<div></div>', [dirA])).toThrowError(SyntaxError, `Template parse errors:
Can't bind to 'invalidProp' since it isn't a known property of 'div'. ("[ERROR ->]<div></div>"): TestComp@0:0, Directive DirA`); Can't bind to 'invalidProp' since it isn't a known property of 'div'. ("[ERROR ->]<div></div>"): TestComp@0:0, Directive DirA`);
}); });
it('should report errors in expressions', () => { it('should report errors in expressions', () => {
expect(() => parse('<div [prop]="a b"></div>', [])).toThrowError(`Template parse errors: expect(() => parse('<div [prop]="a b"></div>', []))
.toThrowError(SyntaxError, `Template parse errors:
Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("<div [ERROR ->][prop]="a b"></div>"): TestComp@0:5`); Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("<div [ERROR ->][prop]="a b"></div>"): TestComp@0:5`);
}); });
@ -1544,7 +1557,7 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("<div [
.toSummary(); .toSummary();
expect(() => parse('<div>', [dirB, dirA])) expect(() => parse('<div>', [dirB, dirA]))
.toThrowError( .toThrowError(
`Template parse errors:\n` + SyntaxError, `Template parse errors:\n` +
`More than one component matched on this element.\n` + `More than one component matched on this element.\n` +
`Make sure that only one component's selector can match a given 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`); `Conflicting components: DirB,DirA ("[ERROR ->]<div>"): TestComp@0:0`);
@ -1562,7 +1575,7 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("<div [
}) })
.toSummary(); .toSummary();
expect(() => parse('<template [a]="b" (e)="f"></template>', [dirA])) expect(() => parse('<template [a]="b" (e)="f"></template>', [dirA]))
.toThrowError(`Template parse errors: .toThrowError(SyntaxError, `Template parse errors:
Event binding e not emitted by any directive on an embedded template. Make sure that the event name is spelled correctly and all directives are listed in the "@NgModule.declarations". ("<template [a]="b" [ERROR ->](e)="f"></template>"): TestComp@0:18 Event binding e not emitted by any directive on an embedded template. Make sure that the event name is spelled correctly and all directives are listed in the "@NgModule.declarations". ("<template [a]="b" [ERROR ->](e)="f"></template>"): TestComp@0:18
Components on an embedded template: DirA ("[ERROR ->]<template [a]="b" (e)="f"></template>"): TestComp@0:0 Components on an embedded template: DirA ("[ERROR ->]<template [a]="b" (e)="f"></template>"): TestComp@0:0
Property binding a not used by any directive on an embedded template. Make sure that the property name is spelled correctly and all directives are listed in the "@NgModule.declarations". ("[ERROR ->]<template [a]="b" (e)="f"></template>"): TestComp@0:0`); Property binding a not used by any directive on an embedded template. Make sure that the property name is spelled correctly and all directives are listed in the "@NgModule.declarations". ("[ERROR ->]<template [a]="b" (e)="f"></template>"): TestComp@0:0`);
@ -1578,7 +1591,8 @@ Property binding a not used by any directive on an embedded template. Make sure
template: new CompileTemplateMetadata({ngContentSelectors: []}) template: new CompileTemplateMetadata({ngContentSelectors: []})
}) })
.toSummary(); .toSummary();
expect(() => parse('<div *a="b"></div>', [dirA])).toThrowError(`Template parse errors: expect(() => parse('<div *a="b"></div>', [dirA]))
.toThrowError(SyntaxError, `Template parse errors:
Components on an embedded template: DirA ("[ERROR ->]<div *a="b"></div>"): TestComp@0:0 Components on an embedded template: DirA ("[ERROR ->]<div *a="b"></div>"): TestComp@0:0
Property binding a not used by any directive on an embedded template. Make sure that the property name is spelled correctly and all directives are listed in the "@NgModule.declarations". ("[ERROR ->]<div *a="b"></div>"): TestComp@0:0`); Property binding a not used by any directive on an embedded template. Make sure that the property name is spelled correctly and all directives are listed in the "@NgModule.declarations". ("[ERROR ->]<div *a="b"></div>"): TestComp@0:0`);
}); });
@ -1838,7 +1852,7 @@ Property binding a not used by any directive on an embedded template. Make sure
}); });
it('should report pipes as error that have not been defined as dependencies', () => { it('should report pipes as error that have not been defined as dependencies', () => {
expect(() => parse('{{a | test}}', [])).toThrowError(`Template parse errors: expect(() => parse('{{a | test}}', [])).toThrowError(SyntaxError, `Template parse errors:
The pipe 'test' could not be found ("{{[ERROR ->]a | test}}"): TestComp@0:2`); The pipe 'test' could not be found ("{{[ERROR ->]a | test}}"): TestComp@0:2`);
}); });

View File

@ -8,7 +8,7 @@
"author": "angular", "author": "angular",
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"rxjs": "5.0.0-rc.4", "rxjs": "^5.0.1",
"zone.js": "^0.7.2" "zone.js": "^0.7.2"
}, },
"repository": { "repository": {

View File

@ -94,13 +94,13 @@ export function createPlatform(injector: Injector): PlatformRef {
* @experimental APIs related to application bootstrap are currently under review. * @experimental APIs related to application bootstrap are currently under review.
*/ */
export function createPlatformFactory( export function createPlatformFactory(
parentPlaformFactory: (extraProviders?: Provider[]) => PlatformRef, name: string, parentPlatformFactory: (extraProviders?: Provider[]) => PlatformRef, name: string,
providers: Provider[] = []): (extraProviders?: Provider[]) => PlatformRef { providers: Provider[] = []): (extraProviders?: Provider[]) => PlatformRef {
const marker = new OpaqueToken(`Platform: ${name}`); const marker = new OpaqueToken(`Platform: ${name}`);
return (extraProviders: Provider[] = []) => { return (extraProviders: Provider[] = []) => {
if (!getPlatform()) { if (!getPlatform()) {
if (parentPlaformFactory) { if (parentPlatformFactory) {
parentPlaformFactory( parentPlatformFactory(
providers.concat(extraProviders).concat({provide: marker, useValue: true})); providers.concat(extraProviders).concat({provide: marker, useValue: true}));
} else { } else {
createPlatform(ReflectiveInjector.resolveAndCreate( createPlatform(ReflectiveInjector.resolveAndCreate(

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 {OpaqueToken} from '../di'; import {Injectable, OpaqueToken} from '../di';
import {BaseError} from '../facade/errors'; import {BaseError} from '../facade/errors';
import {stringify} from '../facade/lang'; import {stringify} from '../facade/lang';
import {ViewEncapsulation} from '../metadata'; import {ViewEncapsulation} from '../metadata';
@ -54,6 +54,7 @@ function _throwError() {
* of components. * of components.
* @stable * @stable
*/ */
@Injectable()
export class Compiler { export class Compiler {
/** /**
* Compiles the given NgModule and all of its components. All templates of the components listed * Compiles the given NgModule and all of its components. All templates of the components listed

View File

@ -1364,10 +1364,7 @@ class CompWithRef {
noop() {} noop() {}
} }
@Component({ @Component({selector: 'wrap-comp-with-ref', template: '<comp-with-ref></comp-with-ref>'})
selector: 'wrap-comp-with-ref',
template: '<comp-with-ref></comp-with-ref>'
})
class WrapCompWithRef { class WrapCompWithRef {
constructor(public changeDetectorRef: ChangeDetectorRef) {} constructor(public changeDetectorRef: ChangeDetectorRef) {}
} }

View File

@ -8,7 +8,7 @@
"author": "angular", "author": "angular",
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"rxjs": "5.0.0-rc.4", "rxjs": "^5.0.1",
"@angular/core": "0.0.0-PLACEHOLDER", "@angular/core": "0.0.0-PLACEHOLDER",
"@angular/platform-browser": "0.0.0-PLACEHOLDER" "@angular/platform-browser": "0.0.0-PLACEHOLDER"
}, },

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 {CompileDirectiveMetadata, CompilerConfig, StaticReflector, StaticSymbol, StaticSymbolCache, componentModuleUrl, createOfflineCompileUrlResolver} from '@angular/compiler'; import {AotSummaryResolver, CompileDirectiveMetadata, CompilerConfig, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, componentModuleUrl, createOfflineCompileUrlResolver} from '@angular/compiler';
import {NgAnalyzedModules, analyzeNgModules, extractProgramSymbols} from '@angular/compiler/src/aot/compiler'; import {NgAnalyzedModules, analyzeNgModules, extractProgramSymbols} from '@angular/compiler/src/aot/compiler';
import {DirectiveNormalizer} from '@angular/compiler/src/directive_normalizer'; import {DirectiveNormalizer} from '@angular/compiler/src/directive_normalizer';
import {DirectiveResolver} from '@angular/compiler/src/directive_resolver'; import {DirectiveResolver} from '@angular/compiler/src/directive_resolver';
@ -30,6 +30,7 @@ import {ReflectorHost} from './reflector_host';
import {BuiltinType, CompletionKind, Declaration, DeclarationError, Declarations, Definition, LanguageService, LanguageServiceHost, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable, TemplateSource, TemplateSources} from './types'; import {BuiltinType, CompletionKind, Declaration, DeclarationError, Declarations, Definition, LanguageService, LanguageServiceHost, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable, TemplateSource, TemplateSources} from './types';
/** /**
* Create a `LanguageServiceHost` * Create a `LanguageServiceHost`
*/ */
@ -75,6 +76,7 @@ export class DummyResourceLoader extends ResourceLoader {
export class TypeScriptServiceHost implements LanguageServiceHost { export class TypeScriptServiceHost implements LanguageServiceHost {
private _resolver: CompileMetadataResolver; private _resolver: CompileMetadataResolver;
private _staticSymbolCache = new StaticSymbolCache(); private _staticSymbolCache = new StaticSymbolCache();
private _staticSymbolResolver: StaticSymbolResolver;
private _reflector: StaticReflector; private _reflector: StaticReflector;
private _reflectorHost: ReflectorHost; private _reflectorHost: ReflectorHost;
private _checker: ts.TypeChecker; private _checker: ts.TypeChecker;
@ -159,10 +161,13 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
private ensureAnalyzedModules(): NgAnalyzedModules { private ensureAnalyzedModules(): NgAnalyzedModules {
let analyzedModules = this.analyzedModules; let analyzedModules = this.analyzedModules;
if (!analyzedModules) { if (!analyzedModules) {
const analyzeHost = {isSourceFile(filePath: string) { return true; }};
const programSymbols = extractProgramSymbols( const programSymbols = extractProgramSymbols(
this.reflector, this.program.getSourceFiles().map(sf => sf.fileName), {}); this.staticSymbolResolver, this.program.getSourceFiles().map(sf => sf.fileName),
analyzeHost);
analyzedModules = this.analyzedModules = analyzeNgModules(programSymbols, {}, this.resolver); analyzedModules = this.analyzedModules =
analyzeNgModules(programSymbols, analyzeHost, this.resolver);
} }
return analyzedModules; return analyzedModules;
} }
@ -224,6 +229,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
if (this.modulesOutOfDate) { if (this.modulesOutOfDate) {
this.analyzedModules = null; this.analyzedModules = null;
this._reflector = null; this._reflector = null;
this._staticSymbolResolver = null;
this.templateReferences = null; this.templateReferences = null;
this.fileToComponent = null; this.fileToComponent = null;
this.ensureAnalyzedModules(); this.ensureAnalyzedModules();
@ -385,12 +391,27 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
errors.push(error); errors.push(error);
} }
private get staticSymbolResolver(): StaticSymbolResolver {
let result = this._staticSymbolResolver;
if (!result) {
const summaryResolver = new AotSummaryResolver(
{
loadSummary(filePath: string) { return null; },
isSourceFile(sourceFilePath: string) { return true; }
},
this._staticSymbolCache);
result = this._staticSymbolResolver = new StaticSymbolResolver(
this.reflectorHost, this._staticSymbolCache, summaryResolver,
(e, filePath) => this.collectError(e, filePath));
}
return result;
}
private get reflector(): StaticReflector { private get reflector(): StaticReflector {
let result = this._reflector; let result = this._reflector;
if (!result) { if (!result) {
result = this._reflector = new StaticReflector( result = this._reflector = new StaticReflector(
this.reflectorHost, this._staticSymbolCache, [], [], this.staticSymbolResolver, [], [], (e, filePath) => this.collectError(e, filePath));
(e, filePath) => this.collectError(e, filePath));
} }
return result; return result;
} }

View File

@ -124,7 +124,11 @@ export class WebAnimationsPlayer implements AnimationPlayer {
this._started = false; this._started = false;
} }
private _resetDomPlayerState() { this._player.cancel(); } private _resetDomPlayerState() {
if (this._player) {
this._player.cancel();
}
}
restart(): void { restart(): void {
this.reset(); this.reset();

View File

@ -175,6 +175,12 @@ export function main() {
expect(data['keyframes']).toEqual([{opacity: '0.5'}, {opacity: '1'}]); expect(data['keyframes']).toEqual([{opacity: '0.5'}, {opacity: '1'}]);
}); });
it('should allow the player to be destroyed before it is initialized', () => {
const elm = el('<div></div>');
const player = new ExtendedWebAnimationsPlayer(elm, [], {});
expect(() => { player.destroy(); }).not.toThrowError();
});
describe('previousStyle', () => { describe('previousStyle', () => {
it('should merge keyframe styles based on the previous styles passed in when the player has finished its operation', it('should merge keyframe styles based on the previous styles passed in when the player has finished its operation',
() => { () => {

View File

@ -24,6 +24,6 @@
"@angular/core": "0.0.0-PLACEHOLDER", "@angular/core": "0.0.0-PLACEHOLDER",
"@angular/common": "0.0.0-PLACEHOLDER", "@angular/common": "0.0.0-PLACEHOLDER",
"@angular/platform-browser": "0.0.0-PLACEHOLDER", "@angular/platform-browser": "0.0.0-PLACEHOLDER",
"rxjs": "5.0.0-rc.4" "rxjs": "^5.0.1"
} }
} }

View File

@ -320,7 +320,7 @@ export class UpgradeAdapter {
windowNgMock.module(this.ng1Module.name); windowNgMock.module(this.ng1Module.name);
const upgrade = new UpgradeAdapterRef(); const upgrade = new UpgradeAdapterRef();
this.ng2BootstrapDeferred.promise.then( this.ng2BootstrapDeferred.promise.then(
() => { (<any>upgrade)._bootstrapDone(this.moduleRef, upgrade.ng1Injector); }, onError); (ng1Injector) => { (<any>upgrade)._bootstrapDone(this.moduleRef, ng1Injector); }, onError);
return upgrade; return upgrade;
} }

View File

@ -11,7 +11,7 @@ import {async, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import * as angular from '@angular/upgrade/src/angular_js'; import * as angular from '@angular/upgrade/src/angular_js';
import {UpgradeAdapter, sortProjectableNodes} from '@angular/upgrade/src/upgrade_adapter'; import {UpgradeAdapter, UpgradeAdapterRef, sortProjectableNodes} from '@angular/upgrade/src/upgrade_adapter';
export function main() { export function main() {
describe('adapter: ng1 to ng2', () => { describe('adapter: ng1 to ng2', () => {
@ -51,13 +51,15 @@ export function main() {
})); }));
it('should output an error message to the console and re-throw', fakeAsync(() => { it('should output an error message to the console and re-throw', fakeAsync(() => {
spyOn(console, 'error'); let consoleErrorSpy: jasmine.Spy = spyOn(console, 'error');
expect(() => { expect(() => {
adapter.bootstrap(html('<ng2></ng2>'), ['ng1']); adapter.bootstrap(html('<ng2></ng2>'), ['ng1']);
flushMicrotasks(); flushMicrotasks();
}).toThrowError(); }).toThrowError();
expect(console.error).toHaveBeenCalled(); let args: any[] = consoleErrorSpy.calls.mostRecent().args;
expect(console.error).toHaveBeenCalledWith(jasmine.any(Error), jasmine.any(String)); expect(consoleErrorSpy).toHaveBeenCalled();
expect(args.length).toBeGreaterThan(0);
expect(args[0]).toEqual(jasmine.any(Error));
})); }));
}); });
@ -1294,6 +1296,44 @@ export function main() {
}); });
})); }));
}); });
describe('registerForNg1Tests', () => {
let upgradeAdapterRef: UpgradeAdapterRef;
let $compile: angular.ICompileService;
let $rootScope: angular.IRootScopeService;
beforeEach(() => {
const ng1Module = angular.module('ng1', []);
const Ng2 = Component({
selector: 'ng2',
template: 'Hello World',
}).Class({constructor: function() {}});
const Ng2Module = NgModule({declarations: [Ng2], imports: [BrowserModule]}).Class({
constructor: function() {}
});
const upgradeAdapter = new UpgradeAdapter(Ng2Module);
ng1Module.directive('ng2', upgradeAdapter.downgradeNg2Component(Ng2));
upgradeAdapterRef = upgradeAdapter.registerForNg1Tests(['ng1']);
});
beforeEach(
inject((_$compile_: angular.ICompileService, _$rootScope_: angular.IRootScopeService) => {
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
it('should be able to test ng1 components that use ng2 components', async(() => {
upgradeAdapterRef.ready(() => {
const element = $compile('<ng2></ng2>')($rootScope);
$rootScope.$digest();
expect(element[0].textContent).toContain('Hello World');
});
}));
});
}); });
describe('sortProjectableNodes', () => { describe('sortProjectableNodes', () => {

View File

@ -1,6 +1,6 @@
{ {
"name": "angular-srcs", "name": "angular-srcs",
"version": "2.3.0", "version": "4.0.0-beta.0",
"dependencies": { "dependencies": {
"@types/angularjs": { "@types/angularjs": {
"version": "1.5.13-alpha", "version": "1.5.13-alpha",
@ -5189,7 +5189,7 @@
"dev": true "dev": true
}, },
"rxjs": { "rxjs": {
"version": "5.0.0-rc.4" "version": "5.0.1"
}, },
"sauce-connect-launcher": { "sauce-connect-launcher": {
"version": "0.13.0", "version": "0.13.0",

8
npm-shrinkwrap.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "angular-srcs", "name": "angular-srcs",
"version": "2.3.0", "version": "4.0.0-beta.0",
"dependencies": { "dependencies": {
"@types/angularjs": { "@types/angularjs": {
"version": "1.5.13-alpha", "version": "1.5.13-alpha",
@ -7565,9 +7565,9 @@
"dev": true "dev": true
}, },
"rxjs": { "rxjs": {
"version": "5.0.0-rc.4", "version": "5.0.1",
"from": "rxjs@5.0.0-rc.4", "from": "rxjs@5.0.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.0.0-rc.4.tgz" "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.0.1.tgz"
}, },
"sauce-connect-launcher": { "sauce-connect-launcher": {
"version": "0.13.0", "version": "0.13.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "angular-srcs", "name": "angular-srcs",
"version": "2.3.1", "version": "2.4.0",
"private": true, "private": true,
"branchPattern": "2.0.*", "branchPattern": "2.0.*",
"description": "Angular 2 - a web framework for modern web apps", "description": "Angular 2 - a web framework for modern web apps",
@ -20,7 +20,7 @@
"dependencies": { "dependencies": {
"core-js": "^2.4.1", "core-js": "^2.4.1",
"reflect-metadata": "^0.1.3", "reflect-metadata": "^0.1.3",
"rxjs": "5.0.0-rc.4", "rxjs": "^5.0.1",
"zone.js": "^0.7.2" "zone.js": "^0.7.2"
}, },
"devDependencies": { "devDependencies": {

View File

@ -10,7 +10,7 @@ PKGS=(
reflect-metadata@0.1.8 reflect-metadata@0.1.8
typescript@2.0.2 typescript@2.0.2
zone.js@0.6.25 zone.js@0.6.25
rxjs@5.0.0-rc.4 rxjs@5.0.1
@types/{node@6.0.38,jasmine@2.2.33} @types/{node@6.0.38,jasmine@2.2.33}
jasmine@2.4.1 jasmine@2.4.1
webpack@2.1.0-beta.21 webpack@2.1.0-beta.21

View File

@ -9,7 +9,12 @@ function publishRepo {
BUILD_REPO="${COMPONENT}-builds" BUILD_REPO="${COMPONENT}-builds"
REPO_DIR="tmp/${BUILD_REPO}" REPO_DIR="tmp/${BUILD_REPO}"
echo "Pushing build artifacts to angular/${BUILD_REPO}" if [ -n "$CREATE_REPOS" ]; then
curl -u "$ORG:$TOKEN" https://api.github.com/user/repos \
-d '{"name":"'$BUILD_REPO'", "auto_init": true}'
fi
echo "Pushing build artifacts to ${ORG}/${BUILD_REPO}"
# create local repo folder and clone build repo into it # create local repo folder and clone build repo into it
rm -rf $REPO_DIR rm -rf $REPO_DIR
@ -37,14 +42,18 @@ function publishRepo {
for UMD_FILE in ${UMD_FILES}; do for UMD_FILE in ${UMD_FILES}; do
sed -i "s/\\\$\\\$ANGULAR_VERSION\\\$\\\$/${BUILD_VER}/g" ${UMD_FILE} sed -i "s/\\\$\\\$ANGULAR_VERSION\\\$\\\$/${BUILD_VER}/g" ${UMD_FILE}
done done
(
cd $REPO_DIR && \
git config credential.helper "store --file=.git/credentials" && \
echo "https://${GITHUB_TOKEN_ANGULAR}:@github.com" > .git/credentials
)
fi fi
echo `date` > $REPO_DIR/BUILD_INFO echo `date` > $REPO_DIR/BUILD_INFO
echo $SHA >> $REPO_DIR/BUILD_INFO echo $SHA >> $REPO_DIR/BUILD_INFO
( (
cd $REPO_DIR && \ cd $REPO_DIR && \
git config credential.helper "store --file=.git/credentials" && \
echo "https://${GITHUB_TOKEN_ANGULAR}:@github.com" > .git/credentials && \
git config user.name "${COMMITTER_USER_NAME}" && \ git config user.name "${COMMITTER_USER_NAME}" && \
git config user.email "${COMMITTER_USER_EMAIL}" && \ git config user.email "${COMMITTER_USER_EMAIL}" && \
git add --all && \ git add --all && \
@ -55,9 +64,7 @@ function publishRepo {
} }
# Publish all individual packages from packages-dist. # Publish all individual packages from packages-dist.
if [[ "$TRAVIS_REPO_SLUG" == "angular/angular" && \ function publishPackages {
"$TRAVIS_PULL_REQUEST" == "false" && \
"$CI_MODE" == "e2e" ]]; then
for dir in dist/packages-dist/*/ dist/tools/@angular/tsc-wrapped for dir in dist/packages-dist/*/ dist/tools/@angular/tsc-wrapped
do do
COMPONENT="$(basename ${dir})" COMPONENT="$(basename ${dir})"
@ -66,10 +73,13 @@ if [[ "$TRAVIS_REPO_SLUG" == "angular/angular" && \
COMPONENT="${COMPONENT//_/-}" COMPONENT="${COMPONENT//_/-}"
JS_BUILD_ARTIFACTS_DIR="${dir}" JS_BUILD_ARTIFACTS_DIR="${dir}"
REPO_URL="https://github.com/angular/${COMPONENT}-builds.git" if [[ "$1" == "ssh" ]]; then
# Use the below URL for testing when using SSH authentication REPO_URL="git@github.com:${ORG}/${COMPONENT}-builds.git"
# REPO_URL="git@github.com:angular/${COMPONENT}-builds.git" elif [[ "$1" == "http" ]]; then
REPO_URL="https://github.com/${ORG}/${COMPONENT}-builds.git"
else
die "Don't have a way to publish to scheme $1"
fi
SHA=`git rev-parse HEAD` SHA=`git rev-parse HEAD`
SHORT_SHA=`git rev-parse --short HEAD` SHORT_SHA=`git rev-parse --short HEAD`
COMMIT_MSG=`git log --oneline | head -n1` COMMIT_MSG=`git log --oneline | head -n1`
@ -80,6 +90,18 @@ if [[ "$TRAVIS_REPO_SLUG" == "angular/angular" && \
done done
echo "Finished publishing build artifacts" echo "Finished publishing build artifacts"
}
# See DEVELOPER.md for help
if [ $# -gt 0 ]; then
ORG=$1
publishPackages "ssh"
elif [[ \
"$TRAVIS_REPO_SLUG" == "angular/angular" && \
"$TRAVIS_PULL_REQUEST" == "false" && \
"$CI_MODE" == "e2e" ]]; then
ORG="angular"
publishPackages "http"
else else
echo "Not building the upstream/master branch, build artifacts won't be published." echo "Not building the upstream/master branch, build artifacts won't be published."
fi fi

View File

@ -224,6 +224,34 @@ export class MetadataCollector {
return recordEntry(result, classDeclaration); return recordEntry(result, classDeclaration);
} }
// Collect all exported symbols from an exports clause.
const exportMap = new Map<string, string>();
ts.forEachChild(sourceFile, node => {
switch (node.kind) {
case ts.SyntaxKind.ExportDeclaration:
const exportDeclaration = <ts.ExportDeclaration>node;
const {moduleSpecifier, exportClause} = exportDeclaration;
if (!moduleSpecifier) {
exportClause.elements.forEach(spec => {
const exportedAs = spec.name.text;
const name = (spec.propertyName || spec.name).text;
exportMap.set(name, exportedAs);
});
}
}
});
const isExportedIdentifier = (identifier: ts.Identifier) => exportMap.has(identifier.text);
const isExported = (node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.EnumDeclaration) =>
(node.flags & ts.NodeFlags.Export) || isExportedIdentifier(node.name);
const exportedIdentifierName = (identifier: ts.Identifier) =>
exportMap.get(identifier.text) || identifier.text;
const exportedName =
(node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.EnumDeclaration) =>
exportedIdentifierName(node.name);
// Predeclare classes and functions // Predeclare classes and functions
ts.forEachChild(sourceFile, node => { ts.forEachChild(sourceFile, node => {
switch (node.kind) { switch (node.kind) {
@ -231,8 +259,9 @@ export class MetadataCollector {
const classDeclaration = <ts.ClassDeclaration>node; const classDeclaration = <ts.ClassDeclaration>node;
if (classDeclaration.name) { if (classDeclaration.name) {
const className = classDeclaration.name.text; const className = classDeclaration.name.text;
if (node.flags & ts.NodeFlags.Export) { if (isExported(classDeclaration)) {
locals.define(className, {__symbolic: 'reference', name: className}); locals.define(
className, {__symbolic: 'reference', name: exportedName(classDeclaration)});
} else { } else {
locals.define( locals.define(
className, errorSym('Reference to non-exported class', node, {className})); className, errorSym('Reference to non-exported class', node, {className}));
@ -241,9 +270,9 @@ export class MetadataCollector {
break; break;
case ts.SyntaxKind.FunctionDeclaration: case ts.SyntaxKind.FunctionDeclaration:
if (!(node.flags & ts.NodeFlags.Export)) {
// Report references to this function as an error.
const functionDeclaration = <ts.FunctionDeclaration>node; const functionDeclaration = <ts.FunctionDeclaration>node;
if (!isExported(functionDeclaration)) {
// Report references to this function as an error.
const nameNode = functionDeclaration.name; const nameNode = functionDeclaration.name;
if (nameNode && nameNode.text) { if (nameNode && nameNode.text) {
locals.define( locals.define(
@ -268,10 +297,14 @@ export class MetadataCollector {
if (exportClause) { if (exportClause) {
exportClause.elements.forEach(spec => { exportClause.elements.forEach(spec => {
const name = spec.name.text; const name = spec.name.text;
// If the symbol was not already exported, export a reference since it is a
// reference to an import
if (!metadata || !metadata[name]) {
const propNode = spec.propertyName || spec.name; const propNode = spec.propertyName || spec.name;
const value: MetadataValue = evaluator.evaluateNode(propNode); const value: MetadataValue = evaluator.evaluateNode(propNode);
if (!metadata) metadata = {}; if (!metadata) metadata = {};
metadata[name] = recordEntry(value, node); metadata[name] = recordEntry(value, node);
}
}); });
} }
} }
@ -294,9 +327,9 @@ export class MetadataCollector {
const classDeclaration = <ts.ClassDeclaration>node; const classDeclaration = <ts.ClassDeclaration>node;
if (classDeclaration.name) { if (classDeclaration.name) {
const className = classDeclaration.name.text; const className = classDeclaration.name.text;
if (node.flags & ts.NodeFlags.Export) { if (isExported(classDeclaration)) {
if (!metadata) metadata = {}; if (!metadata) metadata = {};
metadata[className] = classMetadataOf(classDeclaration); metadata[exportedName(classDeclaration)] = classMetadataOf(classDeclaration);
} }
} }
// Otherwise don't record metadata for the class. // Otherwise don't record metadata for the class.
@ -306,24 +339,20 @@ export class MetadataCollector {
// Record functions that return a single value. Record the parameter // Record functions that return a single value. Record the parameter
// names substitution will be performed by the StaticReflector. // names substitution will be performed by the StaticReflector.
const functionDeclaration = <ts.FunctionDeclaration>node; const functionDeclaration = <ts.FunctionDeclaration>node;
if (node.flags & ts.NodeFlags.Export) { if (isExported(functionDeclaration)) {
if (!metadata) metadata = {}; if (!metadata) metadata = {};
const name = exportedName(functionDeclaration);
const maybeFunc = maybeGetSimpleFunction(functionDeclaration); const maybeFunc = maybeGetSimpleFunction(functionDeclaration);
if (maybeFunc) { metadata[name] =
metadata[maybeFunc.name] = recordEntry(maybeFunc.func, node); maybeFunc ? recordEntry(maybeFunc.func, node) : {__symbolic: 'function'};
} else if (functionDeclaration.name.kind == ts.SyntaxKind.Identifier) {
const nameNode = <ts.Identifier>functionDeclaration.name;
const functionName = nameNode.text;
metadata[functionName] = {__symbolic: 'function'};
}
} }
break; break;
case ts.SyntaxKind.EnumDeclaration: case ts.SyntaxKind.EnumDeclaration:
if (node.flags & ts.NodeFlags.Export) {
const enumDeclaration = <ts.EnumDeclaration>node; const enumDeclaration = <ts.EnumDeclaration>node;
if (isExported(enumDeclaration)) {
const enumValueHolder: {[name: string]: MetadataValue} = {}; const enumValueHolder: {[name: string]: MetadataValue} = {};
const enumName = enumDeclaration.name.text; const enumName = exportedName(enumDeclaration);
let nextDefaultValue: MetadataValue = 0; let nextDefaultValue: MetadataValue = 0;
let writtenMembers = 0; let writtenMembers = 0;
for (const member of enumDeclaration.members) { for (const member of enumDeclaration.members) {
@ -376,9 +405,10 @@ export class MetadataCollector {
} }
let exported = false; let exported = false;
if (variableStatement.flags & ts.NodeFlags.Export || if (variableStatement.flags & ts.NodeFlags.Export ||
variableDeclaration.flags & ts.NodeFlags.Export) { variableDeclaration.flags & ts.NodeFlags.Export ||
isExportedIdentifier(nameNode)) {
if (!metadata) metadata = {}; if (!metadata) metadata = {};
metadata[nameNode.text] = recordEntry(varValue, node); metadata[exportedIdentifierName(nameNode)] = recordEntry(varValue, node);
exported = true; exported = true;
} }
if (isPrimitive(varValue)) { if (isPrimitive(varValue)) {

View File

@ -40,6 +40,7 @@ describe('Collector', () => {
'private-enum.ts', 'private-enum.ts',
're-exports.ts', 're-exports.ts',
're-exports-2.ts', 're-exports-2.ts',
'export-as.d.ts',
'static-field-reference.ts', 'static-field-reference.ts',
'static-method.ts', 'static-method.ts',
'static-method-call.ts', 'static-method-call.ts',
@ -528,20 +529,19 @@ describe('Collector', () => {
]); ]);
}); });
it('should be able to collect a export as symbol', () => {
const source = program.getSourceFile('export-as.d.ts');
const metadata = collector.getMetadata(source);
expect(metadata.metadata).toEqual({SomeFunction: {__symbolic: 'function'}});
});
it('should be able to collect exports with no module specifier', () => { it('should be able to collect exports with no module specifier', () => {
const source = program.getSourceFile('/re-exports-2.ts'); const source = program.getSourceFile('/re-exports-2.ts');
const metadata = collector.getMetadata(source); const metadata = collector.getMetadata(source);
expect(metadata.metadata).toEqual({ expect(metadata.metadata).toEqual({
MyClass: Object({__symbolic: 'class'}),
OtherModule: {__symbolic: 'reference', module: './static-field-reference', name: 'Foo'}, OtherModule: {__symbolic: 'reference', module: './static-field-reference', name: 'Foo'},
MyOtherModule: {__symbolic: 'reference', module: './static-field', name: 'MyModule'}, MyOtherModule: {__symbolic: 'reference', module: './static-field', name: 'MyModule'}
// TODO(vicb): support exported symbols - https://github.com/angular/angular/issues/13473
MyClass: {
__symbolic: 'error',
message: 'Reference to non-exported class',
line: 3,
character: 4,
context: {className: 'MyClass'}
},
}); });
}); });
@ -1006,6 +1006,10 @@ const FILES: Directory = {
import {Foo as OtherModule} from './static-field-reference'; import {Foo as OtherModule} from './static-field-reference';
class MyClass {} class MyClass {}
export {OtherModule, MyModule as MyOtherModule, MyClass}; export {OtherModule, MyModule as MyOtherModule, MyClass};
`,
'export-as.d.ts': `
declare function someFunction(): void;
export { someFunction as SomeFunction };
`, `,
'local-symbol-ref.ts': ` 'local-symbol-ref.ts': `
import {Component, Validators} from 'angular2/core'; import {Component, Validators} from 'angular2/core';

View File

@ -305,7 +305,7 @@ export interface ContentChildrenDecorator {
export declare function createPlatform(injector: Injector): PlatformRef; export declare function createPlatform(injector: Injector): PlatformRef;
/** @experimental */ /** @experimental */
export declare function createPlatformFactory(parentPlaformFactory: (extraProviders?: Provider[]) => PlatformRef, name: string, providers?: Provider[]): (extraProviders?: Provider[]) => PlatformRef; export declare function createPlatformFactory(parentPlatformFactory: (extraProviders?: Provider[]) => PlatformRef, name: string, providers?: Provider[]): (extraProviders?: Provider[]) => PlatformRef;
/** @stable */ /** @stable */
export declare const CUSTOM_ELEMENTS_SCHEMA: SchemaMetadata; export declare const CUSTOM_ELEMENTS_SCHEMA: SchemaMetadata;