Compare commits

...

20 Commits

Author SHA1 Message Date
171a9bdc85 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 16:24:53 -08:00
896916af29 build(npm): update angular version in shrinkwrap files 2016-12-19 16:23:54 -08:00
e49c7fae22 refactor(compiler-cli): support extracting the mesage bundle without writing a file (#13580) 2016-12-19 15:28:55 -08:00
6b65fc1286 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 11:56:10 -08:00
0e3981afc1 fix(compiler-cli): produce metadata for .d.ts files without metadata (#13526)
Fixes #13307
Fixes #13473
Fixes #13521
2016-12-16 15:33:47 -08:00
e78508507d 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-16 15:33:16 -08:00
a23fa94ca8 fix(common): capitalize first letter of all words in TitleCasePipe (#13511) 2016-12-16 15:24:26 -08:00
4568d5ddac refactor(core): fix typo (#13515)
Closes #13512
2016-12-16 15:21:58 -08:00
c6e893953f 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-16 15:14:16 -08:00
55dfa1b69d test(forms): refactor integration tests to improve speed (#13500) 2016-12-15 17:07:26 -08:00
0fe3cd9a4c fix(i18n): add a default example to xmb placeholders (#13507)
Otherwise the TC would not be able to load the message
2016-12-15 15:33:42 -08:00
0c19898694 fix(animations): allow players to be destroyed before initialized (#13346)
Closes #13293
Closes #13346
2016-12-15 14:18:57 -08:00
5b6e8ea3ec refactor(compiler): format update (#13506) 2016-12-15 13:54:38 -08:00
732f446ad2 docs(common): fix ngIf example (#13496) 2016-12-15 13:07:36 -08:00
f0e092515c refactor(compiler): don't print stack trace on template parse errors (#13390) 2016-12-15 13:07:12 -08:00
14e785f5b7 fix(build): use bash string comparison operator (#13502) 2016-12-15 12:05:29 -08:00
01d1624884 feature(DEVELOPER.md): add easy way to publish personal snapshot builds (#13469) 2016-12-15 11:19:21 -08:00
33910ddfc9 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-15 09:12:40 -08:00
01ca2db6ae docs(changelog): add changelog for 4.0.0-beta.0 2016-12-14 21:54:31 -08:00
6cefccb314 build: bump angular to 4.0.0-beta.0 & tsc-wrapped to 0.5.0 2016-12-14 16:42:44 -08:00
94 changed files with 2532 additions and 1428 deletions

View File

@ -1,3 +1,76 @@
<a name="4.0.0-beta.0"></a>
# [4.0.0-beta.0](https://github.com/angular/angular/compare/2.3.0...4.0.0-beta.0) (2016-12-15)
### Features
* **common:** add a `titlecase` pipe ([#13324](https://github.com/angular/angular/issues/13324)) ([61d7c1e](https://github.com/angular/angular/commit/61d7c1e)), closes [#11436](https://github.com/angular/angular/issues/11436)
* **common:** export NgLocaleLocalization ([#13367](https://github.com/angular/angular/issues/13367)) ([56dce0e](https://github.com/angular/angular/commit/56dce0e)), closes [#11921](https://github.com/angular/angular/issues/11921)
* **compiler:** add id property to i18nMessage ([6dd5201](https://github.com/angular/angular/commit/6dd5201))
* **compiler:** digest methods return i18nMessage id if sets ([562f7a2](https://github.com/angular/angular/commit/562f7a2))
* **forms:** add novalidate by default ([#13092](https://github.com/angular/angular/issues/13092)) ([4c35be3](https://github.com/angular/angular/commit/4c35be3))
* **http:** simplify URLSearchParams creation ([#13338](https://github.com/angular/angular/issues/13338)) ([90c2235](https://github.com/angular/angular/commit/90c2235)), closes [#8858](https://github.com/angular/angular/issues/8858)
* **language-service:** warn when a method isn't called in an event ([#13437](https://github.com/angular/angular/issues/13437)) ([9ec0a4e](https://github.com/angular/angular/commit/9ec0a4e))
* **platform browser:** introduce Meta service ([#12322](https://github.com/angular/angular/issues/12322)) ([72361fb](https://github.com/angular/angular/commit/72361fb))
* **router:** routerLink add tabindex attribute ([#13094](https://github.com/angular/angular/issues/13094)) ([a006c14](https://github.com/angular/angular/commit/a006c14)), closes [#10895](https://github.com/angular/angular/issues/10895)
* **testing:** add overrideTemplate method ([#13372](https://github.com/angular/angular/issues/13372)) ([169ed82](https://github.com/angular/angular/commit/169ed82)), closes [#10685](https://github.com/angular/angular/issues/10685)
* **common** ngIf now supports else; saves condition to local var ([b4db73d](https://github.com/angular/angular/commit/b4db73d)), closes [#13061](https://github.com/angular/angular/issues/13061) [#13297](https://github.com/angular/angular/issues/13297)
Note: 4.0.0-beta.0 release also contains all the changes present in the 2.3.1 release.
<a name="2.3.1"></a>
## [2.3.1](https://github.com/angular/angular/compare/2.3.0...2.3.1) (2016-12-15)
### Bug Fixes
* **animations:** always cleanup players after they have finished internally ([#13334](https://github.com/angular/angular/issues/13334)) ([a26e054](https://github.com/angular/angular/commit/a26e054)), closes [#13333](https://github.com/angular/angular/issues/13333)
* **animations:** throw errors and normalize offset beyond the range of [0,1] ([6557bc3](https://github.com/angular/angular/commit/6557bc3)), closes [#13348](https://github.com/angular/angular/issues/13348) [#13440](https://github.com/angular/angular/issues/13440)
* **compiler:** emit quoted object literal keys if the source is quoted ([ecfad46](https://github.com/angular/angular/commit/ecfad46)), closes [#13249](https://github.com/angular/angular/issues/13249) [#13356](https://github.com/angular/angular/issues/13356)
* **compiler:** fix merge error in compiler_host ([69b52eb](https://github.com/angular/angular/commit/69b52eb))
* **compiler:** fix PR 13322 ([#13331](https://github.com/angular/angular/issues/13331)) ([79728b4](https://github.com/angular/angular/commit/79728b4))
* **compiler:** fix simplify a reference without a name ([1c279b3](https://github.com/angular/angular/commit/1c279b3)), closes [#13470](https://github.com/angular/angular/issues/13470)
* **compiler:** fix transpiled ES5 code ([#13322](https://github.com/angular/angular/issues/13322)) ([6c1d790](https://github.com/angular/angular/commit/6c1d790)), closes [#13301](https://github.com/angular/angular/issues/13301)
* **compiler:** generated CSS files suffixed with ngstyle. ([#13353](https://github.com/angular/angular/issues/13353)) ([c8a9b70](https://github.com/angular/angular/commit/c8a9b70)), closes [#13141](https://github.com/angular/angular/issues/13141)
* **compiler:** make sure provider values with `name` property dont break. ([efa2d80](https://github.com/angular/angular/commit/efa2d80)), closes [#13394](https://github.com/angular/angular/issues/13394) [#13445](https://github.com/angular/angular/issues/13445)
* **compiler:** narrow the span reported for invalid pipes ([307d305](https://github.com/angular/angular/commit/307d305)), closes [#13326](https://github.com/angular/angular/issues/13326) [#13411](https://github.com/angular/angular/issues/13411)
* **compiler:** propagate exports when upgrading metadata to v2 ([f6ef7d6](https://github.com/angular/angular/commit/f6ef7d6))
* **compiler:** resolver should merge host bindings and listeners ([#13474](https://github.com/angular/angular/issues/13474)) ([6aeaca3](https://github.com/angular/angular/commit/6aeaca3)), closes [#13327](https://github.com/angular/angular/issues/13327)
* **compiler:** support dotted property binding ([8db184d](https://github.com/angular/angular/commit/8db184d)), closes [angular/flex-layout#34](https://github.com/angular/flex-layout/issues/34)
* **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:** 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:** 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:** 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)
* **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)
* **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)
* **compiler**: better error when directive not listed in NgModule.declarations ([b0cd514](https://github.com/angular/angular/commit/b0cd514))
* **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:** 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))
* **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:** 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)
### Performance Improvements
* **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>
# [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`.
* [Prerequisite Software](#prerequisite-software)
@ -137,4 +137,21 @@ You can automatically format your code by running:
$ 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',
// include Angular v1 for upgrade module testing
'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/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/zone.js',
'node_modules/zone.js/dist/long-stack-trace-zone.js',
'node_modules/zone.js/dist/proxy.js',
'node_modules/zone.js/dist/sync-test.js',
'node_modules/zone.js/dist/jasmine-patch.js',
'node_modules/zone.js/dist/async-test.js',
'node_modules/zone.js/dist/fake-async-test.js',
// 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},
'node_modules/reflect-metadata/Reflect.js', 'tools/build/file2modulename.js', 'test-main.js',
{pattern: 'dist/all/empty.*', included: false, watched: false}, {
'node_modules/reflect-metadata/Reflect.js',
'tools/build/file2modulename.js',
'test-main.js',
{pattern: 'dist/all/empty.*', included: false, watched: false},
{
pattern: 'modules/@angular/platform-browser/test/static_assets/**',
included: false,
watched: false

View File

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

View File

@ -76,7 +76,7 @@ import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef} from '
* Simple form:
* - `<div *ngIf="condition">...</div>`
* - `<div template="ngIf condition">...</div>`
* - `<ng-container [ngIf]="condition"><div>...</div></ng-container>`
* - `<template [ngIf]="condition"><div>...</div></template>`
*
* Form with an else block:
* ```

View File

@ -27,6 +27,17 @@ export class LowerCasePipe implements PipeTransform {
}
}
/**
* Helper method to transform a single word to titlecase.
*
* @stable
*/
function titleCaseWord(word: string) {
if (!word) return word;
return word[0].toUpperCase() + word.substr(1).toLowerCase();
}
/**
* Transforms text to titlecase.
*
@ -40,7 +51,7 @@ export class TitleCasePipe implements PipeTransform {
throw new InvalidPipeArgumentError(TitleCasePipe, value);
}
return value[0].toUpperCase() + value.substr(1).toLowerCase();
return value.split(/\b/g).map(word => titleCaseWord(word)).join('');
}
}

View File

@ -32,6 +32,14 @@ export function main() {
it('should return titlecase', () => { expect(pipe.transform('foo')).toEqual('Foo'); });
it('should return titlecase for subsequent words',
() => { expect(pipe.transform('one TWO Three fouR')).toEqual('One Two Three Four'); });
it('should support empty strings', () => { expect(pipe.transform('')).toEqual(''); });
it('should persist whitespace',
() => { expect(pipe.transform('one two')).toEqual('One Two'); });
it('should titlecase when there is a new value', () => {
expect(pipe.transform('bar')).toEqual('Bar');
expect(pipe.transform('foo')).toEqual('Foo');

View File

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

View File

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

View File

@ -19,7 +19,6 @@ import {AngularCompilerOptions, CodeGenerator, CompilerHostContext, NodeCompiler
const glob = require('glob');
/**
* Main method.
* Standalone program that executes codegen using the ngtools API and tests that files were
@ -30,6 +29,7 @@ function main() {
Promise.resolve()
.then(() => codeGenTest())
.then(() => i18nTest())
.then(() => lazyRoutesTest())
.then(() => {
console.log('All done!');
@ -42,7 +42,6 @@ function main() {
});
}
function codeGenTest() {
const basePath = path.join(__dirname, '../ngtools_src');
const project = path.join(basePath, 'tsconfig-build.json');
@ -52,12 +51,9 @@ function codeGenTest() {
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);
return delegateHost.writeFile.call(delegateHost, fileName, ...rest);
}
});
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;
@ -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() {
const basePath = path.join(__dirname, '../ngtools_src');

View File

@ -27,9 +27,14 @@ function main() {
const basePath = path.resolve(__dirname, '..');
const project = path.resolve(basePath, 'tsconfig-build.json');
const readFiles: string[] = [];
const writtenFiles: {fileName: string, content: string}[] = [];
class AssertingHostContext extends NodeCompilerHostContext {
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));
return super.readFile(fileName);
}
@ -45,16 +50,29 @@ function main() {
config.ngOptions.generateCodeForLibraries = false;
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) => {
console.log(`>>> codegen done, asserting read files`);
assertSomeFileMatch(readFiles, /^node_modules\/.*\.ngsummary\.json$/);
assertNoFileMatch(readFiles, /^node_modules\/.*\.metadata.json$/);
assertNoFileMatch(readFiles, /^node_modules\/.*\.html$/);
assertNoFileMatch(readFiles, /^node_modules\/.*\.css$/);
assertNoFileMatch(readFiles, /^src\/.*\.ngsummary\.json$/);
assertSomeFileMatch(readFiles, /^src\/.*\.html$/);
assertSomeFileMatch(readFiles, /^src\/.*\.css$/);
console.log(`>>> asserting written files`);
assertWrittenFile(writtenFiles, /^src\/module\.ngfactory\.ts$/, /class MainModuleInjector/);
console.log(`done, no errors.`);
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')}`);
}
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();

View File

@ -9,7 +9,7 @@
"ng-xi18n": "./src/extract_i18n.js"
},
"dependencies": {
"@angular/tsc-wrapped": "0.4.2",
"@angular/tsc-wrapped": "0.5.0",
"reflect-metadata": "^0.1.2",
"minimist": "^1.2.0"
},

View File

@ -21,9 +21,7 @@ import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
import {Console} from './private_import_core';
const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$/;
const GENERATED_META_FILES = /\.json$/;
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.ngstyle\.ts$/;
const PREAMBLE = `/**
* @fileoverview This file is generated by the Angular 2 template compiler.
@ -102,14 +100,8 @@ export class CodeGenerator {
debug: options.debug === true,
translations: transContent,
i18nFormat: cliOptions.i18nFormat,
locale: cliOptions.locale,
excludeFilePattern: options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES :
GENERATED_FILES
locale: cliOptions.locale
});
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 NODE_MODULES = '/node_modules/';
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 {
readResource(fileName: string): Promise<string>;
@ -28,6 +30,7 @@ export class CompilerHost implements AotCompilerHost {
protected basePath: string;
private genDir: string;
private resolverCache = new Map<string, ModuleMetadata[]>();
protected resolveModuleNameHost: CompilerHostContext;
constructor(
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);
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.
getCanonicalFileName(fileName: string): string { return fileName; }
moduleNameToFileName(m: string, containingFile: string) {
moduleNameToFileName(m: string, containingFile: string): string|null {
if (!containingFile || !containingFile.length) {
if (m.indexOf('.') === 0) {
throw new Error('Resolution of relative paths requires a containing file.');
@ -53,7 +75,8 @@ export class CompilerHost implements AotCompilerHost {
}
m = m.replace(EXT, '');
const resolved =
ts.resolveModuleName(m, containingFile.replace(/\\/g, '/'), this.options, this.context)
ts.resolveModuleName(
m, containingFile.replace(/\\/g, '/'), this.options, this.resolveModuleNameHost)
.resolvedModule;
return resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null;
};
@ -158,6 +181,12 @@ export class CompilerHost implements AotCompilerHost {
const metadataPath = filePath.replace(DTS, '.metadata.json');
if (this.context.fileExists(metadataPath)) {
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 {
const sf = this.getSourceFile(filePath);
@ -173,35 +202,13 @@ export class CompilerHost implements AotCompilerHost {
}
try {
const metadataOrMetadatas = JSON.parse(this.context.readFile(filePath));
const metadatas = metadataOrMetadatas ?
const metadatas: ModuleMetadata[] = metadataOrMetadatas ?
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
[];
const v1Metadata = metadatas.find((m: any) => m['version'] === 1);
let v3Metadata = metadatas.find((m: any) => m['version'] === 3);
const v1Metadata = metadatas.find(m => m.version === 1);
let v3Metadata = metadatas.find(m => m.version === 3);
if (!v3Metadata && v1Metadata) {
// 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
// the metadata and the `extends` clause.
v3Metadata = {'__symbolic': 'module', 'version': 3, 'metadata': {}};
if (v1Metadata.exports) {
v3Metadata.exports = v1Metadata.exports;
}
for (let prop in v1Metadata.metadata) {
v3Metadata.metadata[prop] = v1Metadata.metadata[prop];
}
const exports = this.metadataCollector.getMetadata(this.getSourceFile(dtsFilePath));
if (exports) {
for (let prop in exports.metadata) {
if (!v3Metadata.metadata[prop]) {
v3Metadata.metadata[prop] = exports.metadata[prop];
}
}
if (exports.exports) {
v3Metadata.exports = exports.exports;
}
}
metadatas.push(v3Metadata);
metadatas.push(this.upgradeVersion1Metadata(v1Metadata, dtsFilePath));
}
this.resolverCache.set(filePath, metadatas);
return metadatas;
@ -211,13 +218,49 @@ export class CompilerHost implements AotCompilerHost {
}
}
private upgradeVersion1Metadata(v1Metadata: ModuleMetadata, dtsFilePath: string): ModuleMetadata {
// 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
// the metadata and the `extends` clause.
let v3Metadata: ModuleMetadata = {'__symbolic': 'module', 'version': 3, 'metadata': {}};
if (v1Metadata.exports) {
v3Metadata.exports = v1Metadata.exports;
}
for (let prop in v1Metadata.metadata) {
v3Metadata.metadata[prop] = v1Metadata.metadata[prop];
}
const exports = this.metadataCollector.getMetadata(this.getSourceFile(dtsFilePath));
if (exports) {
for (let prop in exports.metadata) {
if (!v3Metadata.metadata[prop]) {
v3Metadata.metadata[prop] = exports.metadata[prop];
}
}
if (exports.exports) {
v3Metadata.exports = exports.exports;
}
}
return v3Metadata;
}
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 {
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 {

View File

@ -14,41 +14,15 @@
// Must be imported first, because angular2 decorators throws on load.
import 'reflect-metadata';
import * as compiler from '@angular/compiler';
import * as tsc from '@angular/tsc-wrapped';
import * as path from 'path';
import * as ts from 'typescript';
import {Extractor} from './extractor';
function extract(
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions,
program: ts.Program, host: ts.CompilerHost) {
const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host);
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);
});
program: ts.Program, host: ts.CompilerHost): Promise<void> {
return Extractor.create(ngOptions, program, host).extract(cliOptions.i18nFormat);
}
// Entry point

View File

@ -15,29 +15,74 @@ import 'reflect-metadata';
import * as compiler from '@angular/compiler';
import * as tsc from '@angular/tsc-wrapped';
import * as path from 'path';
import * as ts from 'typescript';
import {excludeFilePattern} from './codegen';
import {CompilerHost, ModuleResolutionHostAdapter} from './compiler_host';
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
export class Extractor {
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) {}
extract(): Promise<compiler.MessageBundle> {
return this.ngExtractor.extract(this.program.getSourceFiles().map(
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)));
extract(formatName: string): Promise<void> {
// Checks the format and returns the extension
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(
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program,
moduleResolverHost: ts.ModuleResolutionHost, ngCompilerHost?: CompilerHost): Extractor {
if (!ngCompilerHost)
ngCompilerHost =
new CompilerHost(program, options, new ModuleResolutionHostAdapter(moduleResolverHost));
const {extractor: ngExtractor} = compiler.Extractor.create(
ngCompilerHost, {excludeFilePattern: excludeFilePattern(options)});
return new Extractor(ngExtractor, ngCompilerHost, program);
options: tsc.AngularCompilerOptions, program: ts.Program, tsCompilerHost: ts.CompilerHost,
compilerHostContext?: CompilerHostContext, ngCompilerHost?: CompilerHost): Extractor {
if (!ngCompilerHost) {
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
const context = compilerHostContext || new ModuleResolutionHostAdapter(tsCompilerHost);
ngCompilerHost = usePathMapping ? new PathMappedCompilerHost(program, options, context) :
new CompilerHost(program, options, context);
}
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 tsc from '@angular/tsc-wrapped';
import {SyntaxError} from '@angular/compiler';
import {CodeGenerator} from './codegen';
function codegen(
@ -28,7 +29,7 @@ export function main(
const cliOptions = new tsc.NgcCliOptions(args);
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);
return Promise.resolve(1);
} else {

View File

@ -13,16 +13,16 @@
* 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 * as ts from 'typescript';
import {CodeGenerator} from './codegen';
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
import {Extractor} from './extractor';
import {listLazyRoutesOfModule} from './ngtools_impl';
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
export interface NgTools_InternalApi_NG2_CodeGen_Options {
basePath: string;
compilerOptions: ts.CompilerOptions;
@ -50,9 +50,18 @@ export interface NgTools_InternalApi_NG2_ListLazyRoutes_Options {
// Every new property under this line should be optional.
}
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
@ -94,7 +103,6 @@ export class NgTools_InternalApi_NG_2 {
return codeGenerator.codegen();
}
/**
* @internal
* @private
@ -111,7 +119,10 @@ export class NgTools_InternalApi_NG_2 {
new PathMappedCompilerHost(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);
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
*/
import {ModuleMetadata} from '@angular/tsc-wrapped';
import * as ts from 'typescript';
import {CompilerHost} from '../src/compiler_host';
@ -150,12 +151,14 @@ describe('CompilerHost', () => {
it('should be able to read a metadata file', () => {
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 ', () => {
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 ', () => {
@ -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 dummyMetadata: ModuleMetadata = {
__symbolic: 'module',
version: 3,
metadata:
{foo: {__symbolic: 'error', message: 'Variable not initialized', line: 0, character: 11}}
};
const FILES: Entry = {
'tmp': {
'src': {
@ -204,7 +218,7 @@ const FILES: Entry = {
'@angular': {
'core.d.ts': dummyModule,
'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}},
'unused.d.ts': dummyModule,
'empty.d.ts': 'export declare var a: string;',
@ -225,6 +239,9 @@ const FILES: Entry = {
`,
'v1.metadata.json':
`{"__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": [],
"outDir": "built",
"declaration": true,
"module": "es2015"
"module": "es2015",
"moduleResolution": "node"
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true
},
"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

View File

@ -32,7 +32,9 @@ export * from './src/aot/compiler_host';
export * from './src/aot/static_reflector';
export * from './src/aot/static_reflection_capabilities';
export * from './src/aot/static_symbol';
export * from './src/aot/static_symbol_resolver';
export * from './src/aot/summary_resolver';
export * from './src/summary_resolver';
export {JitCompiler} from './src/jit/compiler';
export * from './src/jit/compiler_factory';
export * from './src/url_resolver';
@ -60,4 +62,5 @@ export * from './src/style_compiler';
export * from './src/template_parser/template_parser';
export {ViewCompiler} from './src/view_compiler/view_compiler';
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.

View File

@ -6,11 +6,10 @@
* 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 {StringMapWrapper} from '../facade/collection';
import {isBlank, isPresent} from '../facade/lang';
import {CompilerInjectable} from '../injectable';
import {ParseError} from '../parse_util';
import {ANY_STATE, FILL_STYLE_FLAG} from '../private_import_core';
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
@ -35,7 +34,7 @@ export class AnimationEntryParseResult {
constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {}
}
@Injectable()
@CompilerInjectable()
export class AnimationParser {
constructor(private _schema: ElementSchemaRegistry) {}

View File

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

View File

@ -34,8 +34,11 @@ import {AotCompilerHost} from './compiler_host';
import {AotCompilerOptions} from './compiler_options';
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
import {StaticReflector} from './static_reflector';
import {StaticSymbolCache} from './static_symbol';
import {StaticSymbolResolver} from './static_symbol_resolver';
import {AotSummaryResolver} from './summary_resolver';
/**
* 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 || '';
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);
const htmlParser = new I18NHtmlParser(new HtmlParser(), translations, options.i18nFormat);
const config = new CompilerConfig({
@ -60,17 +66,16 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
const console = new Console();
const tmplParser =
new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []);
const summaryResolver = new AotSummaryResolver(compilerHost, staticReflector, options);
const resolver = new CompileMetadataResolver(
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer,
staticReflector);
// TODO(vicb): do not pass options.i18nFormat here
const compiler = new AotCompiler(
resolver, tmplParser, new StyleCompiler(urlResolver),
compilerHost, resolver, tmplParser, new StyleCompiler(urlResolver),
new ViewCompiler(config, elementSchemaRegistry),
new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console),
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};
}

View File

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

View File

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

View File

@ -8,6 +8,7 @@
import {GetterFn, MethodFn, ReflectionCapabilities, SetterFn, reflector} from '../private_import_core';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
export class StaticAndDynamicReflectionCapabilities {
static install(staticDelegate: StaticReflector) {
@ -42,7 +43,7 @@ export class StaticAndDynamicReflectionCapabilities {
method(name: string): MethodFn { return this.dynamicDelegate.method(name); }
importUri(type: any): string { return this.staticDelegate.importUri(type); }
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 {
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 {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 = {
coreDecorators: '@angular/core/src/metadata',
diDecorators: '@angular/core/src/di/metadata',
@ -22,66 +24,20 @@ const ANGULAR_IMPORT_LOCATIONS = {
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
* templates statically.
*/
export class StaticReflector implements ReflectorReader {
private declarationCache = new Map<string, StaticSymbol>();
private annotationCache = new Map<StaticSymbol, any[]>();
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
private parameterCache = new Map<StaticSymbol, any[]>();
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 opaqueToken: StaticSymbol;
constructor(
private host: StaticReflectorHost,
private staticSymbolCache: StaticSymbolCache = new StaticSymbolCache(),
private symbolResolver: StaticSymbolResolver,
knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [],
knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = [],
private errorRecorder?: (error: any, fileName: string) => void) {
@ -94,12 +50,26 @@ export class StaticReflector implements ReflectorReader {
}
importUri(typeOrFunc: StaticSymbol): string {
const staticSymbol = this.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, '');
const staticSymbol = this.findSymbolDeclaration(typeOrFunc);
return staticSymbol ? staticSymbol.filePath : null;
}
resolveIdentifier(name: string, moduleUrl: string, runtime: any): any {
return this.findDeclaration(moduleUrl, name, '');
resolveIdentifier(name: string, moduleUrl: string): StaticSymbol {
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 {
@ -128,7 +98,7 @@ export class StaticReflector implements ReflectorReader {
public propMetadata(type: StaticSymbol): {[key: string]: any[]} {
let propMetadata = this.propertyCache.get(type);
if (!propMetadata) {
const classMetadata = this.getTypeMetadata(type) || {};
const classMetadata = this.getTypeMetadata(type);
propMetadata = {};
if (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} {
let methodNames = this.methodCache.get(type);
if (!methodNames) {
const classMetadata = this.getTypeMetadata(type) || {};
const classMetadata = this.getTypeMetadata(type);
methodNames = {};
if (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.
*/
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) {
@ -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 */
public simplify(context: StaticSymbol, value: any): any {
const self = this;
@ -414,93 +294,41 @@ export class StaticReflector implements ReflectorReader {
const calling = new Map<StaticSymbol, boolean>();
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 {
const moduleMetadata = self.getModuleMetadata(staticSymbol.filePath);
const declarationValue =
moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null;
return declarationValue;
const resolvedSymbol = self.symbolResolver.resolveSymbol(staticSymbol);
return resolvedSymbol ? resolvedSymbol.metadata : null;
}
function isOpaqueToken(context: StaticSymbol, value: any): boolean {
if (value && value.__symbolic === 'new' && value.expression) {
const target = value.expression;
if (target.__symbolic == 'reference') {
return sameSymbol(resolveReference(context, target), self.opaqueToken);
function simplifyCall(functionSymbol: StaticSymbol, targetFunction: any, args: any[]) {
if (targetFunction && targetFunction['__symbolic'] == 'function') {
if (calling.get(functionSymbol)) {
throw new Error('Recursion not supported');
}
}
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 (calling.get(functionSymbol)) {
throw new Error('Recursion not supported');
}
calling.set(functionSymbol, true);
try {
const value = targetFunction['value'];
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 defaults: any[] = targetFunction.defaults;
if (defaults && defaults.length > args.length) {
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
}
const functionScope = BindingScope.build();
for (let i = 0; i < parameters.length; i++) {
functionScope.define(parameters[i], args[i]);
}
const oldScope = scope;
let result: any;
try {
scope = functionScope.done();
result = simplifyInContext(functionSymbol, value, depth + 1);
} finally {
scope = oldScope;
}
return result;
calling.set(functionSymbol, true);
try {
const value = targetFunction['value'];
if (value && (depth != 0 || value.__symbolic != 'error')) {
const parameters: string[] = targetFunction['parameters'];
const defaults: any[] = targetFunction.defaults;
if (defaults && defaults.length > args.length) {
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
}
} finally {
calling.delete(functionSymbol);
const functionScope = BindingScope.build();
for (let i = 0; i < parameters.length; i++) {
functionScope.define(parameters[i], args[i]);
}
const oldScope = scope;
let result: any;
try {
scope = functionScope.done();
result = simplifyInContext(functionSymbol, value, depth + 1);
} finally {
scope = oldScope;
}
return result;
}
} finally {
calling.delete(functionSymbol);
}
}
@ -511,7 +339,7 @@ export class StaticReflector implements ReflectorReader {
return {__symbolic: 'ignore'};
}
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 {
@ -540,7 +368,18 @@ export class StaticReflector implements ReflectorReader {
return result;
}
if (expression instanceof StaticSymbol) {
return expression;
// Stop simplification at builtin symbols
if (expression === self.opaqueToken || self.conversionMap.has(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['__symbolic']) {
@ -618,50 +457,33 @@ export class StaticReflector implements ReflectorReader {
if (indexTarget && isPrimitive(index)) return indexTarget[index];
return null;
case 'select':
const member = expression['member'];
let selectContext = context;
let selectTarget = simplify(expression['expression']);
if (selectTarget instanceof StaticSymbol) {
// Access to a static instance variable
const member: string = expression['member'];
const members = selectTarget.members ?
(selectTarget.members as string[]).concat(member) :
[member];
const declarationValue = resolveReferenceValue(selectTarget);
const members = selectTarget.members.concat(member);
selectContext =
self.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
if (declarationValue && declarationValue.statics) {
selectTarget = declarationValue.statics;
const declarationValue = resolveReferenceValue(selectContext);
if (declarationValue) {
return simplifyInContext(selectContext, declarationValue, depth + 1);
} else {
return selectContext;
}
}
const member = simplifyInContext(selectContext, expression['member'], depth + 1);
if (selectTarget && isPrimitive(member))
return simplifyInContext(selectContext, selectTarget[member], depth + 1);
return null;
case 'reference':
if (!expression['name']) {
return context;
// Note: This only has to deal with variable references,
// as symbol references have been converted into StaticSymbols already
// in the StaticSymbolResolver!
const name: string = expression['name'];
const localValue = scope.resolve(name);
if (localValue != BindingScope.missing) {
return localValue;
}
if (!expression.module) {
const name: string = expression['name'];
const localValue = scope.resolve(name);
if (localValue != BindingScope.missing) {
return localValue;
}
}
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;
break;
case 'class':
return context;
case 'function':
@ -669,26 +491,26 @@ export class StaticReflector implements ReflectorReader {
case 'new':
case 'call':
// Determine if the function is a built-in conversion
let target = expression['expression'];
if (target['module']) {
staticSymbol =
self.findDeclaration(target['module'], target['name'], context.filePath);
} else {
staticSymbol = self.getStaticSymbol(context.filePath, target['name']);
}
let converter = self.conversionMap.get(staticSymbol);
if (converter) {
let args: any[] = expression['arguments'];
if (!args) {
args = [];
staticSymbol = simplifyInContext(context, expression['expression'], depth + 1);
if (staticSymbol instanceof StaticSymbol) {
if (staticSymbol === self.opaqueToken) {
// if somebody calls new OpaqueToken, don't create an OpaqueToken,
// but rather return the symbol to which the OpaqueToken is assigned to.
return context;
}
const argExpressions: any[] = expression['arguments'] || [];
const args =
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
let converter = self.conversionMap.get(staticSymbol);
if (converter) {
return converter(context, args);
} else {
// Determine if the function is one we can simplify.
const targetFunction = resolveReferenceValue(staticSymbol);
return simplifyCall(staticSymbol, targetFunction, args);
}
return converter(
context, args.map(arg => simplifyInContext(context, arg, depth + 1)));
}
// Determine if the function is one we can simplify.
return simplifyCall(expression);
break;
case 'error':
let message = produceErrorMessage(expression);
if (expression['line']) {
@ -709,7 +531,9 @@ export class StaticReflector implements ReflectorReader {
try {
return simplify(value);
} 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) {
throw positionalError(message, e.fileName, e.line, e.column);
}
@ -733,40 +557,10 @@ export class StaticReflector implements ReflectorReader {
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} {
const moduleMetadata = this.getModuleMetadata(type.filePath);
return moduleMetadata['metadata'][type.name] || {__symbolic: 'class'};
const resolvedSymbol = this.symbolResolver.resolveSymbol(type);
return resolvedSymbol && resolvedSymbol.metadata ? resolvedSymbol.metadata :
{__symbolic: 'class'};
}
}
@ -858,7 +652,7 @@ class PopulatedScope extends BindingScope {
}
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 {

View File

@ -14,3 +14,23 @@
export class StaticSymbol {
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
* found in the LICENSE file at https://angular.io/license
*/
import {CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleSummary, CompilePipeSummary, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, identifierModuleUrl, identifierName} from '../compile_metadata';
import {SummaryResolver} from '../summary_resolver';
import {CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
import {Summary, SummaryResolver} from '../summary_resolver';
import {GeneratedFile} from './generated_file';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
import {filterFileByPatterns} from './utils';
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {ResolvedStaticSymbol} from './static_symbol_resolver';
import {deserializeSummaries, summaryFileName} from './summary_serializer';
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
*/
loadSummary(filePath: string): string;
loadSummary(filePath: string): string /*|null*/;
/**
* Returns the output file path of a source file.
* E.g.
* `some_file.ts` -> `some_file.d.ts`
* Returns whether a file is a source file or not.
*/
getOutputFileName(sourceFilePath: string): string;
isSourceFile(sourceFilePath: string): boolean;
}
export interface AotSummaryResolverOptions {
includeFilePattern?: RegExp;
excludeFilePattern?: RegExp;
}
export class AotSummaryResolver implements SummaryResolver<StaticSymbol> {
private summaryCache = new Map<StaticSymbol, Summary<StaticSymbol>>();
private loadedFilePaths = new Set<string>();
export class AotSummaryResolver implements SummaryResolver {
private summaryCache: {[cacheKey: string]: CompileTypeSummary} = {};
constructor(private host: AotSummaryResolverHost, private staticSymbolCache: StaticSymbolCache) {}
constructor(
private host: AotSummaryResolverHost, private staticReflector: StaticReflector,
private options: AotSummaryResolverOptions) {}
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 _assertNoMembers(symbol: StaticSymbol) {
if (symbol.members.length) {
throw new Error(
`Internal state: StaticSymbols in summaries can't have members! ${JSON.stringify(symbol)}`);
}
}
private _cacheKey(symbol: StaticSymbol) { return `${symbol.filePath}|${symbol.name}`; }
resolveSummary(staticSymbol: StaticSymbol): Summary<StaticSymbol> {
this._assertNoMembers(staticSymbol);
let summary = this.summaryCache.get(staticSymbol);
if (!summary) {
this._loadSummaryFile(staticSymbol.filePath);
summary = this.summaryCache.get(staticSymbol);
}
return summary;
}
resolveSummary(staticSymbol: StaticSymbol): any {
const filePath = staticSymbol.filePath;
const name = staticSymbol.name;
const cacheKey = this._cacheKey(staticSymbol);
if (!filterFileByPatterns(filePath, this.options)) {
let summary = this.summaryCache[cacheKey];
getSymbolsOf(filePath: string): StaticSymbol[] {
this._loadSummaryFile(filePath);
return Array.from(this.summaryCache.keys()).filter((symbol) => symbol.filePath === filePath);
}
private _loadSummaryFile(filePath: string) {
if (this.loadedFilePaths.has(filePath)) {
return;
}
this.loadedFilePaths.add(filePath);
if (!this.host.isSourceFile(filePath)) {
const summaryFilePath = summaryFileName(filePath);
if (!summary) {
try {
const jsonReviver = (key: string, value: any) => {
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;
}
};
const readSummaries: CompileTypeSummary[] =
JSON.parse(this.host.loadSummary(summaryFilePath), jsonReviver);
readSummaries.forEach((summary) => {
const filePath = summary.type.reference.filePath;
this.summaryCache[this._cacheKey(summary.type.reference)] = summary;
});
summary = this.summaryCache[cacheKey];
} catch (e) {
console.error(`Error loading summary file ${summaryFilePath}`);
throw e;
}
let json: string;
try {
json = this.host.loadSummary(summaryFilePath);
} catch (e) {
console.error(`Error loading summary file ${summaryFilePath}`);
throw e;
}
if (!summary) {
throw new Error(
`Could not find the symbol ${name} in the summary file ${summaryFilePath}!`);
if (json) {
const readSummaries = deserializeSummaries(this.staticSymbolCache, json);
readSummaries.forEach((summary) => { this.summaryCache.set(summary.symbol, summary); });
}
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 enum CompileSummaryKind {
Template,
Pipe,
Directive,
NgModule
NgModule,
Injectable
}
/**
@ -126,9 +126,10 @@ export enum CompileSummaryKind {
* in other modules / components. However, this data is not enough to compile
* the directive / module itself.
*/
export interface CompileSummary { summaryKind: CompileSummaryKind; }
export interface CompileTypeSummary extends CompileSummary { type: CompileTypeMetadata; }
export interface CompileTypeSummary {
summaryKind: CompileSummaryKind;
type: CompileTypeMetadata;
}
export interface CompileDiDependencyMetadata {
isAttribute?: boolean;
@ -210,7 +211,7 @@ export class CompileStylesheetMetadata {
/**
* Summary Metadata regarding compilation of a template.
*/
export interface CompileTemplateSummary extends CompileSummary {
export interface CompileTemplateSummary {
animations: string[];
ngContentSelectors: string[];
encapsulation: ViewEncapsulation;
@ -258,7 +259,6 @@ export class CompileTemplateMetadata {
toSummary(): CompileTemplateSummary {
return {
summaryKind: CompileSummaryKind.Template,
animations: this.animations.map(anim => anim.name),
ngContentSelectors: this.ngContentSelectors,
encapsulation: this.encapsulation

View File

@ -6,11 +6,12 @@
* 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 {CompilerConfig} from './config';
import {isBlank, isPresent, stringify} from './facade/lang';
import {CompilerInjectable} from './injectable';
import * as html from './ml_parser/ast';
import {HtmlParser} from './ml_parser/html_parser';
import {InterpolationConfig} from './ml_parser/interpolation_config';
@ -18,7 +19,7 @@ import {ResourceLoader} from './resource_loader';
import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver';
import {PreparsedElementType, preparseElement} from './template_parser/template_preparser';
import {UrlResolver} from './url_resolver';
import {SyncAsyncResult} from './util';
import {SyncAsyncResult, SyntaxError} from './util';
export interface PrenormalizedTemplateMetadata {
componentType: any;
@ -32,7 +33,7 @@ export interface PrenormalizedTemplateMetadata {
animations?: CompileAnimationEntryMetadata[];
}
@Injectable()
@CompilerInjectable()
export class DirectiveNormalizer {
private _resourceLoaderCache = new Map<string, Promise<string>>();
@ -70,7 +71,7 @@ export class DirectiveNormalizer {
} else if (prenormData.templateUrl) {
normalizedTemplateAsync = this.normalizeTemplateAsync(prenormData);
} else {
throw new Error(
throw new SyntaxError(
`No template specified for component ${stringify(prenormData.componentType)}`);
}
@ -104,7 +105,7 @@ export class DirectiveNormalizer {
template, stringify(prenomData.componentType), false, interpolationConfig);
if (rootNodesAndErrors.errors.length > 0) {
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({
styles: prenomData.styles,

View File

@ -6,14 +6,16 @@
* 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 {stringify} from './facade/lang';
import {CompilerInjectable} from './injectable';
import {ReflectorReader, reflector} from './private_import_core';
import {splitAtColon} from './util';
/*
* Resolve a `Type` for {@link Directive}.
*
@ -21,7 +23,7 @@ import {splitAtColon} from './util';
*
* See {@link Compiler}
*/
@Injectable()
@CompilerInjectable()
export class DirectiveResolver {
constructor(private _reflector: ReflectorReader = reflector) {}

View File

@ -6,8 +6,6 @@
* 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 {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util';
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 {Parser} from './expression_parser/parser';
import {Identifiers, createIdentifier} from './identifiers';
import {CompilerInjectable} from './injectable';
import {DEFAULT_INTERPOLATION_CONFIG} from './ml_parser/interpolation_config';
import {ClassBuilder, createClassStmt} from './output/class_builder';
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.
*/
@Injectable()
@CompilerInjectable()
export class DirectiveWrapperCompiler {
static dirWrapperClassName(id: CompileIdentifierMetadata) {
return `Wrapper_${identifierName(id)}`;

View File

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

View File

@ -6,10 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable} from '@angular/core';
import * as chars from '../chars';
import {escapeRegExp, isBlank, isPresent} from '../facade/lang';
import {CompilerInjectable} from '../injectable';
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';
@ -31,7 +30,7 @@ function _createInterpolateRegExp(config: InterpolationConfig): RegExp {
return new RegExp(pattern, 'g');
}
@Injectable()
@CompilerInjectable()
export class Parser {
private errors: ParserError[] = [];

View File

@ -14,7 +14,9 @@ import {ViewEncapsulation} from '@angular/core';
import {analyzeAndValidateNgModules, extractProgramSymbols} from '../aot/compiler';
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 {CompileDirectiveMetadata} from '../compile_metadata';
import {CompilerConfig} from '../config';
@ -26,23 +28,17 @@ import {InterpolationConfig} from '../ml_parser/interpolation_config';
import {NgModuleResolver} from '../ng_module_resolver';
import {ParseError} from '../parse_util';
import {PipeResolver} from '../pipe_resolver';
import {Console} from '../private_import_core';
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
import {createOfflineCompileUrlResolver} from '../url_resolver';
import {I18NHtmlParser} from './i18n_html_parser';
import {MessageBundle} from './message_bundle';
export interface ExtractorOptions {
includeFilePattern?: RegExp;
excludeFilePattern?: RegExp;
}
/**
* The host of the Extractor disconnects the implementation from TypeScript / other language
* 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)
*/
@ -51,14 +47,13 @@ export interface ExtractorHost extends StaticReflectorHost, AotSummaryResolverHo
export class Extractor {
constructor(
private options: ExtractorOptions, public host: ExtractorHost,
private staticReflector: StaticReflector, private messageBundle: MessageBundle,
private metadataResolver: CompileMetadataResolver) {}
public host: ExtractorHost, private staticSymbolResolver: StaticSymbolResolver,
private messageBundle: MessageBundle, private metadataResolver: CompileMetadataResolver) {}
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} =
analyzeAndValidateNgModules(programSymbols, this.options, this.metadataResolver);
analyzeAndValidateNgModules(programSymbols, this.host, this.metadataResolver);
return Promise
.all(ngModules.map(
ngModule => this.metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
@ -91,12 +86,14 @@ export class Extractor {
});
}
static create(host: ExtractorHost, options: ExtractorOptions):
{extractor: Extractor, staticReflector: StaticReflector} {
static create(host: ExtractorHost): {extractor: Extractor, staticReflector: StaticReflector} {
const htmlParser = new I18NHtmlParser(new HtmlParser());
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);
const config = new CompilerConfig({
@ -111,13 +108,13 @@ export class Extractor {
const elementSchemaRegistry = new DomElementSchemaRegistry();
const resolver = new CompileMetadataResolver(
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
new PipeResolver(staticReflector), new AotSummaryResolver(host, staticReflector, options),
elementSchemaRegistry, normalizer, staticReflector);
new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer,
staticReflector);
// TODO(vicb): implicit tags & attributes
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};
}
}

View File

@ -6,7 +6,7 @@
* 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 {MessageBundle} from './message_bundle';
export {Serializer} from './serializers/serializer';

View File

@ -39,6 +39,7 @@ const _DOCTYPE = `<!ELEMENT messagebundle (msg)*>
export class Xmb implements Serializer {
write(messages: i18n.Message[]): string {
const exampleVisitor = new ExampleVisitor();
const visitor = new _Visitor();
const visited: {[id: string]: boolean} = {};
let rootNode = new xml.Tag(_MESSAGES_TAG);
@ -71,7 +72,7 @@ export class Xmb implements Serializer {
new xml.CR(),
new xml.Doctype(_MESSAGES_TAG, _DOCTYPE),
new xml.CR(),
rootNode,
exampleVisitor.addDefaultExamples(rootNode),
new xml.CR(),
]);
}
@ -133,4 +134,27 @@ class _Visitor implements i18n.Visitor {
export function digest(message: i18n.Message): string {
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
*/
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 {AnimationParser} from '../animation/animation_parser';
@ -15,6 +15,7 @@ import {CompilerConfig} from '../config';
import {DirectiveNormalizer} from '../directive_normalizer';
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
import {stringify} from '../facade/lang';
import {CompilerInjectable} from '../injectable';
import {CompileMetadataResolver} from '../metadata_resolver';
import {NgModuleCompiler} from '../ng_module_compiler';
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
* application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security).
*/
@Injectable()
@CompilerInjectable()
export class JitCompiler implements Compiler {
private _compiledTemplateCache = 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
*/
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 {CompilerConfig} from '../config';
@ -16,6 +16,7 @@ import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
import {Lexer} from '../expression_parser/lexer';
import {Parser} from '../expression_parser/parser';
import * as i18n from '../i18n/index';
import {CompilerInjectable} from '../injectable';
import {CompileMetadataResolver} from '../metadata_resolver';
import {HtmlParser} from '../ml_parser/html_parser';
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 {
private _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 {isBlank, isPresent, stringify} from './facade/lang';
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers';
import {CompilerInjectable} from './injectable';
import {hasLifecycleHook} from './lifecycle_reflector';
import {NgModuleResolver} from './ng_module_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 {SummaryResolver} from './summary_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 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
// not required to check that the user would have been able
// to wait correctly.
@Injectable()
@CompilerInjectable()
export class CompileMetadataResolver {
private _directiveCache = new Map<Type<any>, cpl.CompileDirectiveMetadata>();
private _summaryCache = new Map<Type<any>, cpl.CompileTypeSummary>();
@ -45,7 +46,7 @@ export class CompileMetadataResolver {
constructor(
private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver,
private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver,
private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver<any>,
private _schemaRegistry: ElementSchemaRegistry,
private _directiveNormalizer: DirectiveNormalizer,
private _reflector: ReflectorReader = reflector,
@ -128,12 +129,13 @@ export class CompileMetadataResolver {
}
private _loadSummary(type: any, kind: cpl.CompileSummaryKind): cpl.CompileTypeSummary {
let summary = this._summaryCache.get(type);
if (!summary) {
summary = this._summaryResolver.resolveSummary(type);
this._summaryCache.set(type, summary);
let typeSummary = this._summaryCache.get(type);
if (!typeSummary) {
const summary = this._summaryResolver.resolveSummary(type);
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> {
@ -237,7 +239,7 @@ export class CompileMetadataResolver {
if (dirMeta.viewProviders) {
viewProviders = this._getProvidersMetadata(
dirMeta.viewProviders, entryComponentMetadata,
`viewProviders for "${stringify(directiveType)}"`, [], directiveType);
`viewProviders for "${stringifyType(directiveType)}"`, [], directiveType);
}
if (dirMeta.entryComponents) {
entryComponentMetadata = flattenAndDedupeArray(dirMeta.entryComponents)
@ -251,7 +253,8 @@ export class CompileMetadataResolver {
// Directive
if (!selector) {
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);
selector = 'error';
}
@ -260,8 +263,8 @@ export class CompileMetadataResolver {
let providers: cpl.CompileProviderMetadata[] = [];
if (isPresent(dirMeta.providers)) {
providers = this._getProvidersMetadata(
dirMeta.providers, entryComponentMetadata, `providers for "${stringify(directiveType)}"`,
[], directiveType);
dirMeta.providers, entryComponentMetadata,
`providers for "${stringifyType(directiveType)}"`, [], directiveType);
}
let queries: cpl.CompileQueryMetadata[] = [];
let viewQueries: cpl.CompileQueryMetadata[] = [];
@ -297,8 +300,8 @@ export class CompileMetadataResolver {
const dirMeta = this._directiveCache.get(directiveType);
if (!dirMeta) {
this._reportError(
new Error(
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringify(directiveType)}.`),
new SyntaxError(
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringifyType(directiveType)}.`),
directiveType);
}
return dirMeta;
@ -309,8 +312,8 @@ export class CompileMetadataResolver {
<cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
if (!dirSummary) {
this._reportError(
new Error(
`Illegal state: Could not load the summary for directive ${stringify(dirType)}.`),
new SyntaxError(
`Illegal state: Could not load the summary for directive ${stringifyType(dirType)}.`),
dirType);
}
return dirSummary;
@ -383,7 +386,8 @@ export class CompileMetadataResolver {
if (moduleWithProviders.providers) {
providers.push(...this._getProvidersMetadata(
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);
if (!importedModuleSummary) {
this._reportError(
new Error(
`Unexpected ${this._getTypeDescriptor(importedType)} '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`),
new SyntaxError(
`Unexpected ${this._getTypeDescriptor(importedType)} '${stringifyType(importedType)}' imported by the module '${stringifyType(moduleType)}'`),
moduleType);
return;
}
importedModules.push(importedModuleSummary);
} else {
this._reportError(
new Error(
`Unexpected value '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`),
new SyntaxError(
`Unexpected value '${stringifyType(importedType)}' imported by the module '${stringifyType(moduleType)}'`),
moduleType);
return;
}
@ -411,8 +415,8 @@ export class CompileMetadataResolver {
flattenAndDedupeArray(meta.exports).forEach((exportedType) => {
if (!isValidType(exportedType)) {
this._reportError(
new Error(
`Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`),
new SyntaxError(
`Unexpected value '${stringifyType(exportedType)}' exported by the module '${stringifyType(moduleType)}'`),
moduleType);
return;
}
@ -432,8 +436,8 @@ export class CompileMetadataResolver {
flattenAndDedupeArray(meta.declarations).forEach((declaredType) => {
if (!isValidType(declaredType)) {
this._reportError(
new Error(
`Unexpected value '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`),
new SyntaxError(
`Unexpected value '${stringifyType(declaredType)}' declared by the module '${stringifyType(moduleType)}'`),
moduleType);
return;
}
@ -449,8 +453,8 @@ export class CompileMetadataResolver {
this._addTypeToModule(declaredType, moduleType);
} else {
this._reportError(
new Error(
`Unexpected ${this._getTypeDescriptor(declaredType)} '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`),
new SyntaxError(
`Unexpected ${this._getTypeDescriptor(declaredType)} '${stringifyType(declaredType)}' declared by the module '${stringifyType(moduleType)}'`),
moduleType);
return;
}
@ -468,8 +472,8 @@ export class CompileMetadataResolver {
transitiveModule.addExportedPipe(exportedId);
} else {
this._reportError(
new Error(
`Can't export ${this._getTypeDescriptor(exportedId.reference)} ${stringify(exportedId.reference)} from ${stringify(moduleType)} as it was neither declared nor imported!`),
new SyntaxError(
`Can't export ${this._getTypeDescriptor(exportedId.reference)} ${stringifyType(exportedId.reference)} from ${stringifyType(moduleType)} as it was neither declared nor imported!`),
moduleType);
}
});
@ -478,25 +482,25 @@ export class CompileMetadataResolver {
// so that they overwrite any other provider we already added.
if (meta.providers) {
providers.push(...this._getProvidersMetadata(
meta.providers, entryComponents, `provider for the NgModule '${stringify(moduleType)}'`,
[], moduleType));
meta.providers, entryComponents,
`provider for the NgModule '${stringifyType(moduleType)}'`, [], moduleType));
}
if (meta.entryComponents) {
entryComponents.push(
...flattenAndDedupeArray(meta.entryComponents).map(type => this._getTypeMetadata(type)));
entryComponents.push(...flattenAndDedupeArray(meta.entryComponents)
.map(type => this._getIdentifierMetadata(type)));
}
if (meta.bootstrap) {
flattenAndDedupeArray(meta.bootstrap).forEach(type => {
if (!isValidType(type)) {
this._reportError(
new Error(
`Unexpected value '${stringify(type)}' used in the bootstrap property of module '${stringify(moduleType)}'`),
new SyntaxError(
`Unexpected value '${stringifyType(type)}' used in the bootstrap property of module '${stringifyType(moduleType)}'`),
moduleType);
return;
}
bootstrapComponents.push(this._getTypeMetadata(type));
bootstrapComponents.push(this._getIdentifierMetadata(type));
});
}
@ -554,10 +558,10 @@ export class CompileMetadataResolver {
const oldModule = this._ngModuleOfTypes.get(type);
if (oldModule && oldModule !== moduleType) {
this._reportError(
new Error(
`Type ${stringify(type)} is part of the declarations of 2 modules: ${stringify(oldModule)} and ${stringify(moduleType)}! ` +
`Please consider moving ${stringify(type)} to a higher module that imports ${stringify(oldModule)} and ${stringify(moduleType)}. ` +
`You can also create a new NgModule that exports and includes ${stringify(type)} then import that NgModule in ${stringify(oldModule)} and ${stringify(moduleType)}.`),
new SyntaxError(
`Type ${stringifyType(type)} is part of the declarations of 2 modules: ${stringifyType(oldModule)} and ${stringifyType(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 ${stringifyType(type)} then import that NgModule in ${stringifyType(oldModule)} and ${stringifyType(moduleType)}.`),
moduleType);
}
this._ngModuleOfTypes.set(type, moduleType);
@ -606,6 +610,26 @@ export class CompileMetadataResolver {
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 {
const identifier = this._getIdentifierMetadata(type);
return {
@ -630,8 +654,8 @@ export class CompileMetadataResolver {
const pipeMeta = this._pipeCache.get(pipeType);
if (!pipeMeta) {
this._reportError(
new Error(
`Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringify(pipeType)}.`),
new SyntaxError(
`Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringifyType(pipeType)}.`),
pipeType);
}
return pipeMeta;
@ -642,7 +666,8 @@ export class CompileMetadataResolver {
<cpl.CompilePipeSummary>this._loadSummary(pipeType, cpl.CompileSummaryKind.Pipe);
if (!pipeSummary) {
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);
}
return pipeSummary;
@ -722,9 +747,10 @@ export class CompileMetadataResolver {
if (hasUnknownDeps) {
const depsTokens =
dependenciesMetadata.map((dep) => dep ? stringify(dep.token) : '?').join(', ');
dependenciesMetadata.map((dep) => dep ? stringifyType(dep.token) : '?').join(', ');
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);
}
@ -761,9 +787,9 @@ export class CompileMetadataResolver {
(<string[]>providers.reduce(
(soFar: string[], seenProvider: any, seenProviderIdx: number) => {
if (seenProviderIdx < providerIdx) {
soFar.push(`${stringify(seenProvider)}`);
soFar.push(`${stringifyType(seenProvider)}`);
} else if (seenProviderIdx == providerIdx) {
soFar.push(`?${stringify(seenProvider)}?`);
soFar.push(`?${stringifyType(seenProvider)}?`);
} else if (seenProviderIdx == providerIdx + 1) {
soFar.push('...');
}
@ -772,7 +798,7 @@ export class CompileMetadataResolver {
[]))
.join(', ');
this._reportError(
new Error(
new SyntaxError(
`Invalid ${debugInfo ? debugInfo : 'provider'} - only instances of Provider and Type are allowed, got: [${providersInfo}]`),
type);
}
@ -793,19 +819,21 @@ export class CompileMetadataResolver {
if (provider.useFactory || provider.useExisting || provider.useClass) {
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 [];
}
if (!provider.multi) {
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 [];
}
extractIdentifiers(provider.useValue, collectedIdentifiers);
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);
}
});
@ -819,7 +847,7 @@ export class CompileMetadataResolver {
let token: cpl.CompileTokenMetadata = this._getTokenMetadata(provider.token);
if (provider.useClass) {
compileTypeMetadata = this._getTypeMetadata(provider.useClass, provider.dependencies);
compileTypeMetadata = this._getInjectableMetadata(provider.useClass, provider.dependencies);
compileDeps = compileTypeMetadata.diDeps;
if (provider.token === provider.useClass) {
// use the compileTypeMetadata as it contains information about lifecycleHooks...
@ -867,8 +895,8 @@ export class CompileMetadataResolver {
} else {
if (!q.selector) {
this._reportError(
new Error(
`Can't construct a query for the property "${propertyName}" of "${stringify(typeOrFunc)}" since the query selector wasn't defined.`),
new SyntaxError(
`Can't construct a query for the property "${propertyName}" of "${stringifyType(typeOrFunc)}" since the query selector wasn't defined.`),
typeOrFunc);
}
selectors = [this._getTokenMetadata(q.selector)];
@ -935,8 +963,8 @@ export function componentModuleUrl(
const scheme = getUrlScheme(moduleId);
return scheme ? moduleId : `package:${moduleId}${MODULE_SUFFIX}`;
} else if (moduleId !== null && moduleId !== void 0) {
throw new Error(
`moduleId should be a string in "${stringify(type)}". See https://goo.gl/wIDDiL for more information.\n` +
throw new SyntaxError(
`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.`);
}
@ -952,3 +980,11 @@ class _CompileValueConverter extends ValueTransformer {
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
*/
import {Injectable} from '@angular/core';
import {CompilerInjectable} from '../injectable';
import {getHtmlTagDefinition} from './html_tags';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation_config';
@ -14,7 +14,7 @@ import {ParseTreeResult, Parser} from './parser';
export {ParseTreeResult, TreeError} from './parser';
@Injectable()
@CompilerInjectable()
export class HtmlParser extends Parser {
constructor() { super(getHtmlTagDefinition); }

View File

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

View File

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

View File

@ -6,12 +6,11 @@
* 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 {createDiTokenExpression} from './compiler_util/identifier_util';
import {isPresent} from './facade/lang';
import {Identifiers, createIdentifier, createIdentifierToken, resolveIdentifier} from './identifiers';
import {CompilerInjectable} from './injectable';
import {ClassBuilder, createClassStmt} from './output/class_builder';
import * as o from './output/output_ast';
import {convertValueToOutputAst} from './output/value_util';
@ -31,7 +30,7 @@ export class NgModuleCompileResult {
public dependencies: ComponentFactoryDependency[]) {}
}
@Injectable()
@CompilerInjectable()
export class NgModuleCompiler {
compile(ngModuleMeta: CompileNgModuleMetadata, extraProviders: CompileProviderMetadata[]):
NgModuleCompileResult {

View File

@ -6,10 +6,11 @@
* 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 {isPresent, stringify} from './facade/lang';
import {CompilerInjectable} from './injectable';
import {ReflectorReader, reflector} from './private_import_core';
function _isNgModuleMetadata(obj: any): obj is NgModule {
@ -19,7 +20,7 @@ function _isNgModuleMetadata(obj: any): obj is NgModule {
/**
* Resolves types to {@link NgModule}.
*/
@Injectable()
@CompilerInjectable()
export class NgModuleResolver {
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.
* 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}.`);
}
if (value.reference && value.reference.members) {
if (value.reference && value.reference.members && value.reference.members.length) {
ctx.print(value.reference.name);
ctx.print('.');
ctx.print(value.reference.members.join('.'));

View File

@ -6,10 +6,11 @@
* 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 {isPresent, stringify} from './facade/lang';
import {CompilerInjectable} from './injectable';
import {ReflectorReader, reflector} from './private_import_core';
function _isPipeMetadata(type: any): boolean {
@ -23,7 +24,7 @@ function _isPipeMetadata(type: any): boolean {
*
* See {@link Compiler}
*/
@Injectable()
@CompilerInjectable()
export class PipeResolver {
constructor(private _reflector: ReflectorReader = reflector) {}

View File

@ -6,7 +6,8 @@
* 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';
@ -238,7 +239,7 @@ const _ATTR_TO_PROP: {[name: string]: string} = {
'tabindex': 'tabIndex',
};
@Injectable()
@CompilerInjectable()
export class DomElementSchemaRegistry extends ElementSchemaRegistry {
private _schema: {[element: string]: {[property: string]: string}} = {};

View File

@ -6,9 +6,10 @@
* 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 {CompilerInjectable} from './injectable';
import * as o from './output/output_ast';
import {ShadowCss} from './shadow_css';
import {UrlResolver} from './url_resolver';
@ -36,7 +37,7 @@ export class CompiledStylesheet {
public meta: CompileStylesheetMetadata) {}
}
@Injectable()
@CompilerInjectable()
export class StyleCompiler {
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
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable} from '@angular/core';
import {CompileTypeSummary} from './compile_metadata';
import {CompilerInjectable} from './injectable';
@Injectable()
export class SummaryResolver {
resolveSummary(reference: any): CompileTypeSummary { return null; }
export interface Summary<T> {
symbol: T;
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
*/
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 {Parser} from '../expression_parser/parser';
import {isPresent} from '../facade/lang';
import {I18NHtmlParser} from '../i18n/i18n_html_parser';
import {Identifiers, createIdentifierToken, identifierToken} from '../identifiers';
import {CompilerInjectable} from '../injectable';
import * as html from '../ml_parser/ast';
import {ParseTreeResult} from '../ml_parser/html_parser';
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 {CssSelector, SelectorMatcher} from '../selector';
import {isStyleUrlResolvable} from '../style_url_resolver';
import {SyntaxError} from '../util';
import {BindingParser, BoundProperty} from './binding_parser';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from './template_ast';
import {PreparsedElementType, preparseElement} from './template_preparser';
@ -79,7 +79,7 @@ export class TemplateParseResult {
constructor(public templateAst?: TemplateAst[], public errors?: ParseError[]) {}
}
@Injectable()
@CompilerInjectable()
export class TemplateParser {
constructor(
private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry,
@ -99,7 +99,7 @@ export class TemplateParser {
if (errors.length > 0) {
const errorString = errors.join('\n');
throw new Error(`Template parse errors:\n${errorString}`);
throw new SyntaxError(`Template parse errors:\n${errorString}`);
}
return result.templateAst;

View File

@ -6,9 +6,11 @@
* 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 {CompilerInjectable} from './injectable';
/**
* 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
* application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security).
*/
@Injectable()
@CompilerInjectable()
export class UrlResolver {
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
*/
import {BaseError} from './facade/errors';
import {isPrimitive, isStrictStringMap} from './facade/lang';
export const MODULE_SUFFIX = '';
const CAMEL_CASE_REGEXP = /([A-Z])/g;
@ -78,3 +78,5 @@ export class SyncAsyncResult<T> {
}
}
}
export class SyntaxError extends BaseError {}

View File

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

View File

@ -6,26 +6,25 @@
* 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 {MetadataCollector} from '@angular/tsc-wrapped';
import * as ts from 'typescript';
// This matches .ts files but not .d.ts files.
const TS_EXT = /(^.|(?!\.d)..)\.ts$/;
import {MockStaticSymbolResolverHost, MockSummaryResolver} from './static_symbol_resolver_spec';
describe('StaticReflector', () => {
const noContext = new StaticSymbol('', '');
let host: StaticReflectorHost;
let noContext: StaticSymbol;
let host: StaticSymbolResolverHost;
let symbolResolver: StaticSymbolResolver;
let reflector: StaticReflector;
function init(
testData: {[key: string]: any} = DEFAULT_TEST_DATA,
decorators: {name: string, filePath: string, ctor: any}[] = []) {
host = new MockStaticReflectorHost(testData);
reflector = new StaticReflector(host, undefined, decorators);
const symbolCache = new StaticSymbolCache();
host = new MockStaticSymbolResolverHost(testData);
symbolResolver = new StaticSymbolResolver(host, symbolCache, new MockSummaryResolver([]));
reflector = new StaticReflector(symbolResolver, decorators);
noContext = reflector.getStaticSymbol('', '');
}
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', () => {
const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass');
const annotations = reflector.annotations(UnknownClass);
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', () => {
const HeroDetailComponent =
reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
@ -129,7 +126,7 @@ describe('StaticReflector', () => {
});
it('should simplify a static symbol into itself', () => {
const staticSymbol = new StaticSymbol('', '');
const staticSymbol = reflector.getStaticSymbol('', '');
expect(simplify(noContext, staticSymbol)).toBe(staticSymbol);
});
@ -306,49 +303,43 @@ describe('StaticReflector', () => {
expect(simplify(noContext, expr)).toBe(2);
});
it('should simplify a module reference', () => {
it('should simplify a file reference', () => {
expect(simplify(
new StaticSymbol('/src/cases', ''),
({__symbolic: 'reference', module: './extern', name: 's'})))
reflector.getStaticSymbol('/src/cases', ''),
reflector.getStaticSymbol('/src/extern.d.ts', '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', () => {
expect(simplify(
new StaticSymbol('/src/cases', ''),
({__symbolic: 'reference', module: './extern', name: 'nonExisting'})))
reflector.getStaticSymbol('/src/cases', ''),
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', () => {
expect(simplify(
new StaticSymbol('/src/cases', 'myFunction'),
reflector.getStaticSymbol('/src/cases', 'myFunction'),
({__symbolic: 'function', parameters: ['a'], value: []})))
.toEqual(reflector.getStaticSymbol('/src/cases', 'myFunction'));
});
it('should simplify values initialized with a function call', () => {
expect(simplify(new StaticSymbol('/tmp/src/function-reference.ts', ''), {
__symbolic: 'reference',
name: 'one'
})).toEqual(['some-value']);
expect(simplify(new StaticSymbol('/tmp/src/function-reference.ts', ''), {
__symbolic: 'reference',
name: 'three'
})).toEqual(3);
expect(simplify(
reflector.getStaticSymbol('/tmp/src/function-reference.ts', ''),
reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'one')))
.toEqual(['some-value']);
expect(simplify(
reflector.getStaticSymbol('/tmp/src/function-reference.ts', ''),
reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'three')))
.toEqual(3);
});
it('should error on direct recursive calls', () => {
expect(
() => simplify(
new StaticSymbol('/tmp/src/function-reference.ts', ''),
{__symbolic: 'reference', name: 'recursion'}))
reflector.getStaticSymbol('/tmp/src/function-reference.ts', ''),
reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'recursion')))
.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'));
});
@ -362,7 +353,8 @@ describe('StaticReflector', () => {
expect(moduleMetadata).toBeDefined();
const classData: any = moduleMetadata['InvalidMetadata'];
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) {
expect(e.fileName).toBe('/tmp/src/invalid-metadata.ts');
threw = true;
@ -373,48 +365,17 @@ describe('StaticReflector', () => {
it('should error on indirect recursive calls', () => {
expect(
() => simplify(
new StaticSymbol('/tmp/src/function-reference.ts', ''),
{__symbolic: 'reference', name: 'indirectRecursion'}))
reflector.getStaticSymbol('/tmp/src/function-reference.ts', ''),
reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'indirectRecursion')))
.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'));
});
it('should simplify a spread expression', () => {
expect(simplify(new StaticSymbol('/tmp/src/spread.ts', ''), {
__symbolic: 'reference',
name: 'spread'
})).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'
}
}]
}]
}
}
}
});
expect(simplify(
reflector.getStaticSymbol('/tmp/src/spread.ts', ''),
reflector.getStaticSymbol('/tmp/src/spread.ts', 'spread')))
.toEqual([0, 1, 2, 3, 4, 5]);
});
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');
});
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', () => {
class ClassDecorator {
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} = {
'/tmp/@angular/common/src/forms-deprecated/directives.d.ts': [{
'__symbolic': 'module',
@ -1008,8 +841,6 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
}
},
'/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': {
__symbolic: 'module',
version: 3,
@ -1343,47 +1174,4 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
@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
*/
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 {MockStaticReflectorHost} from './static_reflector_spec';
import {MockStaticSymbolResolverHost, MockSummaryResolver} from './static_symbol_resolver_spec';
const EXT = /\.ts$|.d.ts$/;
export function main() {
describe('AotSummaryResolver', () => {
let resolver: AotSummaryResolver;
let staticReflector: StaticReflector;
let summaryResolver: AotSummaryResolver;
let symbolCache: StaticSymbolCache;
let host: MockAotSummaryResolverHost;
beforeEach(() => { symbolCache = new StaticSymbolCache(); });
function init(summaries: {[filePath: string]: string} = {}) {
// Note: We don't give the static reflector metadata files,
// so that we can test that we can deserialize summary files
// 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$/});
host = new MockAotSummaryResolverHost(summaries);
summaryResolver = new AotSummaryResolver(host, symbolCache);
}
it('should add .ngsummary.json to the filename', () => {
init();
expect(resolver.serializeSummaries('a.ts', []).genFileUrl).toBe('a.ngsummary.json');
expect(resolver.serializeSummaries('a.d.ts', []).genFileUrl).toBe('a.ngsummary.json');
expect(resolver.serializeSummaries('a.js', []).genFileUrl).toBe('a.ngsummary.json');
function serialize(symbols: ResolvedStaticSymbol[], types: CompileTypeSummary[]): string {
// Note: Don't use the top level host / summaryResolver as they might not be created yet
const mockSummaryResolver = new MockSummaryResolver([]);
const symbolResolver = new StaticSymbolResolver(
new MockStaticSymbolResolverHost({}), symbolCache, mockSummaryResolver);
return serializeSummaries(
new MockAotSummarySerializerHost(), mockSummaryResolver, symbolResolver, symbols, types);
}
it('should load serialized summary files', () => {
const asymbol = symbolCache.get('/a.d.ts', 'a');
init({'/a.ngsummary.json': serialize([{symbol: asymbol, metadata: 1}], [])});
expect(summaryResolver.resolveSummary(asymbol)).toEqual({symbol: asymbol, metadata: 1});
});
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'])
}]);
it('should not load summaries for source files', () => {
init({});
spyOn(host, 'loadSummary').and.callThrough();
// 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']));
expect(summaryResolver.resolveSummary(symbolCache.get('/a.ts', 'a'))).toBeFalsy();
expect(host.loadSummary).not.toHaveBeenCalled();
});
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});
resolver.resolveSummary(
staticReflector.getStaticSymbol('/tmp/some_module.d.ts', 'SomeModule'));
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 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 {
return './' + path.basename(fileName).replace(EXT, '');
}
@ -135,4 +74,13 @@ class MockAotSummaryResolverHost implements AotSummaryResolverHost {
getOutputFileName(sourceFileName: string): string {
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
* 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 {CompilerConfig} from '@angular/compiler/src/config';
import {DirectiveNormalizer} from '@angular/compiler/src/directive_normalizer';
@ -31,7 +31,7 @@ export function main() {
expect(() => normalizer.normalizeTemplate({
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
})).toThrowError('No template specified for component SomeComp');
})).toThrowError(SyntaxError, 'No template specified for component SomeComp');
}));
});

View File

@ -46,7 +46,7 @@ export function main(): void {
<!ELEMENT ex (#PCDATA)>
]>
<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="7999024498831672133" desc="d" meaning="m">foo</msg>
<msg id="i" desc="d" meaning="m">foo</msg>

View File

@ -15,8 +15,8 @@ import {identifierName} from '../src/compile_metadata';
import {stringify} from '../src/facade/lang';
import {CompileMetadataResolver} from '../src/metadata_resolver';
import {ResourceLoader} from '../src/resource_loader';
import {SyntaxError} from '../src/util';
import {MockResourceLoader} from '../testing/resource_loader_mock';
import {MalformedStylesComponent} from './metadata_resolver_fixture';
export function main() {
@ -34,8 +34,9 @@ export function main() {
}
expect(() => resolver.getDirectiveMetadata(ComponentWithEverythingInline))
.toThrowError(/Illegal state/);
expect(() => resolver.getPipeMetadata(SomePipe)).toThrowError(/Illegal state/);
.toThrowError(SyntaxError, /Illegal state/);
expect(() => resolver.getPipeMetadata(SomePipe))
.toThrowError(SyntaxError, /Illegal state/);
}));
it('should read metadata in sync for components with inline resources',
@ -121,10 +122,10 @@ export function main() {
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, true))
.toThrowError(
`moduleId should be a string in "ComponentWithInvalidModuleId". 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.`);
SyntaxError, `moduleId should be a string in "ComponentWithInvalidModuleId". 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.`);
}));
@ -145,7 +146,7 @@ export function main() {
}
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',
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
@ -155,6 +156,7 @@ export function main() {
expect(
() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithImportedComponent, true))
.toThrowError(
SyntaxError,
`Unexpected directive 'ComponentWithoutModuleId' imported by the module 'ModuleWithImportedComponent'`);
}));
@ -168,6 +170,7 @@ export function main() {
}
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithImportedPipe, true))
.toThrowError(
SyntaxError,
`Unexpected pipe 'SomePipe' imported by the module 'ModuleWithImportedPipe'`);
}));
@ -181,6 +184,7 @@ export function main() {
}
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithDeclaredModule, true))
.toThrowError(
SyntaxError,
`Unexpected module 'SomeModule' declared by the module 'ModuleWithDeclaredModule'`);
}));
@ -191,6 +195,7 @@ export function main() {
}
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithNullDeclared, true))
.toThrowError(
SyntaxError,
`Unexpected value 'null' declared by the module 'ModuleWithNullDeclared'`);
}));
@ -201,6 +206,7 @@ export function main() {
}
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithNullImported, true))
.toThrowError(
SyntaxError,
`Unexpected value 'null' imported by the module 'ModuleWithNullImported'`);
}));
@ -212,7 +218,8 @@ export function main() {
}
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',
@ -223,6 +230,7 @@ export function main() {
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, true))
.toThrowError(
SyntaxError,
`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))
.toThrowError(
SyntaxError,
`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))
.toThrowError(
SyntaxError,
`Unexpected value 'null' used in the bootstrap property of module 'ModuleWithNullBootstrap'`);
expect(
() =>
resolver.loadNgModuleDirectiveAndPipeMetadata(ModuleWithUndefinedBootstrap, true))
.toThrowError(
SyntaxError,
`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
*/
import {beforeEach, describe, expect, it} from '../../../core/testing/testing_internal';
import * as html from '../../src/ml_parser/ast';
import {HtmlParser, ParseTreeResult, TreeError} from '../../src/ml_parser/html_parser';
import {TokenType} from '../../src/ml_parser/lexer';
@ -304,6 +303,18 @@ export function main() {
]))).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', () => {
const parsed = parser.parse(
`{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
*/
import {describe, expect, it} from '../../../core/testing/testing_internal';
import * as html from '../../src/ml_parser/ast';
import {HtmlParser} from '../../src/ml_parser/html_parser';
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', () => {
it('should error on unknown plural cases', () => {
expect(humanizeErrors(expand('{n, plural, unknown {-}}').errors)).toEqual([

View File

@ -6,7 +6,6 @@
* 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 {InterpolationConfig} from '../../src/ml_parser/interpolation_config';
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', () => {
@ -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', () => {
expect(tokenizeAndHumanizeParts('{one.two, three, =4 {four <b>a</b>}}', true)).toEqual([
[lex.TokenType.EXPANSION_FORM_START],

View File

@ -6,7 +6,7 @@
* 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 {ImportResolver} from '@angular/compiler/src/output/path_util';
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 * as ts from 'typescript';
import {MockSummaryResolver} from '../aot/static_symbol_resolver_spec';
describe('TypeScriptEmitter (node only)', () => {
it('should quote identifiers quoted in the source', () => {
const sourceText = `
@ -27,7 +29,10 @@ describe('TypeScriptEmitter (node only)', () => {
const source = ts.createSourceFile('test.ts', sourceText, ts.ScriptTarget.Latest);
const collector = new MetadataCollector({quotedNames: true});
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
const metadata = collector.getMetadata(source);
@ -62,11 +67,11 @@ describe('TypeScriptEmitter (node only)', () => {
});
});
class StubReflectorHost implements StaticReflectorHost {
class StubReflectorHost implements StaticSymbolResolverHost {
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 {
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string { return ''; }
}
}

View File

@ -5,7 +5,7 @@
* 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 {SyntaxError} from '@angular/compiler';
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 {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry';
@ -374,7 +374,7 @@ export function main() {
describe('errors', () => {
it('should throw error when binding to an unknown property', () => {
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'.
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.
@ -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', () => {
expect(() => parse('<unknown></unknown>', [])).toThrowError(`Template parse errors:
expect(() => parse('<unknown></unknown>', []))
.toThrowError(SyntaxError, `Template parse errors:
'unknown' is not a known element:
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`);
});
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:
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`);
@ -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', () => {
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`);
});
it('should throw error when binding to an invalid attribute', () => {
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`);
});
});
@ -445,6 +447,7 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
() => {
expect(() => { parse('<div @someAnimation="value2">', [], [], []); })
.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/);
});
@ -479,6 +482,7 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
expect(() => { parse('<broken></broken>', [dirA]); })
.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`);
});
@ -494,6 +498,7 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
expect(() => { parse('<broken></broken>', [dirA]); })
.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`);
});
@ -940,8 +945,8 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
const dirB = createDir('[dirB]', {providers: [provider1]});
expect(() => parse('<div dirA dirB>', [dirA, dirB]))
.toThrowError(
`Template parse errors:\n` +
`Mixing multi and non multi provider is not possible for token service0 ("[ERROR ->]<div dirA dirB>"): TestComp@0:0`);
SyntaxError, `Template parse errors:\n` +
`Mixing multi and non multi provider is not possible for token service0 ("[ERROR ->]<div dirA dirB>"): TestComp@0:0`);
});
it('should sort providers by their DI order', () => {
@ -1033,6 +1038,7 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
const dirA = createDir('[dirA]', {deps: ['self:provider0']});
expect(() => parse('<div dirA></div>', [dirA]))
.toThrowError(
SyntaxError,
'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']});
expect(() => parse('<div dirA></div>', [dirA]))
.toThrowError(
SyntaxError,
'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', () => {
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`);
});
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`);
});
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`);
});
it('should report duplicate reference names', () => {
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`);
});
@ -1466,8 +1476,8 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
it('should report when ng-content has non WS content', () => {
expect(() => parse('<ng-content>content</ng-content>', []))
.toThrowError(
`Template parse errors:\n` +
`<ng-content> element cannot have content. ("[ERROR ->]<ng-content>content</ng-content>"): TestComp@0:0`);
SyntaxError, `Template parse errors:\n` +
`<ng-content> element cannot have content. ("[ERROR ->]<ng-content>content</ng-content>"): TestComp@0:0`);
});
it('should treat *attr on a template element as valid',
@ -1477,18 +1487,20 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
() => { expect(() => parse('<template template="ngIf">', [])).not.toThrowError(); });
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`);
});
it('should report when mix of template and *attrs are used on the same element', () => {
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`);
});
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`);
});
@ -1501,12 +1513,13 @@ Can't bind to 'invalidProp' since it isn't a known property of 'div'. ("<div [ER
host: {'[invalidProp]': 'someProp'}
})
.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`);
});
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`);
});
@ -1544,10 +1557,10 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("<div [
.toSummary();
expect(() => parse('<div>', [dirB, dirA]))
.toThrowError(
`Template parse errors:\n` +
`More than one component matched on this element.\n` +
`Make sure that only one component's selector can match a given element.\n` +
`Conflicting components: DirB,DirA ("[ERROR ->]<div>"): TestComp@0:0`);
SyntaxError, `Template parse errors:\n` +
`More than one component matched on this element.\n` +
`Make sure that only one component's selector can match a given element.\n` +
`Conflicting components: DirB,DirA ("[ERROR ->]<div>"): TestComp@0:0`);
});
it('should not allow components or element bindings nor dom events on explicit embedded templates',
@ -1562,7 +1575,7 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("<div [
})
.toSummary();
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
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`);
@ -1578,7 +1591,8 @@ Property binding a not used by any directive on an embedded template. Make sure
template: new CompileTemplateMetadata({ngContentSelectors: []})
})
.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
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', () => {
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`);
});

View File

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

View File

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

View File

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

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Directive, EventEmitter, Input, Output, forwardRef} from '@angular/core';
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
import {Component, Directive, EventEmitter, Input, Output, Type, forwardRef} from '@angular/core';
import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
import {AbstractControl, ControlValueAccessor, FormArray, FormControl, FormGroup, FormGroupDirective, FormsModule, NG_ASYNC_VALIDATORS, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgControl, ReactiveFormsModule, Validator, Validators} from '@angular/forms';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
@ -16,38 +16,15 @@ import {dispatchEvent} from '@angular/platform-browser/testing/browser_util';
export function main() {
describe('reactive forms integration tests', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule],
declarations: [
FormControlComp,
FormGroupComp,
FormArrayComp,
FormArrayNestedGroup,
FormControlNameSelect,
FormControlNumberInput,
FormControlRangeInput,
FormControlRadioButtons,
WrappedValue,
WrappedValueForm,
MyInput,
MyInputForm,
FormGroupNgModel,
FormControlNgModel,
LoginIsEmptyValidator,
LoginIsEmptyWrapper,
ValidationBindingsForm,
UniqLoginValidator,
UniqLoginWrapper,
NestedFormGroupComp,
FormControlCheckboxRequiredValidator,
]
});
});
function initTest<T>(component: Type<T>, ...directives: Type<any>[]): ComponentFixture<T> {
TestBed.configureTestingModule(
{declarations: [component, ...directives], imports: [FormsModule, ReactiveFormsModule]});
return TestBed.createComponent(component);
}
describe('basic functionality', () => {
it('should work with single controls', () => {
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control = new FormControl('old value');
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -64,7 +41,7 @@ export function main() {
});
it('should work with formGroups (model -> view)', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')});
fixture.detectChanges();
@ -73,7 +50,7 @@ export function main() {
});
it('should add novalidate by default to form', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')});
fixture.detectChanges();
@ -82,7 +59,7 @@ export function main() {
});
it('work with formGroups (view -> model)', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('oldValue')});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -99,7 +76,7 @@ export function main() {
describe('rebound form groups', () => {
it('should update DOM elements initially', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.form = new FormGroup({'login': new FormControl('oldValue')});
fixture.detectChanges();
@ -111,7 +88,7 @@ export function main() {
});
it('should update model when UI changes', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.form = new FormGroup({'login': new FormControl('oldValue')});
fixture.detectChanges();
@ -132,7 +109,7 @@ export function main() {
});
it('should work with radio buttons when reusing control', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const food = new FormControl('chicken');
fixture.componentInstance.form =
new FormGroup({'food': food, 'drink': new FormControl('')});
@ -150,7 +127,7 @@ export function main() {
});
it('should update nested form group model when UI changes', () => {
const fixture = TestBed.createComponent(NestedFormGroupComp);
const fixture = initTest(NestedFormGroupComp);
fixture.componentInstance.form = new FormGroup(
{'signin': new FormGroup({'login': new FormControl(), 'password': new FormControl()})});
fixture.detectChanges();
@ -178,7 +155,7 @@ export function main() {
});
it('should pick up dir validators from form controls', () => {
const fixture = TestBed.createComponent(LoginIsEmptyWrapper);
const fixture = initTest(LoginIsEmptyWrapper, LoginIsEmptyValidator);
const form = new FormGroup({
'login': new FormControl(''),
'min': new FormControl(''),
@ -202,7 +179,7 @@ export function main() {
});
it('should pick up dir validators from nested form groups', () => {
const fixture = TestBed.createComponent(NestedFormGroupComp);
const fixture = initTest(NestedFormGroupComp, LoginIsEmptyValidator);
const form = new FormGroup({
'signin':
new FormGroup({'login': new FormControl(''), 'password': new FormControl('')})
@ -222,7 +199,7 @@ export function main() {
});
it('should strip named controls that are not found', () => {
const fixture = TestBed.createComponent(NestedFormGroupComp);
const fixture = initTest(NestedFormGroupComp, LoginIsEmptyValidator);
const form = new FormGroup({
'signin':
new FormGroup({'login': new FormControl(''), 'password': new FormControl('')})
@ -248,7 +225,7 @@ export function main() {
});
it('should strip array controls that are not found', () => {
const fixture = TestBed.createComponent(FormArrayComp);
const fixture = initTest(FormArrayComp);
const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
const form = new FormGroup({cities: cityArray});
fixture.componentInstance.form = form;
@ -277,7 +254,7 @@ export function main() {
it('should attach dir to control when leaf control changes', () => {
const form = new FormGroup({'login': new FormControl('oldValue')});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -300,7 +277,7 @@ export function main() {
});
it('should attach dirs to all child controls when group control changes', () => {
const fixture = TestBed.createComponent(NestedFormGroupComp);
const fixture = initTest(NestedFormGroupComp, LoginIsEmptyValidator);
const form = new FormGroup({
signin: new FormGroup(
{login: new FormControl('oldLogin'), password: new FormControl('oldPassword')})
@ -332,7 +309,7 @@ export function main() {
});
it('should attach dirs to all present child controls when array control changes', () => {
const fixture = TestBed.createComponent(FormArrayComp);
const fixture = initTest(FormArrayComp);
const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
const form = new FormGroup({cities: cityArray});
fixture.componentInstance.form = form;
@ -363,7 +340,7 @@ export function main() {
describe('form arrays', () => {
it('should support form arrays', () => {
const fixture = TestBed.createComponent(FormArrayComp);
const fixture = initTest(FormArrayComp);
const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
const form = new FormGroup({cities: cityArray});
fixture.componentInstance.form = form;
@ -386,7 +363,7 @@ export function main() {
});
it('should support pushing new controls to form arrays', () => {
const fixture = TestBed.createComponent(FormArrayComp);
const fixture = initTest(FormArrayComp);
const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
const form = new FormGroup({cities: cityArray});
fixture.componentInstance.form = form;
@ -402,7 +379,7 @@ export function main() {
});
it('should support form groups nested in form arrays', () => {
const fixture = TestBed.createComponent(FormArrayNestedGroup);
const fixture = initTest(FormArrayNestedGroup);
const cityArray = new FormArray([
new FormGroup({town: new FormControl('SF'), state: new FormControl('CA')}),
new FormGroup({town: new FormControl('NY'), state: new FormControl('NY')})
@ -434,7 +411,7 @@ export function main() {
describe('programmatic changes', () => {
it('should update the value in the DOM when setValue() is called', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const login = new FormControl('oldValue');
const form = new FormGroup({'login': login});
fixture.componentInstance.form = form;
@ -450,7 +427,7 @@ export function main() {
describe('disabled controls', () => {
it('should add disabled attribute to an individual control when instantiated as disabled',
() => {
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control = new FormControl({value: 'some value', disabled: true});
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -464,7 +441,7 @@ export function main() {
});
it('should add disabled attribute to formControlName when instantiated as disabled', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const control = new FormControl({value: 'some value', disabled: true});
fixture.componentInstance.form = new FormGroup({login: control});
fixture.componentInstance.control = control;
@ -480,7 +457,7 @@ export function main() {
it('should add disabled attribute to an individual control when disable() is called',
() => {
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control = new FormControl('some value');
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -498,7 +475,7 @@ export function main() {
it('should add disabled attribute to child controls when disable() is called on group',
() => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('login')});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -516,7 +493,7 @@ export function main() {
it('should not add disabled attribute to custom controls when disable() is called', () => {
const fixture = TestBed.createComponent(MyInputForm);
const fixture = initTest(MyInputForm, MyInput);
const control = new FormControl('some value');
fixture.componentInstance.form = new FormGroup({login: control});
fixture.detectChanges();
@ -535,7 +512,7 @@ export function main() {
describe('user input', () => {
it('should mark controls as touched after interacting with the DOM control', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const login = new FormControl('oldValue');
const form = new FormGroup({'login': login});
fixture.componentInstance.form = form;
@ -553,7 +530,7 @@ export function main() {
describe('submit and reset events', () => {
it('should emit ngSubmit event with the original submit event on submit', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')});
fixture.componentInstance.event = null;
fixture.detectChanges();
@ -566,7 +543,7 @@ export function main() {
});
it('should mark formGroup as submitted on submit event', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.form = new FormGroup({'login': new FormControl('loginValue')});
fixture.detectChanges();
@ -581,7 +558,7 @@ export function main() {
});
it('should set value in UI when form resets to that value programmatically', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const login = new FormControl('some value');
const form = new FormGroup({'login': login});
fixture.componentInstance.form = form;
@ -595,7 +572,7 @@ export function main() {
});
it('should clear value in UI when form resets programmatically', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const login = new FormControl('some value');
const form = new FormGroup({'login': login});
fixture.componentInstance.form = form;
@ -613,7 +590,7 @@ export function main() {
describe('value changes and status changes', () => {
it('should mark controls as dirty before emitting a value change event', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const login = new FormControl('oldValue');
fixture.componentInstance.form = new FormGroup({'login': login});
fixture.detectChanges();
@ -628,7 +605,7 @@ export function main() {
it('should mark control as pristine before emitting a value change event when resetting ',
() => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const login = new FormControl('oldValue');
const form = new FormGroup({'login': login});
fixture.componentInstance.form = form;
@ -649,7 +626,7 @@ export function main() {
describe('setting status classes', () => {
it('should work with single fields', () => {
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control = new FormControl('', Validators.required);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -670,7 +647,7 @@ export function main() {
});
it('should work with single fields and async validators', fakeAsync(() => {
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control = new FormControl('', null, uniqLoginAsyncValidator('good'));
fixture.debugElement.componentInstance.control = control;
fixture.detectChanges();
@ -691,7 +668,7 @@ export function main() {
}));
it('should work with single fields that combines async and sync validators', fakeAsync(() => {
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control =
new FormControl('', Validators.required, uniqLoginAsyncValidator('good'));
fixture.debugElement.componentInstance.control = control;
@ -724,7 +701,7 @@ export function main() {
}));
it('should work with single fields in parent forms', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('', Validators.required)});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -745,7 +722,7 @@ export function main() {
});
it('should work with formGroup', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('', Validators.required)});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -774,7 +751,7 @@ export function main() {
it('should support <input> without type', () => {
TestBed.overrideComponent(
FormControlComp, {set: {template: `<input [formControl]="control">`}});
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control = new FormControl('old');
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -791,7 +768,7 @@ export function main() {
});
it('should support <input type=text>', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('old')});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -808,7 +785,7 @@ export function main() {
});
it('should ignore the change event for <input type=text>', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('oldValue')});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -823,7 +800,7 @@ export function main() {
it('should support <textarea>', () => {
TestBed.overrideComponent(
FormControlComp, {set: {template: `<textarea [formControl]="control"></textarea>`}});
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control = new FormControl('old');
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -842,7 +819,7 @@ export function main() {
it('should support <type=checkbox>', () => {
TestBed.overrideComponent(
FormControlComp, {set: {template: `<input type="checkbox" [formControl]="control">`}});
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
const control = new FormControl(true);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -859,7 +836,7 @@ export function main() {
});
it('should support <select>', () => {
const fixture = TestBed.createComponent(FormControlNameSelect);
const fixture = initTest(FormControlNameSelect);
fixture.detectChanges();
// model -> view
@ -879,7 +856,7 @@ export function main() {
describe('should support <type=number>', () => {
it('with basic use case', () => {
const fixture = TestBed.createComponent(FormControlNumberInput);
const fixture = initTest(FormControlNumberInput);
const control = new FormControl(10);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -896,7 +873,7 @@ export function main() {
});
it('when value is cleared in the UI', () => {
const fixture = TestBed.createComponent(FormControlNumberInput);
const fixture = initTest(FormControlNumberInput);
const control = new FormControl(10, Validators.required);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -916,7 +893,7 @@ export function main() {
});
it('when value is cleared programmatically', () => {
const fixture = TestBed.createComponent(FormControlNumberInput);
const fixture = initTest(FormControlNumberInput);
const control = new FormControl(10);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -931,7 +908,7 @@ export function main() {
describe('should support <type=radio>', () => {
it('should support basic functionality', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const form =
new FormGroup({'food': new FormControl('fish'), 'drink': new FormControl('sprite')});
fixture.componentInstance.form = form;
@ -958,7 +935,7 @@ export function main() {
});
it('should support an initial undefined value', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const form = new FormGroup({'food': new FormControl(), 'drink': new FormControl()});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -969,7 +946,7 @@ export function main() {
});
it('should reset properly', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const form =
new FormGroup({'food': new FormControl('fish'), 'drink': new FormControl('sprite')});
fixture.componentInstance.form = form;
@ -984,7 +961,7 @@ export function main() {
});
it('should set value to null and undefined properly', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const form = new FormGroup(
{'food': new FormControl('chicken'), 'drink': new FormControl('sprite')});
fixture.componentInstance.form = form;
@ -1005,7 +982,7 @@ export function main() {
});
it('should use formControlName to group radio buttons when name is absent', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const foodCtrl = new FormControl('fish');
const drinkCtrl = new FormControl('sprite');
fixture.componentInstance.form = new FormGroup({'food': foodCtrl, 'drink': drinkCtrl});
@ -1037,7 +1014,7 @@ export function main() {
});
it('should support removing controls from <type=radio>', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const showRadio = new FormControl('yes');
const form =
new FormGroup({'food': new FormControl('fish'), 'drink': new FormControl('sprite')});
@ -1071,7 +1048,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const form = new FormGroup({
food: new FormControl('fish'),
nested: new FormGroup({food: new FormControl('fish')})
@ -1100,7 +1077,7 @@ export function main() {
});
it('should disable all radio buttons when disable() is called', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const form =
new FormGroup({food: new FormControl('fish'), drink: new FormControl('cola')});
fixture.componentInstance.form = form;
@ -1132,7 +1109,7 @@ export function main() {
});
it('should disable all radio buttons when initially disabled', () => {
const fixture = TestBed.createComponent(FormControlRadioButtons);
const fixture = initTest(FormControlRadioButtons);
const form = new FormGroup({
food: new FormControl({value: 'fish', disabled: true}),
drink: new FormControl('cola')
@ -1151,7 +1128,7 @@ export function main() {
describe('should support <type=range>', () => {
it('with basic use case', () => {
const fixture = TestBed.createComponent(FormControlRangeInput);
const fixture = initTest(FormControlRangeInput);
const control = new FormControl(10);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -1168,7 +1145,7 @@ export function main() {
});
it('when value is cleared in the UI', () => {
const fixture = TestBed.createComponent(FormControlNumberInput);
const fixture = initTest(FormControlNumberInput);
const control = new FormControl(10, Validators.required);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -1188,7 +1165,7 @@ export function main() {
});
it('when value is cleared programmatically', () => {
const fixture = TestBed.createComponent(FormControlNumberInput);
const fixture = initTest(FormControlNumberInput);
const control = new FormControl(10);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -1202,7 +1179,7 @@ export function main() {
describe('custom value accessors', () => {
it('should support basic functionality', () => {
const fixture = TestBed.createComponent(WrappedValueForm);
const fixture = initTest(WrappedValueForm, WrappedValue);
const form = new FormGroup({'login': new FormControl('aa')});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -1225,7 +1202,7 @@ export function main() {
it('should support non builtin input elements that fire a change event without a \'target\' property',
() => {
const fixture = TestBed.createComponent(MyInputForm);
const fixture = initTest(MyInputForm, MyInput);
fixture.componentInstance.form = new FormGroup({'login': new FormControl('aa')});
fixture.detectChanges();
@ -1240,7 +1217,7 @@ export function main() {
});
it('should support custom accessors without setDisabledState - formControlName', () => {
const fixture = TestBed.createComponent(WrappedValueForm);
const fixture = initTest(WrappedValueForm, WrappedValue);
fixture.componentInstance.form = new FormGroup({
'login': new FormControl({value: 'aa', disabled: true}),
});
@ -1254,7 +1231,7 @@ export function main() {
TestBed.overrideComponent(
FormControlComp,
{set: {template: `<input type="text" [formControl]="control" wrapped-value>`}});
const fixture = TestBed.createComponent(FormControlComp);
const fixture = initTest(FormControlComp);
fixture.componentInstance.control = new FormControl({value: 'aa', disabled: true});
fixture.detectChanges();
expect(fixture.componentInstance.control.status).toEqual('DISABLED');
@ -1267,7 +1244,7 @@ export function main() {
describe('ngModel interactions', () => {
it('should support ngModel for complex forms', fakeAsync(() => {
const fixture = TestBed.createComponent(FormGroupNgModel);
const fixture = initTest(FormGroupNgModel);
fixture.componentInstance.form = new FormGroup({'login': new FormControl('')});
fixture.componentInstance.login = 'oldValue';
fixture.detectChanges();
@ -1284,7 +1261,7 @@ export function main() {
}));
it('should support ngModel for single fields', fakeAsync(() => {
const fixture = TestBed.createComponent(FormControlNgModel);
const fixture = initTest(FormControlNgModel);
fixture.componentInstance.control = new FormControl('');
fixture.componentInstance.login = 'oldValue';
fixture.detectChanges();
@ -1301,7 +1278,7 @@ export function main() {
}));
it('should not update the view when the value initially came from the view', fakeAsync(() => {
const fixture = TestBed.createComponent(FormControlNgModel);
const fixture = initTest(FormControlNgModel);
fixture.componentInstance.control = new FormControl('');
fixture.detectChanges();
tick();
@ -1322,7 +1299,7 @@ export function main() {
describe('validations', () => {
it('required validator should validate checkbox', () => {
const fixture = TestBed.createComponent(FormControlCheckboxRequiredValidator);
const fixture = initTest(FormControlCheckboxRequiredValidator);
const control = new FormControl(false, Validators.requiredTrue);
fixture.componentInstance.control = control;
fixture.detectChanges();
@ -1340,7 +1317,7 @@ export function main() {
});
it('should use sync validators defined in html', () => {
const fixture = TestBed.createComponent(LoginIsEmptyWrapper);
const fixture = initTest(LoginIsEmptyWrapper, LoginIsEmptyValidator);
const form = new FormGroup({
'login': new FormControl(''),
'min': new FormControl(''),
@ -1385,7 +1362,7 @@ export function main() {
});
it('should use sync validators using bindings', () => {
const fixture = TestBed.createComponent(ValidationBindingsForm);
const fixture = initTest(ValidationBindingsForm);
const form = new FormGroup({
'login': new FormControl(''),
'min': new FormControl(''),
@ -1433,7 +1410,7 @@ export function main() {
});
it('changes on bound properties should change the validation state of the form', () => {
const fixture = TestBed.createComponent(ValidationBindingsForm);
const fixture = initTest(ValidationBindingsForm);
const form = new FormGroup({
'login': new FormControl(''),
'min': new FormControl(''),
@ -1508,7 +1485,7 @@ export function main() {
});
it('should support rebound controls with rebound validators', () => {
const fixture = TestBed.createComponent(ValidationBindingsForm);
const fixture = initTest(ValidationBindingsForm);
const form = new FormGroup({
'login': new FormControl(''),
'min': new FormControl(''),
@ -1545,7 +1522,7 @@ export function main() {
});
it('should use async validators defined in the html', fakeAsync(() => {
const fixture = TestBed.createComponent(UniqLoginWrapper);
const fixture = initTest(UniqLoginWrapper, UniqLoginValidator);
const form = new FormGroup({'login': new FormControl('')});
tick();
fixture.componentInstance.form = form;
@ -1565,7 +1542,7 @@ export function main() {
}));
it('should use sync validators defined in the model', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('aa', Validators.required)});
fixture.componentInstance.form = form;
fixture.detectChanges();
@ -1579,7 +1556,7 @@ export function main() {
});
it('should use async validators defined in the model', fakeAsync(() => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const control =
new FormControl('', Validators.required, uniqLoginAsyncValidator('expected'));
const form = new FormGroup({'login': control});
@ -1610,7 +1587,7 @@ export function main() {
describe('errors', () => {
it('should throw if a form isn\'t passed into formGroup', () => {
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
expect(() => fixture.detectChanges())
.toThrowError(new RegExp(`formGroup expects a FormGroup instance`));
@ -1624,7 +1601,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
expect(() => fixture.detectChanges())
.toThrowError(
@ -1641,7 +1618,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
expect(() => fixture.detectChanges())
.toThrowError(
@ -1660,7 +1637,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
expect(() => fixture.detectChanges())
.toThrowError(
@ -1677,7 +1654,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
expect(() => fixture.detectChanges())
.toThrowError(
@ -1696,7 +1673,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
expect(() => fixture.detectChanges())
.toThrowError(
@ -1712,7 +1689,7 @@ export function main() {
</div>`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
expect(() => fixture.detectChanges())
.toThrowError(
@ -1729,7 +1706,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.myGroup = new FormGroup({});
expect(() => fixture.detectChanges())
@ -1747,7 +1724,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.myGroup = new FormGroup({});
expect(() => fixture.detectChanges()).not.toThrowError();
@ -1765,7 +1742,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
const myGroup = new FormGroup({person: new FormGroup({})});
fixture.componentInstance.myGroup = new FormGroup({person: new FormGroup({})});
@ -1786,7 +1763,7 @@ export function main() {
`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.myGroup = new FormGroup({});
expect(() => fixture.detectChanges())
@ -1803,7 +1780,7 @@ export function main() {
</form>`
}
});
const fixture = TestBed.createComponent(FormGroupComp);
const fixture = initTest(FormGroupComp);
fixture.componentInstance.form = new FormGroup({'food': new FormControl('fish')});
expect(() => fixture.detectChanges())

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Directive, Input, forwardRef} from '@angular/core';
import {Component, Directive, Input, Type, forwardRef} from '@angular/core';
import {ComponentFixture, TestBed, async, fakeAsync, tick} from '@angular/core/testing';
import {AbstractControl, ControlValueAccessor, FormsModule, NG_ASYNC_VALIDATORS, NG_VALUE_ACCESSOR, NgForm, Validator} from '@angular/forms';
import {By} from '@angular/platform-browser/src/dom/debug/by';
@ -16,37 +16,15 @@ import {dispatchEvent} from '@angular/platform-browser/testing/browser_util';
export function main() {
describe('template-driven forms integration tests', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
StandaloneNgModel,
NgModelForm,
NgModelGroupForm,
NgModelValidBinding,
NgModelNgIfForm,
NgModelRadioForm,
NgModelRangeForm,
NgModelSelectForm,
NgNoFormComp,
InvalidNgModelNoName,
NgModelOptionsStandalone,
NgModelCustomComp,
NgModelCustomWrapper,
NgModelValidationBindings,
NgModelMultipleValidators,
NgAsyncValidator,
NgModelAsyncValidation,
NgModelSelectMultipleForm,
NgModelSelectWithNullForm,
NgModelCheckboxRequiredValidator,
],
imports: [FormsModule]
});
});
function initTest<T>(component: Type<T>, ...directives: Type<any>[]): ComponentFixture<T> {
TestBed.configureTestingModule(
{declarations: [component, ...directives], imports: [FormsModule]});
return TestBed.createComponent(component);
}
describe('basic functionality', () => {
it('should support ngModel for standalone fields', fakeAsync(() => {
const fixture = TestBed.createComponent(StandaloneNgModel);
const fixture = initTest(StandaloneNgModel);
fixture.componentInstance.name = 'oldValue';
fixture.detectChanges();
@ -65,7 +43,7 @@ export function main() {
}));
it('should support ngModel registration with a parent form', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = 'Nancy';
fixture.detectChanges();
@ -77,7 +55,7 @@ export function main() {
}));
it('should add novalidate by default to form element', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
fixture.detectChanges();
tick();
@ -87,7 +65,7 @@ export function main() {
}));
it('should support ngModelGroup', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelGroupForm);
const fixture = initTest(NgModelGroupForm);
fixture.componentInstance.first = 'Nancy';
fixture.componentInstance.last = 'Drew';
fixture.componentInstance.email = 'some email';
@ -110,7 +88,7 @@ export function main() {
}));
it('should add controls and control groups to form control model', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelGroupForm);
const fixture = initTest(NgModelGroupForm);
fixture.componentInstance.first = 'Nancy';
fixture.componentInstance.last = 'Drew';
fixture.componentInstance.email = 'some email';
@ -125,7 +103,7 @@ export function main() {
}));
it('should remove controls and control groups from form control model', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelNgIfForm);
const fixture = initTest(NgModelNgIfForm);
fixture.componentInstance.emailShowing = true;
fixture.componentInstance.first = 'Nancy';
fixture.componentInstance.email = 'some email';
@ -158,7 +136,7 @@ export function main() {
}));
it('should set status classes with ngModel', async(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = 'aa';
fixture.detectChanges();
fixture.whenStable().then(() => {
@ -181,7 +159,7 @@ export function main() {
it('should set status classes with ngModel and async validators', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelAsyncValidation);
const fixture = initTest(NgModelAsyncValidation, NgAsyncValidator);
fixture.whenStable().then(() => {
fixture.detectChanges();
@ -203,7 +181,7 @@ export function main() {
}));
it('should set status classes with ngModelGroup and ngForm', async(() => {
const fixture = TestBed.createComponent(NgModelGroupForm);
const fixture = initTest(NgModelGroupForm);
fixture.componentInstance.first = '';
fixture.detectChanges();
@ -238,13 +216,13 @@ export function main() {
}));
it('should not create a template-driven form when ngNoForm is used', () => {
const fixture = TestBed.createComponent(NgNoFormComp);
const fixture = initTest(NgNoFormComp);
fixture.detectChanges();
expect(fixture.debugElement.children[0].providerTokens.length).toEqual(0);
});
it('should not add novalidate when ngNoForm is used', () => {
const fixture = TestBed.createComponent(NgNoFormComp);
const fixture = initTest(NgNoFormComp);
fixture.detectChanges();
const form = fixture.debugElement.query(By.css('form'));
expect(form.nativeElement.hasAttribute('novalidate')).toBeFalsy();
@ -253,19 +231,19 @@ export function main() {
describe('name and ngModelOptions', () => {
it('should throw if ngModel has a parent form but no name attr or standalone label', () => {
const fixture = TestBed.createComponent(InvalidNgModelNoName);
const fixture = initTest(InvalidNgModelNoName);
expect(() => fixture.detectChanges())
.toThrowError(new RegExp(`name attribute must be set`));
});
it('should not throw if ngModel has a parent form, no name attr, and a standalone label',
() => {
const fixture = TestBed.createComponent(NgModelOptionsStandalone);
const fixture = initTest(NgModelOptionsStandalone);
expect(() => fixture.detectChanges()).not.toThrow();
});
it('should not register standalone ngModels with parent form', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelOptionsStandalone);
const fixture = initTest(NgModelOptionsStandalone);
fixture.componentInstance.one = 'some data';
fixture.componentInstance.two = 'should not show';
fixture.detectChanges();
@ -280,7 +258,7 @@ export function main() {
}));
it('should override name attribute with ngModelOptions name if provided', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
fixture.componentInstance.options = {name: 'override'};
fixture.componentInstance.name = 'some data';
fixture.detectChanges();
@ -293,7 +271,7 @@ export function main() {
describe('submit and reset events', () => {
it('should emit ngSubmit event with the original submit event on submit', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
fixture.componentInstance.event = null;
const form = fixture.debugElement.query(By.css('form'));
@ -304,7 +282,7 @@ export function main() {
}));
it('should mark NgForm as submitted on submit event', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
tick();
const form = fixture.debugElement.children[0].injector.get(NgForm);
@ -318,7 +296,7 @@ export function main() {
}));
it('should reset the form to empty when reset event is fired', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = 'should be cleared';
fixture.detectChanges();
tick();
@ -341,7 +319,7 @@ export function main() {
}));
it('should reset the form submit state when reset button is clicked', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
const form = fixture.debugElement.children[0].injector.get(NgForm);
const formEl = fixture.debugElement.query(By.css('form'));
@ -359,7 +337,7 @@ export function main() {
describe('valueChange and statusChange events', () => {
it('should emit valueChanges and statusChanges on init', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
const form = fixture.debugElement.children[0].injector.get(NgForm);
fixture.componentInstance.name = 'aa';
fixture.detectChanges();
@ -380,7 +358,7 @@ export function main() {
}));
it('should mark controls dirty before emitting the value change event', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
const form = fixture.debugElement.children[0].injector.get(NgForm).form;
fixture.detectChanges();
@ -397,7 +375,7 @@ export function main() {
it('should mark controls pristine before emitting the value change event when resetting ',
fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
fixture.detectChanges();
tick();
@ -419,7 +397,7 @@ export function main() {
describe('disabled controls', () => {
it('should not consider disabled controls in value or validation', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelGroupForm);
const fixture = initTest(NgModelGroupForm);
fixture.componentInstance.isDisabled = false;
fixture.componentInstance.first = '';
fixture.componentInstance.last = 'Drew';
@ -443,7 +421,7 @@ export function main() {
it('should add disabled attribute in the UI if disable() is called programmatically',
fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelGroupForm);
const fixture = initTest(NgModelGroupForm);
fixture.componentInstance.isDisabled = false;
fixture.componentInstance.first = 'Nancy';
fixture.detectChanges();
@ -459,7 +437,7 @@ export function main() {
}));
it('should disable a custom control if disabled attr is added', async(() => {
const fixture = TestBed.createComponent(NgModelCustomWrapper);
const fixture = initTest(NgModelCustomWrapper, NgModelCustomComp);
fixture.componentInstance.name = 'Nancy';
fixture.componentInstance.isDisabled = true;
fixture.detectChanges();
@ -485,7 +463,7 @@ export function main() {
`,
}
});
const fixture = TestBed.createComponent(NgModelForm);
const fixture = initTest(NgModelForm);
fixture.detectChanges();
tick();
const form = fixture.debugElement.children[0].injector.get(NgForm);
@ -501,7 +479,7 @@ export function main() {
}));
it('should disable radio controls properly with programmatic call', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelRadioForm);
const fixture = initTest(NgModelRadioForm);
fixture.componentInstance.food = 'fish';
fixture.detectChanges();
tick();
@ -537,7 +515,7 @@ export function main() {
describe('range control', () => {
it('should support <type=range>', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelRangeForm);
const fixture = initTest(NgModelRangeForm);
// model -> view
fixture.componentInstance.val = 4;
fixture.detectChanges();
@ -557,7 +535,7 @@ export function main() {
describe('radio controls', () => {
it('should support <type=radio>', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelRadioForm);
const fixture = initTest(NgModelRadioForm);
fixture.componentInstance.food = 'fish';
fixture.detectChanges();
tick();
@ -576,7 +554,7 @@ export function main() {
}));
it('should support multiple named <type=radio> groups', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelRadioForm);
const fixture = initTest(NgModelRadioForm);
fixture.componentInstance.food = 'fish';
fixture.componentInstance.drink = 'sprite';
fixture.detectChanges();
@ -599,7 +577,7 @@ export function main() {
}));
it('should support initial undefined value', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelRadioForm);
const fixture = initTest(NgModelRadioForm);
fixture.detectChanges();
tick();
@ -611,7 +589,7 @@ export function main() {
}));
it('should support resetting properly', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelRadioForm);
const fixture = initTest(NgModelRadioForm);
fixture.componentInstance.food = 'chicken';
fixture.detectChanges();
tick();
@ -627,7 +605,7 @@ export function main() {
}));
it('should support setting value to null and undefined', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelRadioForm);
const fixture = initTest(NgModelRadioForm);
fixture.componentInstance.food = 'chicken';
fixture.detectChanges();
tick();
@ -655,7 +633,7 @@ export function main() {
describe('select controls', () => {
it('with option values that are objects', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const fixture = initTest(NgModelSelectForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
comp.selectedCity = comp.cities[1];
@ -679,7 +657,7 @@ export function main() {
}));
it('when new options are added', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const fixture = initTest(NgModelSelectForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}];
comp.selectedCity = comp.cities[1];
@ -698,7 +676,7 @@ export function main() {
}));
it('when options are removed', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const fixture = initTest(NgModelSelectForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}];
comp.selectedCity = comp.cities[1];
@ -716,7 +694,7 @@ export function main() {
}));
it('when option values have same content, but different identities', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelSelectForm);
const fixture = initTest(NgModelSelectForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'NYC'}];
comp.selectedCity = comp.cities[0];
@ -733,7 +711,7 @@ export function main() {
}));
it('should work with null option', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelSelectWithNullForm);
const fixture = initTest(NgModelSelectWithNullForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}];
comp.selectedCity = null;
@ -760,7 +738,7 @@ export function main() {
let comp: NgModelSelectMultipleForm;
beforeEach(() => {
fixture = TestBed.createComponent(NgModelSelectMultipleForm);
fixture = initTest(NgModelSelectMultipleForm);
comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
});
@ -821,7 +799,7 @@ export function main() {
describe('custom value accessors', () => {
it('should support standard writing to view and model', async(() => {
const fixture = TestBed.createComponent(NgModelCustomWrapper);
const fixture = initTest(NgModelCustomWrapper, NgModelCustomComp);
fixture.componentInstance.name = 'Nancy';
fixture.detectChanges();
fixture.whenStable().then(() => {
@ -846,7 +824,7 @@ export function main() {
describe('validation directives', () => {
it('required validator should validate checkbox', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelCheckboxRequiredValidator);
const fixture = initTest(NgModelCheckboxRequiredValidator);
fixture.detectChanges();
tick();
@ -882,7 +860,7 @@ export function main() {
}));
it('should support dir validators using bindings', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelValidationBindings);
const fixture = initTest(NgModelValidationBindings);
fixture.componentInstance.required = true;
fixture.componentInstance.minLen = 3;
fixture.componentInstance.maxLen = 3;
@ -926,7 +904,7 @@ export function main() {
}));
it('should support optional fields with string pattern validator', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelMultipleValidators);
const fixture = initTest(NgModelMultipleValidators);
fixture.componentInstance.required = false;
fixture.componentInstance.pattern = '[a-z]+';
fixture.detectChanges();
@ -948,7 +926,7 @@ export function main() {
}));
it('should support optional fields with RegExp pattern validator', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelMultipleValidators);
const fixture = initTest(NgModelMultipleValidators);
fixture.componentInstance.required = false;
fixture.componentInstance.pattern = /^[a-z]+$/;
fixture.detectChanges();
@ -970,7 +948,7 @@ export function main() {
}));
it('should support optional fields with minlength validator', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelMultipleValidators);
const fixture = initTest(NgModelMultipleValidators);
fixture.componentInstance.required = false;
fixture.componentInstance.minLen = 2;
fixture.detectChanges();
@ -993,7 +971,7 @@ export function main() {
it('changes on bound properties should change the validation state of the form',
fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelValidationBindings);
const fixture = initTest(NgModelValidationBindings);
fixture.detectChanges();
tick();
@ -1067,7 +1045,7 @@ export function main() {
describe('ngModel corner cases', () => {
it('should update the view when the model is set back to what used to be in the view',
fakeAsync(() => {
const fixture = TestBed.createComponent(StandaloneNgModel);
const fixture = initTest(StandaloneNgModel);
fixture.componentInstance.name = '';
fixture.detectChanges();
tick();
@ -1095,7 +1073,7 @@ export function main() {
}));
it('should not crash when validity is checked from a binding', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelValidBinding);
const fixture = initTest(NgModelValidBinding);
tick();
expect(() => fixture.detectChanges()).not.toThrowError();
}));

View File

@ -8,7 +8,7 @@
"author": "angular",
"license": "MIT",
"peerDependencies": {
"rxjs": "5.0.0-rc.4",
"rxjs": "^5.0.1",
"@angular/core": "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
*/
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 {DirectiveNormalizer} from '@angular/compiler/src/directive_normalizer';
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';
/**
* Create a `LanguageServiceHost`
*/
@ -75,6 +76,7 @@ export class DummyResourceLoader extends ResourceLoader {
export class TypeScriptServiceHost implements LanguageServiceHost {
private _resolver: CompileMetadataResolver;
private _staticSymbolCache = new StaticSymbolCache();
private _staticSymbolResolver: StaticSymbolResolver;
private _reflector: StaticReflector;
private _reflectorHost: ReflectorHost;
private _checker: ts.TypeChecker;
@ -159,10 +161,13 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
private ensureAnalyzedModules(): NgAnalyzedModules {
let analyzedModules = this.analyzedModules;
if (!analyzedModules) {
const analyzeHost = {isSourceFile(filePath: string) { return true; }};
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;
}
@ -224,6 +229,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
if (this.modulesOutOfDate) {
this.analyzedModules = null;
this._reflector = null;
this._staticSymbolResolver = null;
this.templateReferences = null;
this.fileToComponent = null;
this.ensureAnalyzedModules();
@ -385,12 +391,27 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
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 {
let result = this._reflector;
if (!result) {
result = this._reflector = new StaticReflector(
this.reflectorHost, this._staticSymbolCache, [], [],
(e, filePath) => this.collectError(e, filePath));
this.staticSymbolResolver, [], [], (e, filePath) => this.collectError(e, filePath));
}
return result;
}

View File

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

View File

@ -175,6 +175,12 @@ export function main() {
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', () => {
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/common": "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);
const upgrade = new UpgradeAdapterRef();
this.ng2BootstrapDeferred.promise.then(
() => { (<any>upgrade)._bootstrapDone(this.moduleRef, upgrade.ng1Injector); }, onError);
(ng1Injector) => { (<any>upgrade)._bootstrapDone(this.moduleRef, ng1Injector); }, onError);
return upgrade;
}

View File

@ -11,7 +11,7 @@ import {async, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import * as angular from '@angular/upgrade/src/angular_js';
import {UpgradeAdapter, sortProjectableNodes} from '@angular/upgrade/src/upgrade_adapter';
import {UpgradeAdapter, UpgradeAdapterRef, sortProjectableNodes} from '@angular/upgrade/src/upgrade_adapter';
export function main() {
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(() => {
spyOn(console, 'error');
let consoleErrorSpy: jasmine.Spy = spyOn(console, 'error');
expect(() => {
adapter.bootstrap(html('<ng2></ng2>'), ['ng1']);
flushMicrotasks();
}).toThrowError();
expect(console.error).toHaveBeenCalled();
expect(console.error).toHaveBeenCalledWith(jasmine.any(Error), jasmine.any(String));
let args: any[] = consoleErrorSpy.calls.mostRecent().args;
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', () => {

View File

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

8
npm-shrinkwrap.json generated
View File

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

View File

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

View File

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

View File

@ -9,7 +9,12 @@ function publishRepo {
BUILD_REPO="${COMPONENT}-builds"
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
rm -rf $REPO_DIR
@ -37,14 +42,18 @@ function publishRepo {
for UMD_FILE in ${UMD_FILES}; do
sed -i "s/\\\$\\\$ANGULAR_VERSION\\\$\\\$/${BUILD_VER}/g" ${UMD_FILE}
done
(
cd $REPO_DIR && \
git config credential.helper "store --file=.git/credentials" && \
echo "https://${GITHUB_TOKEN_ANGULAR}:@github.com" > .git/credentials
)
fi
echo `date` > $REPO_DIR/BUILD_INFO
echo $SHA >> $REPO_DIR/BUILD_INFO
(
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.email "${COMMITTER_USER_EMAIL}" && \
git add --all && \
@ -55,9 +64,7 @@ function publishRepo {
}
# Publish all individual packages from packages-dist.
if [[ "$TRAVIS_REPO_SLUG" == "angular/angular" && \
"$TRAVIS_PULL_REQUEST" == "false" && \
"$CI_MODE" == "e2e" ]]; then
function publishPackages {
for dir in dist/packages-dist/*/ dist/tools/@angular/tsc-wrapped
do
COMPONENT="$(basename ${dir})"
@ -66,10 +73,13 @@ if [[ "$TRAVIS_REPO_SLUG" == "angular/angular" && \
COMPONENT="${COMPONENT//_/-}"
JS_BUILD_ARTIFACTS_DIR="${dir}"
REPO_URL="https://github.com/angular/${COMPONENT}-builds.git"
# Use the below URL for testing when using SSH authentication
# REPO_URL="git@github.com:angular/${COMPONENT}-builds.git"
if [[ "$1" == "ssh" ]]; then
REPO_URL="git@github.com:${ORG}/${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`
SHORT_SHA=`git rev-parse --short HEAD`
COMMIT_MSG=`git log --oneline | head -n1`
@ -80,6 +90,18 @@ if [[ "$TRAVIS_REPO_SLUG" == "angular/angular" && \
done
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
echo "Not building the upstream/master branch, build artifacts won't be published."
fi

View File

@ -1,6 +1,6 @@
{
"name": "@angular/tsc-wrapped",
"version": "0.4.2",
"version": "0.5.0",
"description": "Wraps the tsc CLI, allowing extensions.",
"homepage": "https://github.com/angular/angular/tree/master/tools/tsc-wrapped",
"bugs": "https://github.com/angular/angular/issues",

View File

@ -224,6 +224,34 @@ export class MetadataCollector {
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
ts.forEachChild(sourceFile, node => {
switch (node.kind) {
@ -231,8 +259,9 @@ export class MetadataCollector {
const classDeclaration = <ts.ClassDeclaration>node;
if (classDeclaration.name) {
const className = classDeclaration.name.text;
if (node.flags & ts.NodeFlags.Export) {
locals.define(className, {__symbolic: 'reference', name: className});
if (isExported(classDeclaration)) {
locals.define(
className, {__symbolic: 'reference', name: exportedName(classDeclaration)});
} else {
locals.define(
className, errorSym('Reference to non-exported class', node, {className}));
@ -241,9 +270,9 @@ export class MetadataCollector {
break;
case ts.SyntaxKind.FunctionDeclaration:
if (!(node.flags & ts.NodeFlags.Export)) {
const functionDeclaration = <ts.FunctionDeclaration>node;
if (!isExported(functionDeclaration)) {
// Report references to this function as an error.
const functionDeclaration = <ts.FunctionDeclaration>node;
const nameNode = functionDeclaration.name;
if (nameNode && nameNode.text) {
locals.define(
@ -268,10 +297,14 @@ export class MetadataCollector {
if (exportClause) {
exportClause.elements.forEach(spec => {
const name = spec.name.text;
const propNode = spec.propertyName || spec.name;
const value: MetadataValue = evaluator.evaluateNode(propNode);
if (!metadata) metadata = {};
metadata[name] = recordEntry(value, node);
// 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 value: MetadataValue = evaluator.evaluateNode(propNode);
if (!metadata) metadata = {};
metadata[name] = recordEntry(value, node);
}
});
}
}
@ -294,9 +327,9 @@ export class MetadataCollector {
const classDeclaration = <ts.ClassDeclaration>node;
if (classDeclaration.name) {
const className = classDeclaration.name.text;
if (node.flags & ts.NodeFlags.Export) {
if (isExported(classDeclaration)) {
if (!metadata) metadata = {};
metadata[className] = classMetadataOf(classDeclaration);
metadata[exportedName(classDeclaration)] = classMetadataOf(classDeclaration);
}
}
// 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
// names substitution will be performed by the StaticReflector.
const functionDeclaration = <ts.FunctionDeclaration>node;
if (node.flags & ts.NodeFlags.Export) {
if (isExported(functionDeclaration)) {
if (!metadata) metadata = {};
const name = exportedName(functionDeclaration);
const maybeFunc = maybeGetSimpleFunction(functionDeclaration);
if (maybeFunc) {
metadata[maybeFunc.name] = recordEntry(maybeFunc.func, node);
} else if (functionDeclaration.name.kind == ts.SyntaxKind.Identifier) {
const nameNode = <ts.Identifier>functionDeclaration.name;
const functionName = nameNode.text;
metadata[functionName] = {__symbolic: 'function'};
}
metadata[name] =
maybeFunc ? recordEntry(maybeFunc.func, node) : {__symbolic: 'function'};
}
break;
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 enumName = enumDeclaration.name.text;
const enumName = exportedName(enumDeclaration);
let nextDefaultValue: MetadataValue = 0;
let writtenMembers = 0;
for (const member of enumDeclaration.members) {
@ -376,9 +405,10 @@ export class MetadataCollector {
}
let exported = false;
if (variableStatement.flags & ts.NodeFlags.Export ||
variableDeclaration.flags & ts.NodeFlags.Export) {
variableDeclaration.flags & ts.NodeFlags.Export ||
isExportedIdentifier(nameNode)) {
if (!metadata) metadata = {};
metadata[nameNode.text] = recordEntry(varValue, node);
metadata[exportedIdentifierName(nameNode)] = recordEntry(varValue, node);
exported = true;
}
if (isPrimitive(varValue)) {

View File

@ -40,6 +40,7 @@ describe('Collector', () => {
'private-enum.ts',
're-exports.ts',
're-exports-2.ts',
'export-as.d.ts',
'static-field-reference.ts',
'static-method.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', () => {
const source = program.getSourceFile('/re-exports-2.ts');
const metadata = collector.getMetadata(source);
expect(metadata.metadata).toEqual({
MyClass: Object({__symbolic: 'class'}),
OtherModule: {__symbolic: 'reference', module: './static-field-reference', name: 'Foo'},
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'}
},
MyOtherModule: {__symbolic: 'reference', module: './static-field', name: 'MyModule'}
});
});
@ -1007,6 +1007,10 @@ const FILES: Directory = {
class MyClass {}
export {OtherModule, MyModule as MyOtherModule, MyClass};
`,
'export-as.d.ts': `
declare function someFunction(): void;
export { someFunction as SomeFunction };
`,
'local-symbol-ref.ts': `
import {Component, Validators} from 'angular2/core';

View File

@ -305,7 +305,7 @@ export interface ContentChildrenDecorator {
export declare function createPlatform(injector: Injector): PlatformRef;
/** @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 */
export declare const CUSTOM_ELEMENTS_SCHEMA: SchemaMetadata;