Compare commits
72 Commits
2.2.0
...
2.3.0-beta
Author | SHA1 | Date | |
---|---|---|---|
4553948cad | |||
86ffa884b7 | |||
3e548de99d | |||
909268036b | |||
519a324454 | |||
ef96763fa4 | |||
7dcca307d9 | |||
491d5a22a9 | |||
44572f114f | |||
1ef4696cb7 | |||
07a986d330 | |||
59d2b4c831 | |||
2a5bd2f345 | |||
3c06a5dc25 | |||
adeea5d86a | |||
dddbb1c1cb | |||
bccf0e69dc | |||
b15039d228 | |||
2235048432 | |||
484119e59f | |||
24099bdbd2 | |||
912ca44979 | |||
664a6273e1 | |||
c1a62e2154 | |||
aac37bedc0 | |||
a3884db87c | |||
fc5ac1ebc4 | |||
ad20d7d260 | |||
602522beb2 | |||
4e047302f2 | |||
419a812f04 | |||
f340e1a414 | |||
481c9b3258 | |||
8b2dfb2eca | |||
824ea8406c | |||
1f96a93f59 | |||
009d545787 | |||
53c25210a6 | |||
927aa69726 | |||
ce89039036 | |||
42198cd7d5 | |||
d6ba092a27 | |||
773b31de8f | |||
f79b320fc4 | |||
6a212fd561 | |||
be010a292a | |||
7c36e7f956 | |||
13ba2f90b9 | |||
75277cd94b | |||
46d150266b | |||
1b5384ee54 | |||
9f7d32a326 | |||
9de76ebfa5 | |||
46023e4792 | |||
b55aaf094f | |||
d90b622fa4 | |||
79e2bb9291 | |||
efbbefd353 | |||
c2fae72bc6 | |||
7908679c4b | |||
9ed9ff40b3 | |||
2f14415836 | |||
76e4911e8b | |||
ed5e98d0df | |||
146af1fed9 | |||
c60ba7a72f | |||
05beffe0d0 | |||
08c038ebd9 | |||
582550a90d | |||
1d53a870dd | |||
a0c58a6b5c | |||
d3eff6c483 |
75
CHANGELOG.md
75
CHANGELOG.md
@ -1,3 +1,78 @@
|
||||
<a name="2.3.0-beta.1"></a>
|
||||
# [2.3.0-beta.1](https://github.com/angular/angular/compare/2.3.0-beta.0...2.3.0-beta.1) (2016-11-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
Note: The 2.3.0-beta.1 release also contains all the changes present in the 2.2.2 release.
|
||||
|
||||
### Features
|
||||
|
||||
* **language-service:** add services to support editors ([#12987](https://github.com/angular/angular/issues/12987)) ([519a324](https://github.com/angular/angular/commit/519a324))
|
||||
* **tools:** allow disabling annotation lowering ([c1a62e2](https://github.com/angular/angular/commit/c1a62e2))
|
||||
|
||||
<a name="2.2.2"></a>
|
||||
## [2.2.2](https://github.com/angular/angular/compare/2.2.1...2.2.2) (2016-11-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** blend in all previously transitioned styles into next animation if interrupted ([#13014](https://github.com/angular/angular/issues/13014)) ([ea4fc9b](https://github.com/angular/angular/commit/ea4fc9b)), closes [#13013](https://github.com/angular/angular/issues/13013)
|
||||
* **benchmarks:** use sanitized style values ([#12943](https://github.com/angular/angular/issues/12943)) ([33a7902](https://github.com/angular/angular/commit/33a7902))
|
||||
* **closure:** quote date pattern aliases ([#13012](https://github.com/angular/angular/issues/13012)) ([0956ace](https://github.com/angular/angular/commit/0956ace))
|
||||
* **compiler:** fix versions of `@angular/tsc-wrapped` ([2fe6fb1](https://github.com/angular/angular/commit/2fe6fb1))
|
||||
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([8df328b](https://github.com/angular/angular/commit/8df328b))
|
||||
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([511cd4d](https://github.com/angular/angular/commit/511cd4d))
|
||||
* **router:** removes a peer dependency from router to upgrade ([115f18f](https://github.com/angular/angular/commit/115f18f))
|
||||
* **router:** removes a peer dependency from router to upgrade ([87d5d49](https://github.com/angular/angular/commit/87d5d49))
|
||||
* **router:** support redirects to named outlets ([09226d9](https://github.com/angular/angular/commit/09226d9)), closes [#12740](https://github.com/angular/angular/issues/12740) [#9921](https://github.com/angular/angular/issues/9921)
|
||||
* **upgrade:** call ng1 lifecycle hooks ([#12875](https://github.com/angular/angular/issues/12875)) ([462316b](https://github.com/angular/angular/commit/462316b))
|
||||
|
||||
|
||||
|
||||
<a name="2.3.0-beta.0"></a>
|
||||
# [2.3.0-beta.0](https://github.com/angular/angular/compare/2.2.0...2.3.0-beta.0) (2016-11-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** assert xliff messages have translations ([7908679](https://github.com/angular/angular/commit/7908679)), closes [#12815](https://github.com/angular/angular/issues/12815) [#12604](https://github.com/angular/angular/issues/12604)
|
||||
* **compiler:** updates hash algo for xmb/xtb files ([2f14415](https://github.com/angular/angular/commit/2f14415))
|
||||
* **core:** fix placeholders handling in i18n. ([76e4911](https://github.com/angular/angular/commit/76e4911)), closes [#12512](https://github.com/angular/angular/issues/12512)
|
||||
* **core:** misc i18n fixes ([ed5e98d](https://github.com/angular/angular/commit/ed5e98d))
|
||||
* **core:** xmb serializer uses decimal messaged IDs ([08c038e](https://github.com/angular/angular/commit/08c038e)), closes [#12511](https://github.com/angular/angular/issues/12511)
|
||||
* **platform-browser:** enable AOT ([efbbefd](https://github.com/angular/angular/commit/efbbefd)), closes [#12783](https://github.com/angular/angular/issues/12783)
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** add `attachView` / `detachView` to ApplicationRef ([9f7d32a](https://github.com/angular/angular/commit/9f7d32a)), closes [#9293](https://github.com/angular/angular/issues/9293)
|
||||
* **core:** expose `ViewRef` as `ChangeDetectorRef` ([1b5384e](https://github.com/angular/angular/commit/1b5384e)), closes [#12722](https://github.com/angular/angular/issues/12722)
|
||||
* **core:** implements a decimal fingerprint for i18n ([582550a](https://github.com/angular/angular/commit/582550a))
|
||||
* **router:** register router with ngprobe ([c2fae72](https://github.com/angular/angular/commit/c2fae72))
|
||||
* **router_link:** add skipLocationChange and replaceUrl inputs ([#12850](https://github.com/angular/angular/issues/12850)) ([46d1502](https://github.com/angular/angular/commit/46d1502))
|
||||
|
||||
Note: The 2.3.0-beta.0 release also contains all the changes present in the 2.2.1 release.
|
||||
|
||||
<a name="2.2.1"></a>
|
||||
## [2.2.1](https://github.com/angular/angular/compare/2.2.0...2.2.1) (2016-11-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** only pass in same typed players as previous players into web-animations ([#12907](https://github.com/angular/angular/issues/12907)) ([583d283](https://github.com/angular/angular/commit/583d283))
|
||||
* **animations:** retain styling when transition destinations are changed ([#12208](https://github.com/angular/angular/issues/12208)) ([5c46c49](https://github.com/angular/angular/commit/5c46c49)), closes [#9661](https://github.com/angular/angular/issues/9661)
|
||||
* **core:** support `ngTemplateOutlet` in production mode ([#12921](https://github.com/angular/angular/issues/12921)) ([4628798](https://github.com/angular/angular/commit/4628798)), closes [#12911](https://github.com/angular/angular/issues/12911)
|
||||
* **http:** correctly handle response body for 204 status code ([21a4de9](https://github.com/angular/angular/commit/21a4de9)), closes [#12830](https://github.com/angular/angular/issues/12830) [#12393](https://github.com/angular/angular/issues/12393)
|
||||
* **http:** return request url if it cannot be retrieved from response ([845ea23](https://github.com/angular/angular/commit/845ea23)), closes [#12837](https://github.com/angular/angular/issues/12837)
|
||||
* **upgrade:** make AoT ngUpgrade work with the testability API and resumeBootstrap() ([#12910](https://github.com/angular/angular/issues/12910)) ([dc1662a](https://github.com/angular/angular/commit/dc1662a))
|
||||
* **platform-browser:** fix disableDebugTools() ([#12918](https://github.com/angular/angular/issues/12918)) ([7b67bad](https://github.com/angular/angular/commit/7b67bad))
|
||||
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([364642d](https://github.com/angular/angular/commit/364642d))
|
||||
* **router:** removes a peer dependency from router to upgrade ([1dcf1f4](https://github.com/angular/angular/commit/1dcf1f4))
|
||||
* **forms** allow for null values in HTML select options bound with ngValue ([e0ce545](https://github.com/angular/angular/commit/e0ce545)), closes [#10349](https://github.com/angular/angular/issues/10349)
|
||||
* **router:** should not create a route state if navigation is canceled ([#12868](https://github.com/angular/angular/issues/12868)) ([dabaf85](https://github.com/angular/angular/commit/dabaf85)), closes [#12776](https://github.com/angular/angular/issues/12776)
|
||||
* **common:** select should allow for null values in HTML select options bound with ngValue ([e02c180](https://github.com/angular/angular/commit/e02c180)), closes [#12829](https://github.com/angular/angular/issues/12829)
|
||||
* **compiler-cli:** support ctorParams in function closure ([#12876](https://github.com/angular/angular/issues/12876)) ([6cdc3b5](https://github.com/angular/angular/commit/6cdc3b5))
|
||||
|
||||
|
||||
<a name="2.2.0"></a>
|
||||
# [2.2.0 upgrade-firebooster](https://github.com/angular/angular/compare/2.2.0-rc.0...2.2.0) (2016-11-14)
|
||||
|
||||
|
40
build.sh
40
build.sh
@ -17,9 +17,14 @@ PACKAGES=(core
|
||||
upgrade
|
||||
router
|
||||
compiler-cli
|
||||
language-service
|
||||
benchpress)
|
||||
BUILD_ALL=true
|
||||
BUNDLE=true
|
||||
VERSION_PREFIX=$(node -p "require('./package.json').version")
|
||||
VERSION_SUFFIX="-$(git log --oneline -1 | awk '{print $1}')"
|
||||
ROUTER_VERSION_PREFIX=$(node -p "require('./package.json').version.replace(/^2/, '3')")
|
||||
REMOVE_BENCHPRESS=false
|
||||
|
||||
for ARG in "$@"; do
|
||||
case "$ARG" in
|
||||
@ -31,6 +36,10 @@ for ARG in "$@"; do
|
||||
--bundle=*)
|
||||
BUNDLE=( "${ARG#--bundle=}" )
|
||||
;;
|
||||
--publish)
|
||||
VERSION_SUFFIX=""
|
||||
REMOVE_BENCHPRESS=true
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option $ARG."
|
||||
exit 1
|
||||
@ -38,6 +47,10 @@ for ARG in "$@"; do
|
||||
esac
|
||||
done
|
||||
|
||||
VERSION="${VERSION_PREFIX}${VERSION_SUFFIX}"
|
||||
ROUTER_VERSION="${ROUTER_VERSION_PREFIX}${VERSION_SUFFIX}"
|
||||
echo "====== BUILDING: Version ${VERSION} (Router ${ROUTER_VERSION})"
|
||||
|
||||
export NODE_PATH=${NODE_PATH}:$(pwd)/dist/all:$(pwd)/dist/tools
|
||||
TSC="node --max-old-space-size=3000 dist/tools/@angular/tsc-wrapped/src/main"
|
||||
UGLIFYJS=`pwd`/node_modules/.bin/uglifyjs
|
||||
@ -106,7 +119,13 @@ do
|
||||
UMD_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.min.js
|
||||
UMD_STATIC_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-static.umd.min.js
|
||||
UMD_UPGRADE_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-upgrade.umd.min.js
|
||||
LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt
|
||||
|
||||
if [[ ${PACKAGE} != router ]]; then
|
||||
LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt
|
||||
fi
|
||||
if [[ ${PACKAGE} == router ]]; then
|
||||
LICENSE_BANNER=${PWD}/modules/@angular/router-license-banner.txt
|
||||
fi
|
||||
|
||||
rm -rf ${DESTDIR}
|
||||
|
||||
@ -157,7 +176,6 @@ do
|
||||
mv ${UMD_ES5_PATH}.tmp ${UMD_ES5_PATH}
|
||||
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_ES5_MIN_PATH} ${UMD_ES5_PATH}
|
||||
|
||||
|
||||
if [[ -e rollup-testing.config.js ]]; then
|
||||
echo "====== Rollup ${PACKAGE} testing"
|
||||
../../../node_modules/.bin/rollup -c rollup-testing.config.js
|
||||
@ -191,8 +209,24 @@ do
|
||||
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_UPGRADE_ES5_MIN_PATH} ${UMD_UPGRADE_ES5_PATH}
|
||||
fi
|
||||
) 2>&1 | grep -v "as external dependency"
|
||||
|
||||
fi
|
||||
|
||||
(
|
||||
echo "====== VERSION: Updating version references"
|
||||
cd ${DESTDIR}
|
||||
echo "====== EXECUTE: perl -p -i -e \"s/0\.0\.0\-PLACEHOLDER/${VERSION}/g\" $""(grep -ril 0\.0\.0\-PLACEHOLDER .)"
|
||||
perl -p -i -e "s/0\.0\.0\-PLACEHOLDER/${VERSION}/g" $(grep -ril 0\.0\.0\-PLACEHOLDER .) < /dev/null 2> /dev/null
|
||||
echo "====== EXECUTE: perl -p -i -e \"s/0\.0\.0\-ROUTERPLACEHOLDER/${ROUTER_VERSION}/g\" $""(grep -ril 0\.0\.0\-ROUTERPLACEHOLDER .)"
|
||||
perl -p -i -e "s/0\.0\.0\-ROUTERPLACEHOLDER/${ROUTER_VERSION}/g" $(grep -ril 0\.0\.0\-ROUTERPLACEHOLDER .) < /dev/null 2> /dev/null
|
||||
)
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "====== Building examples: ./modules/@angular/examples/build.sh ====="
|
||||
./modules/@angular/examples/build.sh
|
||||
|
||||
if [[ ${REMOVE_BENCHPRESS} == true ]]; then
|
||||
echo ""
|
||||
echo "==== Removing benchpress from publication"
|
||||
rm -r dist/packages-dist/benchpress
|
||||
fi
|
||||
|
@ -4,7 +4,7 @@ machine:
|
||||
|
||||
dependencies:
|
||||
pre:
|
||||
- npm install -g npm
|
||||
- npm install -g npm@3.6.0
|
||||
|
||||
test:
|
||||
override:
|
||||
|
@ -50,7 +50,9 @@ module.exports = function(config) {
|
||||
'dist/all/@angular/**/e2e_test/**',
|
||||
'dist/all/@angular/router/**',
|
||||
'dist/all/@angular/compiler-cli/**',
|
||||
'dist/all/@angular/compiler/test/aot/**',
|
||||
'dist/all/@angular/benchpress/**',
|
||||
'dist/all/@angular/language-service/**',
|
||||
'dist/all/angular1_router.js',
|
||||
'dist/all/@angular/platform-browser/testing/e2e_util.js',
|
||||
'dist/examples/**/e2e_test/**',
|
||||
|
@ -22,6 +22,7 @@
|
||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"annotateForClosureCompiler": true,
|
||||
"strictMetadataEmit": true
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export {AotCompilerHost, AotCompilerHost as StaticReflectorHost, StaticReflector, StaticSymbol} from '@angular/compiler';
|
||||
export {CodeGenerator} from './src/codegen';
|
||||
export {CompilerHost, CompilerHostContext, NodeCompilerHostContext} from './src/compiler_host';
|
||||
export {Extractor} from './src/extractor';
|
||||
export {NodeReflectorHostContext, ReflectorHost, ReflectorHostContext} from './src/reflector_host';
|
||||
export {StaticReflector, StaticReflectorHost, StaticSymbol} from './src/static_reflector';
|
||||
|
||||
export * from '@angular/tsc-wrapped';
|
||||
|
@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE translationbundle [<!ELEMENT translationbundle (translation)*>
|
||||
<!ATTLIST translationbundle lang CDATA #REQUIRED>
|
||||
<!ELEMENT translation (#PCDATA|ph)*>
|
||||
<!ATTLIST translation id CDATA #REQUIRED>
|
||||
<!ELEMENT ph EMPTY>
|
||||
<!ATTLIST ph name CDATA #REQUIRED>
|
||||
]>
|
||||
<translationbundle>
|
||||
<translation id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4">käännä teksti</translation>
|
||||
<translation id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">tervetuloa</translation>
|
||||
<translation id="63a85808f03b8181e36a952e0fa38202c2304862">other-3rdP-component</translation>
|
||||
</translationbundle>
|
@ -34,9 +34,9 @@ const EXPECTED_XMB = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!ELEMENT ex (#PCDATA)>
|
||||
]>
|
||||
<messagebundle>
|
||||
<msg id="63a85808f03b8181e36a952e0fa38202c2304862">other-3rdP-component</msg>
|
||||
<msg id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" desc="desc" meaning="meaning">translate me</msg>
|
||||
<msg id="65cc4ab3b4c438e07c89be2b677d08369fb62da2">Welcome</msg>
|
||||
<msg id="3772663375917578720">other-3rdP-component</msg>
|
||||
<msg id="8136548302122759730" desc="desc" meaning="meaning">translate me</msg>
|
||||
<msg id="3492007542396725315">Welcome</msg>
|
||||
</messagebundle>
|
||||
`;
|
||||
|
||||
@ -79,5 +79,4 @@ describe('template i18n extraction output', () => {
|
||||
const xlf = fs.readFileSync(xlfOutput, {encoding: 'utf-8'});
|
||||
expect(xlf).toEqual(EXPECTED_XLIFF);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -9,7 +9,7 @@
|
||||
"ng-xi18n": "./src/extract_i18n.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/tsc-wrapped": "^0.3.0",
|
||||
"@angular/tsc-wrapped": "^0.4.0",
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
|
@ -17,11 +17,9 @@ import {readFileSync} from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {PathMappedReflectorHost} from './path_mapped_reflector_host';
|
||||
import {CompilerHost, CompilerHostContext} from './compiler_host';
|
||||
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||
import {Console} from './private_import_core';
|
||||
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
|
||||
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
||||
import {StaticReflector, StaticReflectorHost, StaticSymbol} from './static_reflector';
|
||||
|
||||
const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
|
||||
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
|
||||
@ -38,8 +36,8 @@ const PREAMBLE = `/**
|
||||
export class CodeGenerator {
|
||||
constructor(
|
||||
private options: AngularCompilerOptions, private program: ts.Program,
|
||||
public host: ts.CompilerHost, private staticReflector: StaticReflector,
|
||||
private compiler: compiler.OfflineCompiler, private reflectorHost: StaticReflectorHost) {}
|
||||
public host: ts.CompilerHost, private compiler: compiler.AotCompiler,
|
||||
private ngCompilerHost: CompilerHost) {}
|
||||
|
||||
// Write codegen in a directory structure matching the sources.
|
||||
private calculateEmitPath(filePath: string): string {
|
||||
@ -64,33 +62,30 @@ export class CodeGenerator {
|
||||
return path.join(this.options.genDir, relativePath);
|
||||
}
|
||||
|
||||
codegen(options: {transitiveModules: boolean}): Promise<any> {
|
||||
const staticSymbols =
|
||||
extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options);
|
||||
|
||||
return this.compiler.compileModules(staticSymbols, options).then(generatedModules => {
|
||||
generatedModules.forEach(generatedModule => {
|
||||
const sourceFile = this.program.getSourceFile(generatedModule.fileUrl);
|
||||
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
|
||||
this.host.writeFile(
|
||||
emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]);
|
||||
});
|
||||
});
|
||||
codegen(): Promise<any> {
|
||||
return this.compiler
|
||||
.compileAll(this.program.getSourceFiles().map(
|
||||
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)))
|
||||
.then(generatedModules => {
|
||||
generatedModules.forEach(generatedModule => {
|
||||
const sourceFile = this.program.getSourceFile(generatedModule.fileUrl);
|
||||
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
|
||||
this.host.writeFile(
|
||||
emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static create(
|
||||
options: AngularCompilerOptions, cliOptions: NgcCliOptions, program: ts.Program,
|
||||
compilerHost: ts.CompilerHost, reflectorHostContext?: ReflectorHostContext,
|
||||
resourceLoader?: compiler.ResourceLoader, reflectorHost?: ReflectorHost): CodeGenerator {
|
||||
resourceLoader = resourceLoader || {
|
||||
get: (s: string) => {
|
||||
if (!compilerHost.fileExists(s)) {
|
||||
// TODO: We should really have a test for error cases like this!
|
||||
throw new Error(`Compilation failed. Resource file not found: ${s}`);
|
||||
}
|
||||
return Promise.resolve(compilerHost.readFile(s));
|
||||
}
|
||||
};
|
||||
tsCompilerHost: ts.CompilerHost, compilerHostContext?: CompilerHostContext,
|
||||
ngCompilerHost?: CompilerHost): CodeGenerator {
|
||||
if (!ngCompilerHost) {
|
||||
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
|
||||
ngCompilerHost = usePathMapping ?
|
||||
new PathMappedCompilerHost(program, tsCompilerHost, options, compilerHostContext) :
|
||||
new CompilerHost(program, tsCompilerHost, options, compilerHostContext);
|
||||
}
|
||||
const transFile = cliOptions.i18nFile;
|
||||
const locale = cliOptions.locale;
|
||||
let transContent: string = '';
|
||||
@ -101,84 +96,18 @@ export class CodeGenerator {
|
||||
}
|
||||
transContent = readFileSync(transFile, 'utf8');
|
||||
}
|
||||
|
||||
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
|
||||
if (!reflectorHost) {
|
||||
const usePathMapping = !!options.rootDirs && options.rootDirs.length > 0;
|
||||
reflectorHost = usePathMapping ?
|
||||
new PathMappedReflectorHost(program, compilerHost, options, reflectorHostContext) :
|
||||
new ReflectorHost(program, compilerHost, options, reflectorHostContext);
|
||||
}
|
||||
const staticReflector = new StaticReflector(reflectorHost);
|
||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||
const htmlParser =
|
||||
new compiler.I18NHtmlParser(new compiler.HtmlParser(), transContent, cliOptions.i18nFormat);
|
||||
const config = new compiler.CompilerConfig({
|
||||
genDebugInfo: options.debug === true,
|
||||
defaultEncapsulation: ViewEncapsulation.Emulated,
|
||||
logBindingUpdate: false,
|
||||
useJit: false
|
||||
const {compiler: aotCompiler} = compiler.createAotCompiler(ngCompilerHost, {
|
||||
debug: options.debug === true,
|
||||
translations: transContent,
|
||||
i18nFormat: cliOptions.i18nFormat,
|
||||
locale: cliOptions.locale,
|
||||
excludeFilePattern: options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES :
|
||||
GENERATED_FILES
|
||||
});
|
||||
const normalizer =
|
||||
new compiler.DirectiveNormalizer(resourceLoader, urlResolver, htmlParser, config);
|
||||
const expressionParser = new compiler.Parser(new compiler.Lexer());
|
||||
const elementSchemaRegistry = new compiler.DomElementSchemaRegistry();
|
||||
const console = new Console();
|
||||
const tmplParser = new compiler.TemplateParser(
|
||||
expressionParser, elementSchemaRegistry, htmlParser, console, []);
|
||||
const resolver = new compiler.CompileMetadataResolver(
|
||||
new compiler.NgModuleResolver(staticReflector),
|
||||
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
||||
elementSchemaRegistry, normalizer, staticReflector);
|
||||
// TODO(vicb): do not pass cliOptions.i18nFormat here
|
||||
const offlineCompiler = new compiler.OfflineCompiler(
|
||||
resolver, tmplParser, new compiler.StyleCompiler(urlResolver),
|
||||
new compiler.ViewCompiler(config, elementSchemaRegistry),
|
||||
new compiler.DirectiveWrapperCompiler(
|
||||
config, expressionParser, elementSchemaRegistry, console),
|
||||
new compiler.NgModuleCompiler(), new compiler.TypeScriptEmitter(reflectorHost),
|
||||
cliOptions.locale, cliOptions.i18nFormat,
|
||||
new compiler.AnimationParser(elementSchemaRegistry));
|
||||
|
||||
return new CodeGenerator(
|
||||
options, program, compilerHost, staticReflector, offlineCompiler, reflectorHost);
|
||||
return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost);
|
||||
}
|
||||
}
|
||||
|
||||
export function extractProgramSymbols(
|
||||
program: ts.Program, staticReflector: StaticReflector, reflectorHost: StaticReflectorHost,
|
||||
options: AngularCompilerOptions): StaticSymbol[] {
|
||||
// Compare with false since the default should be true
|
||||
const skipFileNames =
|
||||
options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
|
||||
|
||||
const staticSymbols: StaticSymbol[] = [];
|
||||
|
||||
program.getSourceFiles()
|
||||
.filter(sourceFile => !skipFileNames.test(sourceFile.fileName))
|
||||
.forEach(sourceFile => {
|
||||
const absSrcPath = reflectorHost.getCanonicalFileName(sourceFile.fileName);
|
||||
|
||||
const moduleMetadata = staticReflector.getModuleMetadata(absSrcPath);
|
||||
if (!moduleMetadata) {
|
||||
console.log(`WARNING: no metadata found for ${absSrcPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const metadata = moduleMetadata['metadata'];
|
||||
|
||||
if (!metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const symbol of Object.keys(metadata)) {
|
||||
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
|
||||
// Ignore symbols that are only included to record error information.
|
||||
continue;
|
||||
}
|
||||
staticSymbols.push(reflectorHost.findDeclaration(absSrcPath, symbol, absSrcPath));
|
||||
}
|
||||
});
|
||||
|
||||
return staticSymbols;
|
||||
export function excludeFilePattern(options: AngularCompilerOptions): RegExp {
|
||||
return options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
|
||||
}
|
||||
|
247
modules/@angular/compiler-cli/src/compiler_host.ts
Normal file
247
modules/@angular/compiler-cli/src/compiler_host.ts
Normal file
@ -0,0 +1,247 @@
|
||||
/**
|
||||
* @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 {AotCompilerHost, StaticSymbol} from '@angular/compiler';
|
||||
import {AngularCompilerOptions, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
const DTS = /\.d\.ts$/;
|
||||
const NODE_MODULES = '/node_modules/';
|
||||
const IS_GENERATED = /\.(ngfactory|css(\.shim)?)$/;
|
||||
|
||||
export interface CompilerHostContext {
|
||||
fileExists(fileName: string): boolean;
|
||||
directoryExists(directoryName: string): boolean;
|
||||
readFile(fileName: string): string;
|
||||
readResource(fileName: string): Promise<string>;
|
||||
assumeFileExists(fileName: string): void;
|
||||
}
|
||||
|
||||
export class CompilerHost implements AotCompilerHost {
|
||||
protected metadataCollector = new MetadataCollector();
|
||||
protected context: CompilerHostContext;
|
||||
private isGenDirChildOfRootDir: boolean;
|
||||
protected basePath: string;
|
||||
private genDir: string;
|
||||
private resolverCache = new Map<string, ModuleMetadata[]>();
|
||||
|
||||
constructor(
|
||||
protected program: ts.Program, protected compilerHost: ts.CompilerHost,
|
||||
protected options: AngularCompilerOptions, context?: CompilerHostContext) {
|
||||
// normalize the path so that it never ends with '/'.
|
||||
this.basePath = path.normalize(path.join(this.options.basePath, '.')).replace(/\\/g, '/');
|
||||
this.genDir = path.normalize(path.join(this.options.genDir, '.')).replace(/\\/g, '/');
|
||||
|
||||
this.context = context || new NodeCompilerHostContext(compilerHost);
|
||||
const genPath: string = path.relative(this.basePath, this.genDir);
|
||||
this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..');
|
||||
}
|
||||
|
||||
// We use absolute paths on disk as canonical.
|
||||
getCanonicalFileName(fileName: string): string { return fileName; }
|
||||
|
||||
moduleNameToFileName(m: string, containingFile: string) {
|
||||
if (!containingFile || !containingFile.length) {
|
||||
if (m.indexOf('.') === 0) {
|
||||
throw new Error('Resolution of relative paths requires a containing file.');
|
||||
}
|
||||
// Any containing file gives the same result for absolute imports
|
||||
containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts'));
|
||||
}
|
||||
m = m.replace(EXT, '');
|
||||
const resolved =
|
||||
ts.resolveModuleName(m, containingFile.replace(/\\/g, '/'), this.options, this.context)
|
||||
.resolvedModule;
|
||||
return resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* We want a moduleId that will appear in import statements in the generated code.
|
||||
* These need to be in a form that system.js can load, so absolute file paths don't work.
|
||||
*
|
||||
* The `containingFile` is always in the `genDir`, where as the `importedFile` can be in
|
||||
* `genDir`, `node_module` or `basePath`. The `importedFile` is either a generated file or
|
||||
* existing file.
|
||||
*
|
||||
* | genDir | node_module | rootDir
|
||||
* --------------+----------+-------------+----------
|
||||
* generated | relative | relative | n/a
|
||||
* existing file | n/a | absolute | relative(*)
|
||||
*
|
||||
* NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`.
|
||||
*/
|
||||
fileNameToModuleName(importedFile: string, containingFile: string): string {
|
||||
// If a file does not yet exist (because we compile it later), we still need to
|
||||
// assume it exists it so that the `resolve` method works!
|
||||
if (!this.compilerHost.fileExists(importedFile)) {
|
||||
this.context.assumeFileExists(importedFile);
|
||||
}
|
||||
|
||||
containingFile = this.rewriteGenDirPath(containingFile);
|
||||
const containingDir = path.dirname(containingFile);
|
||||
// drop extension
|
||||
importedFile = importedFile.replace(EXT, '');
|
||||
|
||||
const nodeModulesIndex = importedFile.indexOf(NODE_MODULES);
|
||||
const importModule = nodeModulesIndex === -1 ?
|
||||
null :
|
||||
importedFile.substring(nodeModulesIndex + NODE_MODULES.length);
|
||||
const isGeneratedFile = IS_GENERATED.test(importedFile);
|
||||
|
||||
if (isGeneratedFile) {
|
||||
// rewrite to genDir path
|
||||
if (importModule) {
|
||||
// it is generated, therefore we do a relative path to the factory
|
||||
return this.dotRelative(containingDir, this.genDir + NODE_MODULES + importModule);
|
||||
} else {
|
||||
// assume that import is also in `genDir`
|
||||
importedFile = this.rewriteGenDirPath(importedFile);
|
||||
return this.dotRelative(containingDir, importedFile);
|
||||
}
|
||||
} else {
|
||||
// user code import
|
||||
if (importModule) {
|
||||
return importModule;
|
||||
} else {
|
||||
if (!this.isGenDirChildOfRootDir) {
|
||||
// assume that they are on top of each other.
|
||||
importedFile = importedFile.replace(this.basePath, this.genDir);
|
||||
}
|
||||
return this.dotRelative(containingDir, importedFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private dotRelative(from: string, to: string): string {
|
||||
const rPath: string = path.relative(from, to).replace(/\\/g, '/');
|
||||
return rPath.startsWith('.') ? rPath : './' + rPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the path into `genDir` folder while preserving the `node_modules` directory.
|
||||
*/
|
||||
private rewriteGenDirPath(filepath: string) {
|
||||
const nodeModulesIndex = filepath.indexOf(NODE_MODULES);
|
||||
if (nodeModulesIndex !== -1) {
|
||||
// If we are in node_modulse, transplant them into `genDir`.
|
||||
return path.join(this.genDir, filepath.substring(nodeModulesIndex));
|
||||
} else {
|
||||
// pretend that containing file is on top of the `genDir` to normalize the paths.
|
||||
// we apply the `genDir` => `rootDir` delta through `rootDirPrefix` later.
|
||||
return filepath.replace(this.basePath, this.genDir);
|
||||
}
|
||||
}
|
||||
|
||||
protected getSourceFile(filePath: string): ts.SourceFile {
|
||||
const sf = this.program.getSourceFile(filePath);
|
||||
if (!sf) {
|
||||
if (this.context.fileExists(filePath)) {
|
||||
const sourceText = this.context.readFile(filePath);
|
||||
return ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true);
|
||||
}
|
||||
throw new Error(`Source file ${filePath} not present in program.`);
|
||||
}
|
||||
return sf;
|
||||
}
|
||||
|
||||
getMetadataFor(filePath: string): ModuleMetadata[] {
|
||||
if (!this.context.fileExists(filePath)) {
|
||||
// If the file doesn't exists then we cannot return metadata for the file.
|
||||
// This will occur if the user refernced a declared module for which no file
|
||||
// exists for the module (i.e. jQuery or angularjs).
|
||||
return;
|
||||
}
|
||||
if (DTS.test(filePath)) {
|
||||
const metadataPath = filePath.replace(DTS, '.metadata.json');
|
||||
if (this.context.fileExists(metadataPath)) {
|
||||
return this.readMetadata(metadataPath, filePath);
|
||||
}
|
||||
} else {
|
||||
const sf = this.getSourceFile(filePath);
|
||||
const metadata = this.metadataCollector.getMetadata(sf);
|
||||
return metadata ? [metadata] : [];
|
||||
}
|
||||
}
|
||||
|
||||
readMetadata(filePath: string, dtsFilePath: string): ModuleMetadata[] {
|
||||
let metadatas = this.resolverCache.get(filePath);
|
||||
if (metadatas) {
|
||||
return metadatas;
|
||||
}
|
||||
try {
|
||||
const metadataOrMetadatas = JSON.parse(this.context.readFile(filePath));
|
||||
const metadatas = metadataOrMetadatas ?
|
||||
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
|
||||
[];
|
||||
const v1Metadata = metadatas.find(m => m['version'] === 1);
|
||||
let v2Metadata = metadatas.find(m => m['version'] === 2);
|
||||
if (!v2Metadata && v1Metadata) {
|
||||
// patch up v1 to v2 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
|
||||
v2Metadata = {'__symbolic': 'module', 'version': 2, 'metadata': {}};
|
||||
if (v1Metadata.exports) {
|
||||
v2Metadata.exports = v1Metadata.exports;
|
||||
}
|
||||
for (let prop in v1Metadata.metadata) {
|
||||
v2Metadata.metadata[prop] = v1Metadata.metadata[prop];
|
||||
}
|
||||
const sourceText = this.context.readFile(dtsFilePath);
|
||||
const exports = this.metadataCollector.getMetadata(this.getSourceFile(dtsFilePath));
|
||||
if (exports) {
|
||||
for (let prop in exports.metadata) {
|
||||
if (!v2Metadata.metadata[prop]) {
|
||||
v2Metadata.metadata[prop] = exports.metadata[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
metadatas.push(v2Metadata);
|
||||
}
|
||||
this.resolverCache.set(filePath, metadatas);
|
||||
return metadatas;
|
||||
} catch (e) {
|
||||
console.error(`Failed to read JSON file ${filePath}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
loadResource(filePath: string): Promise<string> { return this.context.readResource(filePath); }
|
||||
}
|
||||
|
||||
export class NodeCompilerHostContext implements CompilerHostContext {
|
||||
constructor(private host: ts.CompilerHost) {}
|
||||
|
||||
private assumedExists: {[fileName: string]: boolean} = {};
|
||||
|
||||
fileExists(fileName: string): boolean {
|
||||
return this.assumedExists[fileName] || this.host.fileExists(fileName);
|
||||
}
|
||||
|
||||
directoryExists(directoryName: string): boolean {
|
||||
try {
|
||||
return fs.statSync(directoryName).isDirectory();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
readFile(fileName: string): string { return fs.readFileSync(fileName, 'utf8'); }
|
||||
|
||||
readResource(s: string) {
|
||||
if (!this.host.fileExists(s)) {
|
||||
// TODO: We should really have a test for error cases like this!
|
||||
throw new Error(`Compilation failed. Resource file not found: ${s}`);
|
||||
}
|
||||
return Promise.resolve(this.host.readFile(s));
|
||||
}
|
||||
|
||||
assumeFileExists(fileName: string): void { this.assumedExists[fileName] = true; }
|
||||
}
|
@ -24,17 +24,7 @@ import {Extractor} from './extractor';
|
||||
function extract(
|
||||
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions,
|
||||
program: ts.Program, host: ts.CompilerHost) {
|
||||
const resourceLoader: compiler.ResourceLoader = {
|
||||
get: (s: string) => {
|
||||
if (!host.fileExists(s)) {
|
||||
// TODO: We should really have a test for error cases like this!
|
||||
throw new Error(`Compilation failed. Resource file not found: ${s}`);
|
||||
}
|
||||
return Promise.resolve(host.readFile(s));
|
||||
}
|
||||
};
|
||||
const extractor =
|
||||
Extractor.create(ngOptions, cliOptions.i18nFormat, program, host, resourceLoader);
|
||||
const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host);
|
||||
|
||||
const bundlePromise: Promise<compiler.MessageBundle> = extractor.extract();
|
||||
|
||||
@ -51,9 +41,8 @@ function extract(
|
||||
case 'xliff':
|
||||
case 'xlf':
|
||||
default:
|
||||
const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser());
|
||||
ext = 'xlf';
|
||||
serializer = new compiler.Xliff(htmlParser, compiler.DEFAULT_INTERPOLATION_CONFIG);
|
||||
serializer = new compiler.Xliff();
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -14,86 +14,28 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import * as compiler from '@angular/compiler';
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
import * as tsc from '@angular/tsc-wrapped';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {extractProgramSymbols} from './codegen';
|
||||
import {ReflectorHost} from './reflector_host';
|
||||
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
||||
import {StaticReflector, StaticSymbol} from './static_reflector';
|
||||
import {excludeFilePattern} from './codegen';
|
||||
import {CompilerHost} from './compiler_host';
|
||||
|
||||
export class Extractor {
|
||||
constructor(
|
||||
private options: tsc.AngularCompilerOptions, private program: ts.Program,
|
||||
public host: ts.CompilerHost, private staticReflector: StaticReflector,
|
||||
private messageBundle: compiler.MessageBundle, private reflectorHost: ReflectorHost,
|
||||
private metadataResolver: compiler.CompileMetadataResolver) {}
|
||||
private ngExtractor: compiler.Extractor, private ngCompilerHost: CompilerHost,
|
||||
private program: ts.Program) {}
|
||||
|
||||
extract(): Promise<compiler.MessageBundle> {
|
||||
const programSymbols: StaticSymbol[] =
|
||||
extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options);
|
||||
|
||||
return compiler
|
||||
.analyzeNgModules(programSymbols, {transitiveModules: true}, this.metadataResolver)
|
||||
.then(({files}) => {
|
||||
const errors: compiler.ParseError[] = [];
|
||||
|
||||
files.forEach(file => {
|
||||
const compMetas: compiler.CompileDirectiveMetadata[] = [];
|
||||
file.directives.forEach(directiveType => {
|
||||
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
|
||||
if (dirMeta && dirMeta.isComponent) {
|
||||
compMetas.push(dirMeta);
|
||||
}
|
||||
});
|
||||
compMetas.forEach(compMeta => {
|
||||
const html = compMeta.template.template;
|
||||
const interpolationConfig =
|
||||
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
|
||||
errors.push(
|
||||
...this.messageBundle.updateFromTemplate(html, file.srcUrl, interpolationConfig));
|
||||
});
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(errors.map(e => e.toString()).join('\n'));
|
||||
}
|
||||
|
||||
return this.messageBundle;
|
||||
});
|
||||
return this.ngExtractor.extract(this.program.getSourceFiles().map(
|
||||
sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName)));
|
||||
}
|
||||
|
||||
static create(
|
||||
options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program,
|
||||
compilerHost: ts.CompilerHost, resourceLoader: compiler.ResourceLoader,
|
||||
reflectorHost?: ReflectorHost): Extractor {
|
||||
const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser());
|
||||
|
||||
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
|
||||
if (!reflectorHost) reflectorHost = new ReflectorHost(program, compilerHost, options);
|
||||
const staticReflector = new StaticReflector(reflectorHost);
|
||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||
|
||||
const config = new compiler.CompilerConfig({
|
||||
genDebugInfo: options.debug === true,
|
||||
defaultEncapsulation: ViewEncapsulation.Emulated,
|
||||
logBindingUpdate: false,
|
||||
useJit: false
|
||||
});
|
||||
|
||||
const normalizer =
|
||||
new compiler.DirectiveNormalizer(resourceLoader, urlResolver, htmlParser, config);
|
||||
const elementSchemaRegistry = new compiler.DomElementSchemaRegistry();
|
||||
const resolver = new compiler.CompileMetadataResolver(
|
||||
new compiler.NgModuleResolver(staticReflector),
|
||||
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
||||
elementSchemaRegistry, normalizer, staticReflector);
|
||||
|
||||
// TODO(vicb): implicit tags & attributes
|
||||
const messageBundle = new compiler.MessageBundle(htmlParser, [], {});
|
||||
|
||||
return new Extractor(
|
||||
options, program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver);
|
||||
tsCompilerHost: ts.CompilerHost, ngCompilerHost?: CompilerHost): Extractor {
|
||||
if (!ngCompilerHost) ngCompilerHost = new CompilerHost(program, tsCompilerHost, options);
|
||||
const {extractor: ngExtractor} = compiler.Extractor.create(
|
||||
ngCompilerHost, {excludeFilePattern: excludeFilePattern(options)});
|
||||
return new Extractor(ngExtractor, ngCompilerHost, program);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,9 +19,7 @@ import {CodeGenerator} from './codegen';
|
||||
function codegen(
|
||||
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program,
|
||||
host: ts.CompilerHost) {
|
||||
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen({
|
||||
transitiveModules: true
|
||||
});
|
||||
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen();
|
||||
}
|
||||
|
||||
// CLI entry point
|
||||
|
@ -6,28 +6,28 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StaticSymbol} from '@angular/compiler';
|
||||
import {AngularCompilerOptions, ModuleMetadata} from '@angular/tsc-wrapped';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
|
||||
import {StaticSymbol} from './static_reflector';
|
||||
import {CompilerHost, CompilerHostContext} from './compiler_host';
|
||||
|
||||
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
const DTS = /\.d\.ts$/;
|
||||
|
||||
/**
|
||||
* This version of the reflector host expects that the program will be compiled
|
||||
* This version of the AotCompilerHost expects that the program will be compiled
|
||||
* and executed with a "path mapped" directory structure, where generated files
|
||||
* are in a parallel tree with the sources, and imported using a `./` relative
|
||||
* import. This requires using TS `rootDirs` option and also teaching the module
|
||||
* loader what to do.
|
||||
*/
|
||||
export class PathMappedReflectorHost extends ReflectorHost {
|
||||
export class PathMappedCompilerHost extends CompilerHost {
|
||||
constructor(
|
||||
program: ts.Program, compilerHost: ts.CompilerHost, options: AngularCompilerOptions,
|
||||
context?: ReflectorHostContext) {
|
||||
context?: CompilerHostContext) {
|
||||
super(program, compilerHost, options, context);
|
||||
}
|
||||
|
||||
@ -42,7 +42,14 @@ export class PathMappedReflectorHost extends ReflectorHost {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
protected resolve(m: string, containingFile: string) {
|
||||
moduleNameToFileName(m: string, containingFile: string) {
|
||||
if (!containingFile || !containingFile.length) {
|
||||
if (m.indexOf('.') === 0) {
|
||||
throw new Error('Resolution of relative paths requires a containing file.');
|
||||
}
|
||||
// Any containing file gives the same result for absolute imports
|
||||
containingFile = this.getCanonicalFileName(path.join(this.basePath, 'index.ts'));
|
||||
}
|
||||
for (const root of this.options.rootDirs || ['']) {
|
||||
const rootedContainingFile = path.join(root, containingFile);
|
||||
const resolved =
|
||||
@ -51,7 +58,7 @@ export class PathMappedReflectorHost extends ReflectorHost {
|
||||
if (this.options.traceResolution) {
|
||||
console.log('resolve', m, containingFile, '=>', resolved.resolvedFileName);
|
||||
}
|
||||
return resolved.resolvedFileName;
|
||||
return this.getCanonicalFileName(resolved.resolvedFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -62,10 +69,7 @@ export class PathMappedReflectorHost extends ReflectorHost {
|
||||
* Relativize the paths by checking candidate prefixes of the absolute path, to see if
|
||||
* they are resolvable by the moduleResolution strategy from the CompilerHost.
|
||||
*/
|
||||
getImportPath(containingFile: string, importedFile: string): string {
|
||||
importedFile = this.resolveAssetUrl(importedFile, containingFile);
|
||||
containingFile = this.resolveAssetUrl(containingFile, '');
|
||||
|
||||
fileNameToModuleName(importedFile: string, containingFile: string): string {
|
||||
if (this.options.traceResolution) {
|
||||
console.log(
|
||||
'getImportPath from containingFile', containingFile, 'to importedFile', importedFile);
|
||||
@ -82,7 +86,7 @@ export class PathMappedReflectorHost extends ReflectorHost {
|
||||
}
|
||||
|
||||
const resolvable = (candidate: string) => {
|
||||
const resolved = this.getCanonicalFileName(this.resolve(candidate, importedFile));
|
||||
const resolved = this.moduleNameToFileName(candidate, importedFile);
|
||||
return resolved && resolved.replace(EXT, '') === importedFile.replace(EXT, '');
|
||||
};
|
||||
|
||||
@ -112,7 +116,7 @@ export class PathMappedReflectorHost extends ReflectorHost {
|
||||
`Unable to find any resolvable import for ${importedFile} relative to ${containingFile}`);
|
||||
}
|
||||
|
||||
getMetadataFor(filePath: string): ModuleMetadata {
|
||||
getMetadataFor(filePath: string): ModuleMetadata[] {
|
||||
for (const root of this.options.rootDirs || []) {
|
||||
const rootedPath = path.join(root, filePath);
|
||||
if (!this.compilerHost.fileExists(rootedPath)) {
|
||||
@ -124,16 +128,13 @@ export class PathMappedReflectorHost extends ReflectorHost {
|
||||
if (DTS.test(rootedPath)) {
|
||||
const metadataPath = rootedPath.replace(DTS, '.metadata.json');
|
||||
if (this.context.fileExists(metadataPath)) {
|
||||
const metadata = this.readMetadata(metadataPath);
|
||||
return (Array.isArray(metadata) && metadata.length == 0) ? undefined : metadata;
|
||||
return this.readMetadata(metadataPath, rootedPath);
|
||||
}
|
||||
} else {
|
||||
const sf = this.program.getSourceFile(rootedPath);
|
||||
if (!sf) {
|
||||
throw new Error(`Source file ${rootedPath} not present in program.`);
|
||||
}
|
||||
sf.fileName = this.getCanonicalFileName(sf.fileName);
|
||||
return this.metadataCollector.getMetadata(sf);
|
||||
const sf = this.getSourceFile(rootedPath);
|
||||
sf.fileName = sf.fileName;
|
||||
const metadata = this.metadataCollector.getMetadata(sf);
|
||||
return metadata ? [metadata] : [];
|
||||
}
|
||||
}
|
||||
}
|
@ -16,9 +16,3 @@ export var ReflectionCapabilities: typeof r.ReflectionCapabilities = r.Reflectio
|
||||
|
||||
export type Console = typeof r._Console;
|
||||
export var Console: typeof r.Console = r.Console;
|
||||
|
||||
export var reflector: typeof r.reflector = r.reflector;
|
||||
|
||||
export type SetterFn = typeof r._SetterFn;
|
||||
export type GetterFn = typeof r._GetterFn;
|
||||
export type MethodFn = typeof r._MethodFn;
|
||||
|
@ -1,354 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AssetUrl, ImportGenerator} from '@angular/compiler';
|
||||
import {AngularCompilerOptions, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {StaticReflectorHost, StaticSymbol} from './static_reflector';
|
||||
|
||||
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
const DTS = /\.d\.ts$/;
|
||||
const NODE_MODULES = '/node_modules/';
|
||||
const IS_GENERATED = /\.(ngfactory|css(\.shim)?)$/;
|
||||
|
||||
export interface ReflectorHostContext {
|
||||
fileExists(fileName: string): boolean;
|
||||
directoryExists(directoryName: string): boolean;
|
||||
readFile(fileName: string): string;
|
||||
assumeFileExists(fileName: string): void;
|
||||
}
|
||||
|
||||
export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
|
||||
protected metadataCollector = new MetadataCollector();
|
||||
protected context: ReflectorHostContext;
|
||||
private isGenDirChildOfRootDir: boolean;
|
||||
protected basePath: string;
|
||||
private genDir: string;
|
||||
constructor(
|
||||
protected program: ts.Program, protected compilerHost: ts.CompilerHost,
|
||||
protected options: AngularCompilerOptions, context?: ReflectorHostContext) {
|
||||
// normalize the path so that it never ends with '/'.
|
||||
this.basePath = path.normalize(path.join(this.options.basePath, '.')).replace(/\\/g, '/');
|
||||
this.genDir = path.normalize(path.join(this.options.genDir, '.')).replace(/\\/g, '/');
|
||||
|
||||
this.context = context || new NodeReflectorHostContext(compilerHost);
|
||||
const genPath: string = path.relative(this.basePath, this.genDir);
|
||||
this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..');
|
||||
}
|
||||
|
||||
angularImportLocations() {
|
||||
return {
|
||||
coreDecorators: '@angular/core/src/metadata',
|
||||
diDecorators: '@angular/core/src/di/metadata',
|
||||
diMetadata: '@angular/core/src/di/metadata',
|
||||
diOpaqueToken: '@angular/core/src/di/opaque_token',
|
||||
animationMetadata: '@angular/core/src/animation/metadata',
|
||||
provider: '@angular/core/src/di/provider'
|
||||
};
|
||||
}
|
||||
|
||||
// We use absolute paths on disk as canonical.
|
||||
getCanonicalFileName(fileName: string): string { return fileName; }
|
||||
|
||||
protected resolve(m: string, containingFile: string) {
|
||||
m = m.replace(EXT, '');
|
||||
const resolved =
|
||||
ts.resolveModuleName(m, containingFile.replace(/\\/g, '/'), this.options, this.context)
|
||||
.resolvedModule;
|
||||
return resolved ? resolved.resolvedFileName : null;
|
||||
};
|
||||
|
||||
protected normalizeAssetUrl(url: string): string {
|
||||
const assetUrl = AssetUrl.parse(url);
|
||||
const path = assetUrl ? `${assetUrl.packageName}/${assetUrl.modulePath}` : null;
|
||||
return this.getCanonicalFileName(path);
|
||||
}
|
||||
|
||||
protected resolveAssetUrl(url: string, containingFile: string): string {
|
||||
const assetUrl = this.normalizeAssetUrl(url);
|
||||
if (assetUrl) {
|
||||
return this.getCanonicalFileName(this.resolve(assetUrl, containingFile));
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* We want a moduleId that will appear in import statements in the generated code.
|
||||
* These need to be in a form that system.js can load, so absolute file paths don't work.
|
||||
*
|
||||
* The `containingFile` is always in the `genDir`, where as the `importedFile` can be in
|
||||
* `genDir`, `node_module` or `basePath`. The `importedFile` is either a generated file or
|
||||
* existing file.
|
||||
*
|
||||
* | genDir | node_module | rootDir
|
||||
* --------------+----------+-------------+----------
|
||||
* generated | relative | relative | n/a
|
||||
* existing file | n/a | absolute | relative(*)
|
||||
*
|
||||
* NOTE: (*) the relative path is computed depending on `isGenDirChildOfRootDir`.
|
||||
*/
|
||||
getImportPath(containingFile: string, importedFile: string): string {
|
||||
importedFile = this.resolveAssetUrl(importedFile, containingFile);
|
||||
containingFile = this.resolveAssetUrl(containingFile, '');
|
||||
|
||||
// If a file does not yet exist (because we compile it later), we still need to
|
||||
// assume it exists it so that the `resolve` method works!
|
||||
if (!this.compilerHost.fileExists(importedFile)) {
|
||||
this.context.assumeFileExists(importedFile);
|
||||
}
|
||||
|
||||
containingFile = this.rewriteGenDirPath(containingFile);
|
||||
const containingDir = path.dirname(containingFile);
|
||||
// drop extension
|
||||
importedFile = importedFile.replace(EXT, '');
|
||||
|
||||
const nodeModulesIndex = importedFile.indexOf(NODE_MODULES);
|
||||
const importModule = nodeModulesIndex === -1 ?
|
||||
null :
|
||||
importedFile.substring(nodeModulesIndex + NODE_MODULES.length);
|
||||
const isGeneratedFile = IS_GENERATED.test(importedFile);
|
||||
|
||||
if (isGeneratedFile) {
|
||||
// rewrite to genDir path
|
||||
if (importModule) {
|
||||
// it is generated, therefore we do a relative path to the factory
|
||||
return this.dotRelative(containingDir, this.genDir + NODE_MODULES + importModule);
|
||||
} else {
|
||||
// assume that import is also in `genDir`
|
||||
importedFile = this.rewriteGenDirPath(importedFile);
|
||||
return this.dotRelative(containingDir, importedFile);
|
||||
}
|
||||
} else {
|
||||
// user code import
|
||||
if (importModule) {
|
||||
return importModule;
|
||||
} else {
|
||||
if (!this.isGenDirChildOfRootDir) {
|
||||
// assume that they are on top of each other.
|
||||
importedFile = importedFile.replace(this.basePath, this.genDir);
|
||||
}
|
||||
return this.dotRelative(containingDir, importedFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private dotRelative(from: string, to: string): string {
|
||||
const rPath: string = path.relative(from, to).replace(/\\/g, '/');
|
||||
return rPath.startsWith('.') ? rPath : './' + rPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the path into `genDir` folder while preserving the `node_modules` directory.
|
||||
*/
|
||||
private rewriteGenDirPath(filepath: string) {
|
||||
const nodeModulesIndex = filepath.indexOf(NODE_MODULES);
|
||||
if (nodeModulesIndex !== -1) {
|
||||
// If we are in node_modulse, transplant them into `genDir`.
|
||||
return path.join(this.genDir, filepath.substring(nodeModulesIndex));
|
||||
} else {
|
||||
// pretend that containing file is on top of the `genDir` to normalize the paths.
|
||||
// we apply the `genDir` => `rootDir` delta through `rootDirPrefix` later.
|
||||
return filepath.replace(this.basePath, this.genDir);
|
||||
}
|
||||
}
|
||||
|
||||
findDeclaration(
|
||||
module: string, symbolName: string, containingFile: string,
|
||||
containingModule?: string): StaticSymbol {
|
||||
if (!containingFile || !containingFile.length) {
|
||||
if (module.indexOf('.') === 0) {
|
||||
throw new Error('Resolution of relative paths requires a containing file.');
|
||||
}
|
||||
// Any containing file gives the same result for absolute imports
|
||||
containingFile = path.join(this.basePath, 'index.ts');
|
||||
}
|
||||
|
||||
try {
|
||||
const assetUrl = this.normalizeAssetUrl(module);
|
||||
if (assetUrl) {
|
||||
module = assetUrl;
|
||||
}
|
||||
const filePath = this.resolve(module, containingFile);
|
||||
|
||||
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.
|
||||
return this.getStaticSymbol(module, symbolName);
|
||||
}
|
||||
|
||||
const tc = this.program.getTypeChecker();
|
||||
const sf = this.program.getSourceFile(filePath);
|
||||
if (!sf || !(<any>sf).symbol) {
|
||||
// The source file was not needed in the compile but we do need the values from
|
||||
// the corresponding .ts files stored in the .metadata.json file. Check the file
|
||||
// for exports to see if the file is exported.
|
||||
return this.resolveExportedSymbol(filePath, symbolName) ||
|
||||
this.getStaticSymbol(filePath, symbolName);
|
||||
}
|
||||
|
||||
let symbol = tc.getExportsOfModule((<any>sf).symbol).find(m => m.name === symbolName);
|
||||
if (!symbol) {
|
||||
throw new Error(`can't find symbol ${symbolName} exported from module ${filePath}`);
|
||||
}
|
||||
if (symbol &&
|
||||
symbol.flags & ts.SymbolFlags.Alias) { // This is an alias, follow what it aliases
|
||||
symbol = tc.getAliasedSymbol(symbol);
|
||||
}
|
||||
const declaration = symbol.getDeclarations()[0];
|
||||
const declarationFile = this.getCanonicalFileName(declaration.getSourceFile().fileName);
|
||||
|
||||
return this.getStaticSymbol(declarationFile, symbol.getName());
|
||||
} catch (e) {
|
||||
console.error(`can't resolve module ${module} from ${containingFile}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private typeCache = new Map<string, StaticSymbol>();
|
||||
private resolverCache = new Map<string, ModuleMetadata>();
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
const memberSuffix = members ? `.${ members.join('.')}` : '';
|
||||
const key = `"${declarationFile}".${name}${memberSuffix}`;
|
||||
let result = this.typeCache.get(key);
|
||||
if (!result) {
|
||||
result = new StaticSymbol(declarationFile, name, members);
|
||||
this.typeCache.set(key, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getMetadataFor(filePath: string): ModuleMetadata {
|
||||
if (!this.context.fileExists(filePath)) {
|
||||
// If the file doesn't exists then we cannot return metadata for the file.
|
||||
// This will occur if the user refernced a declared module for which no file
|
||||
// exists for the module (i.e. jQuery or angularjs).
|
||||
return;
|
||||
}
|
||||
if (DTS.test(filePath)) {
|
||||
const metadataPath = filePath.replace(DTS, '.metadata.json');
|
||||
if (this.context.fileExists(metadataPath)) {
|
||||
const metadata = this.readMetadata(metadataPath);
|
||||
return (Array.isArray(metadata) && metadata.length == 0) ? undefined : metadata;
|
||||
}
|
||||
} else {
|
||||
const sf = this.program.getSourceFile(filePath);
|
||||
if (!sf) {
|
||||
if (this.context.fileExists(filePath)) {
|
||||
const sourceText = this.context.readFile(filePath);
|
||||
return this.metadataCollector.getMetadata(
|
||||
ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true));
|
||||
}
|
||||
|
||||
throw new Error(`Source file ${filePath} not present in program.`);
|
||||
}
|
||||
return this.metadataCollector.getMetadata(sf);
|
||||
}
|
||||
}
|
||||
|
||||
readMetadata(filePath: string) {
|
||||
try {
|
||||
return this.resolverCache.get(filePath) || JSON.parse(this.context.readFile(filePath));
|
||||
} catch (e) {
|
||||
console.error(`Failed to read JSON file ${filePath}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private getResolverMetadata(filePath: string): ModuleMetadata {
|
||||
let metadata = this.resolverCache.get(filePath);
|
||||
if (!metadata) {
|
||||
metadata = this.getMetadataFor(filePath);
|
||||
this.resolverCache.set(filePath, metadata);
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
|
||||
protected resolveExportedSymbol(filePath: string, symbolName: string): StaticSymbol {
|
||||
const resolveModule = (moduleName: string): string => {
|
||||
const resolvedModulePath = this.getCanonicalFileName(this.resolve(moduleName, filePath));
|
||||
if (!resolvedModulePath) {
|
||||
throw new Error(`Could not resolve module '${moduleName}' relative to file ${filePath}`);
|
||||
}
|
||||
return resolvedModulePath;
|
||||
};
|
||||
const metadata = this.getResolverMetadata(filePath);
|
||||
if (metadata) {
|
||||
// If we have metadata for the symbol, this is the original exporting location.
|
||||
if (metadata.metadata[symbolName]) {
|
||||
return this.getStaticSymbol(filePath, symbolName);
|
||||
}
|
||||
|
||||
// If no, try to find the symbol in one of the re-export location
|
||||
if (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 => {
|
||||
if (typeof symbol === 'string') {
|
||||
return symbol == symbolName;
|
||||
} else {
|
||||
return symbol.as == symbolName;
|
||||
}
|
||||
});
|
||||
if (exportSymbol) {
|
||||
let symName = symbolName;
|
||||
if (typeof exportSymbol !== 'string') {
|
||||
symName = exportSymbol.name;
|
||||
}
|
||||
return this.resolveExportedSymbol(resolveModule(moduleExport.from), symName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to find the symbol via export * directives.
|
||||
for (const moduleExport of metadata.exports) {
|
||||
if (!moduleExport.export) {
|
||||
const resolvedModule = resolveModule(moduleExport.from);
|
||||
const candidateSymbol = this.resolveExportedSymbol(resolvedModule, symbolName);
|
||||
if (candidateSymbol) return candidateSymbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeReflectorHostContext implements ReflectorHostContext {
|
||||
constructor(private host: ts.CompilerHost) {}
|
||||
|
||||
private assumedExists: {[fileName: string]: boolean} = {};
|
||||
|
||||
fileExists(fileName: string): boolean {
|
||||
return this.assumedExists[fileName] || this.host.fileExists(fileName);
|
||||
}
|
||||
|
||||
directoryExists(directoryName: string): boolean {
|
||||
try {
|
||||
return fs.statSync(directoryName).isDirectory();
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
readFile(fileName: string): string { return fs.readFileSync(fileName, 'utf8'); }
|
||||
|
||||
assumeFileExists(fileName: string): void { this.assumedExists[fileName] = true; }
|
||||
}
|
219
modules/@angular/compiler-cli/test/aot_host_spec.ts
Normal file
219
modules/@angular/compiler-cli/test/aot_host_spec.ts
Normal file
@ -0,0 +1,219 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {CompilerHost} from '../src/compiler_host';
|
||||
|
||||
import {Directory, Entry, MockAotContext, MockCompilerHost} from './mocks';
|
||||
|
||||
describe('CompilerHost', () => {
|
||||
let context: MockAotContext;
|
||||
let host: ts.CompilerHost;
|
||||
let program: ts.Program;
|
||||
let hostNestedGenDir: CompilerHost;
|
||||
let hostSiblingGenDir: CompilerHost;
|
||||
|
||||
beforeEach(() => {
|
||||
context = new MockAotContext('/tmp/src', clone(FILES));
|
||||
host = new MockCompilerHost(context);
|
||||
program = ts.createProgram(
|
||||
['main.ts'], {
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
},
|
||||
host);
|
||||
// Force a typecheck
|
||||
const errors = program.getSemanticDiagnostics();
|
||||
if (errors && errors.length) {
|
||||
throw new Error('Expected no errors');
|
||||
}
|
||||
hostNestedGenDir = new CompilerHost(
|
||||
program, host, {
|
||||
genDir: '/tmp/project/src/gen/',
|
||||
basePath: '/tmp/project/src',
|
||||
skipMetadataEmit: false,
|
||||
strictMetadataEmit: false,
|
||||
skipTemplateCodegen: false,
|
||||
trace: false
|
||||
},
|
||||
context);
|
||||
hostSiblingGenDir = new CompilerHost(
|
||||
program, host, {
|
||||
genDir: '/tmp/project/gen',
|
||||
basePath: '/tmp/project/src/',
|
||||
skipMetadataEmit: false,
|
||||
strictMetadataEmit: false,
|
||||
skipTemplateCodegen: false,
|
||||
trace: false
|
||||
},
|
||||
context);
|
||||
});
|
||||
|
||||
describe('nestedGenDir', () => {
|
||||
it('should import node_module from factory', () => {
|
||||
expect(hostNestedGenDir.fileNameToModuleName(
|
||||
'/tmp/project/node_modules/@angular/core.d.ts',
|
||||
'/tmp/project/src/gen/my.ngfactory.ts', ))
|
||||
.toEqual('@angular/core');
|
||||
});
|
||||
|
||||
it('should import factory from factory', () => {
|
||||
expect(hostNestedGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/my.other.ngfactory.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||
.toEqual('./my.other.ngfactory');
|
||||
expect(hostNestedGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/my.other.css.ts', '/tmp/project/src/a/my.ngfactory.ts'))
|
||||
.toEqual('../my.other.css');
|
||||
expect(hostNestedGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||
.toEqual('./a/my.other.css.shim');
|
||||
});
|
||||
|
||||
it('should import application from factory', () => {
|
||||
expect(hostNestedGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||
.toEqual('../my.other');
|
||||
expect(hostNestedGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/my.other.ts', '/tmp/project/src/a/my.ngfactory.ts'))
|
||||
.toEqual('../../my.other');
|
||||
expect(hostNestedGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/a/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||
.toEqual('../a/my.other');
|
||||
});
|
||||
});
|
||||
|
||||
describe('siblingGenDir', () => {
|
||||
it('should import node_module from factory', () => {
|
||||
expect(hostSiblingGenDir.fileNameToModuleName(
|
||||
'/tmp/project/node_modules/@angular/core.d.ts',
|
||||
'/tmp/project/src/gen/my.ngfactory.ts'))
|
||||
.toEqual('@angular/core');
|
||||
});
|
||||
|
||||
it('should import factory from factory', () => {
|
||||
expect(hostSiblingGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/my.other.ngfactory.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||
.toEqual('./my.other.ngfactory');
|
||||
expect(hostSiblingGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/my.other.css.ts', '/tmp/project/src/a/my.ngfactory.ts'))
|
||||
.toEqual('../my.other.css');
|
||||
expect(hostSiblingGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||
.toEqual('./a/my.other.css.shim');
|
||||
});
|
||||
|
||||
it('should import application from factory', () => {
|
||||
expect(hostSiblingGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||
.toEqual('./my.other');
|
||||
expect(hostSiblingGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/my.other.ts', '/tmp/project/src/a/my.ngfactory.ts'))
|
||||
.toEqual('../my.other');
|
||||
expect(hostSiblingGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/a/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||
.toEqual('./a/my.other');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to produce an import from main @angular/core', () => {
|
||||
expect(hostNestedGenDir.fileNameToModuleName(
|
||||
'/tmp/project/node_modules/@angular/core.d.ts', '/tmp/project/src/main.ts'))
|
||||
.toEqual('@angular/core');
|
||||
});
|
||||
|
||||
it('should be able to produce an import from main to a sub-directory', () => {
|
||||
expect(hostNestedGenDir.fileNameToModuleName('lib/utils.ts', 'main.ts')).toEqual('./lib/utils');
|
||||
});
|
||||
|
||||
it('should be able to produce an import from to a peer file', () => {
|
||||
expect(hostNestedGenDir.fileNameToModuleName('lib/collections.ts', 'lib/utils.ts'))
|
||||
.toEqual('./collections');
|
||||
});
|
||||
|
||||
it('should be able to produce an import from to a sibling directory', () => {
|
||||
expect(hostNestedGenDir.fileNameToModuleName('lib/utils.ts', 'lib2/utils2.ts'))
|
||||
.toEqual('../lib/utils');
|
||||
});
|
||||
|
||||
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'}}}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
|
||||
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should be able to read empty metadata ', () => {
|
||||
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/empty.d.ts')).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return undefined for missing modules', () => {
|
||||
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should add missing v2 metadata from v1 metadata and .d.ts files', () => {
|
||||
expect(hostNestedGenDir.getMetadataFor('metadata_versions/v1.d.ts')).toEqual([
|
||||
{__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}, {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
metadata: {foo: {__symbolic: 'class'}, bar: {__symbolic: 'class'}}
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
const dummyModule = 'export let foo: any[];';
|
||||
|
||||
const FILES: Entry = {
|
||||
'tmp': {
|
||||
'src': {
|
||||
'main.ts': `
|
||||
import * as c from '@angular/core';
|
||||
import * as r from '@angular/router';
|
||||
import * as u from './lib/utils';
|
||||
import * as cs from './lib/collections';
|
||||
import * as u2 from './lib2/utils2';
|
||||
`,
|
||||
'lib': {
|
||||
'utils.ts': dummyModule,
|
||||
'collections.ts': dummyModule,
|
||||
},
|
||||
'lib2': {'utils2.ts': dummyModule},
|
||||
'node_modules': {
|
||||
'@angular': {
|
||||
'core.d.ts': dummyModule,
|
||||
'core.metadata.json':
|
||||
`{"__symbolic":"module", "version": 2, "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;',
|
||||
'empty.metadata.json': '[]',
|
||||
}
|
||||
},
|
||||
'metadata_versions': {
|
||||
'v1.d.ts': 'export declare class bar {}',
|
||||
'v1.metadata.json':
|
||||
`{"__symbolic":"module", "version": 1, "metadata": {"foo": {"__symbolic": "class"}}}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function clone(entry: Entry): Entry {
|
||||
if (typeof entry === 'string') {
|
||||
return entry;
|
||||
} else {
|
||||
const result: Directory = {};
|
||||
for (const name in entry) {
|
||||
result[name] = clone(entry[name]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -6,14 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ReflectorHostContext} from '@angular/compiler-cli/src/reflector_host';
|
||||
import {CompilerHostContext} from '@angular/compiler-cli/src/compiler_host';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
export type Entry = string | Directory;
|
||||
|
||||
export interface Directory { [name: string]: Entry; }
|
||||
|
||||
export class MockContext implements ReflectorHostContext {
|
||||
export class MockAotContext implements CompilerHostContext {
|
||||
constructor(public currentDirectory: string, private files: Entry) {}
|
||||
|
||||
fileExists(fileName: string): boolean { return typeof this.getEntry(fileName) === 'string'; }
|
||||
@ -28,6 +28,14 @@ export class MockContext implements ReflectorHostContext {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
readResource(fileName: string): Promise<string> {
|
||||
const result = this.readFile(fileName);
|
||||
if (result == null) {
|
||||
return Promise.reject(new Error(`Resource not found: ${fileName}`));
|
||||
}
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
writeFile(fileName: string, data: string): void {
|
||||
const parts = fileName.split('/');
|
||||
const name = parts.pop();
|
||||
@ -89,7 +97,7 @@ function normalize(parts: string[]): string[] {
|
||||
}
|
||||
|
||||
export class MockCompilerHost implements ts.CompilerHost {
|
||||
constructor(private context: MockContext) {}
|
||||
constructor(private context: MockAotContext) {}
|
||||
|
||||
fileExists(fileName: string): boolean { return this.context.fileExists(fileName); }
|
||||
|
||||
|
@ -1,329 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ReflectorHost} from '../src/reflector_host';
|
||||
|
||||
import {Directory, Entry, MockCompilerHost, MockContext} from './mocks';
|
||||
|
||||
describe('reflector_host', () => {
|
||||
let context: MockContext;
|
||||
let host: ts.CompilerHost;
|
||||
let program: ts.Program;
|
||||
let reflectorNestedGenDir: ReflectorHost;
|
||||
let reflectorSiblingGenDir: ReflectorHost;
|
||||
|
||||
beforeEach(() => {
|
||||
context = new MockContext('/tmp/src', clone(FILES));
|
||||
host = new MockCompilerHost(context);
|
||||
program = ts.createProgram(
|
||||
['main.ts'], {
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
},
|
||||
host);
|
||||
// Force a typecheck
|
||||
const errors = program.getSemanticDiagnostics();
|
||||
if (errors && errors.length) {
|
||||
throw new Error('Expected no errors');
|
||||
}
|
||||
reflectorNestedGenDir = new ReflectorHost(
|
||||
program, host, {
|
||||
genDir: '/tmp/project/src/gen/',
|
||||
basePath: '/tmp/project/src',
|
||||
skipMetadataEmit: false,
|
||||
strictMetadataEmit: false,
|
||||
skipTemplateCodegen: false,
|
||||
trace: false
|
||||
},
|
||||
context);
|
||||
reflectorSiblingGenDir = new ReflectorHost(
|
||||
program, host, {
|
||||
genDir: '/tmp/project/gen',
|
||||
basePath: '/tmp/project/src/',
|
||||
skipMetadataEmit: false,
|
||||
strictMetadataEmit: false,
|
||||
skipTemplateCodegen: false,
|
||||
trace: false
|
||||
},
|
||||
context);
|
||||
});
|
||||
|
||||
describe('nestedGenDir', () => {
|
||||
it('should import node_module from factory', () => {
|
||||
expect(reflectorNestedGenDir.getImportPath(
|
||||
'/tmp/project/src/gen/my.ngfactory.ts',
|
||||
'/tmp/project/node_modules/@angular/core.d.ts'))
|
||||
.toEqual('@angular/core');
|
||||
});
|
||||
|
||||
it('should import factory from factory', () => {
|
||||
expect(reflectorNestedGenDir.getImportPath(
|
||||
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ngfactory.ts'))
|
||||
.toEqual('./my.other.ngfactory');
|
||||
expect(reflectorNestedGenDir.getImportPath(
|
||||
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.css.ts'))
|
||||
.toEqual('../my.other.css');
|
||||
expect(reflectorNestedGenDir.getImportPath(
|
||||
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.css.shim.ts'))
|
||||
.toEqual('./a/my.other.css.shim');
|
||||
});
|
||||
|
||||
it('should import application from factory', () => {
|
||||
expect(reflectorNestedGenDir.getImportPath(
|
||||
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
|
||||
.toEqual('../my.other');
|
||||
expect(reflectorNestedGenDir.getImportPath(
|
||||
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
|
||||
.toEqual('../../my.other');
|
||||
expect(reflectorNestedGenDir.getImportPath(
|
||||
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.ts'))
|
||||
.toEqual('../a/my.other');
|
||||
});
|
||||
});
|
||||
|
||||
describe('nestedGenDir', () => {
|
||||
it('should import node_module from factory', () => {
|
||||
expect(reflectorSiblingGenDir.getImportPath(
|
||||
'/tmp/project/src/gen/my.ngfactory.ts',
|
||||
'/tmp/project/node_modules/@angular/core.d.ts'))
|
||||
.toEqual('@angular/core');
|
||||
});
|
||||
|
||||
it('should import factory from factory', () => {
|
||||
expect(reflectorSiblingGenDir.getImportPath(
|
||||
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ngfactory.ts'))
|
||||
.toEqual('./my.other.ngfactory');
|
||||
expect(reflectorSiblingGenDir.getImportPath(
|
||||
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.css.ts'))
|
||||
.toEqual('../my.other.css');
|
||||
expect(reflectorSiblingGenDir.getImportPath(
|
||||
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.css.shim.ts'))
|
||||
.toEqual('./a/my.other.css.shim');
|
||||
});
|
||||
|
||||
it('should import application from factory', () => {
|
||||
expect(reflectorSiblingGenDir.getImportPath(
|
||||
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
|
||||
.toEqual('./my.other');
|
||||
expect(reflectorSiblingGenDir.getImportPath(
|
||||
'/tmp/project/src/a/my.ngfactory.ts', '/tmp/project/src/my.other.ts'))
|
||||
.toEqual('../my.other');
|
||||
expect(reflectorSiblingGenDir.getImportPath(
|
||||
'/tmp/project/src/my.ngfactory.ts', '/tmp/project/src/a/my.other.ts'))
|
||||
.toEqual('./a/my.other');
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide the import locations for angular', () => {
|
||||
const {coreDecorators, diDecorators, diMetadata, animationMetadata, provider} =
|
||||
reflectorNestedGenDir.angularImportLocations();
|
||||
expect(coreDecorators).toEqual('@angular/core/src/metadata');
|
||||
expect(diDecorators).toEqual('@angular/core/src/di/metadata');
|
||||
expect(diMetadata).toEqual('@angular/core/src/di/metadata');
|
||||
expect(animationMetadata).toEqual('@angular/core/src/animation/metadata');
|
||||
expect(provider).toEqual('@angular/core/src/di/provider');
|
||||
});
|
||||
|
||||
it('should be able to produce an import from main @angular/core', () => {
|
||||
expect(reflectorNestedGenDir.getImportPath(
|
||||
'/tmp/project/src/main.ts', '/tmp/project/node_modules/@angular/core.d.ts'))
|
||||
.toEqual('@angular/core');
|
||||
});
|
||||
|
||||
it('should be able to produce an import from main to a sub-directory', () => {
|
||||
expect(reflectorNestedGenDir.getImportPath('main.ts', 'lib/utils.ts')).toEqual('./lib/utils');
|
||||
});
|
||||
|
||||
it('should be able to produce an import from to a peer file', () => {
|
||||
expect(reflectorNestedGenDir.getImportPath('lib/utils.ts', 'lib/collections.ts'))
|
||||
.toEqual('./collections');
|
||||
});
|
||||
|
||||
it('should be able to produce an import from to a sibling directory', () => {
|
||||
expect(reflectorNestedGenDir.getImportPath('lib2/utils2.ts', 'lib/utils.ts'))
|
||||
.toEqual('../lib/utils');
|
||||
});
|
||||
|
||||
it('should be able to produce a symbol for an exported symbol', () => {
|
||||
expect(reflectorNestedGenDir.findDeclaration('@angular/router', 'foo', 'main.ts'))
|
||||
.toBeDefined();
|
||||
});
|
||||
|
||||
it('should be able to produce a symbol for values space only reference', () => {
|
||||
expect(reflectorNestedGenDir.findDeclaration('@angular/router/src/providers', 'foo', 'main.ts'))
|
||||
.toBeDefined();
|
||||
});
|
||||
|
||||
|
||||
it('should be produce the same symbol if asked twice', () => {
|
||||
const foo1 = reflectorNestedGenDir.getStaticSymbol('main.ts', 'foo');
|
||||
const foo2 = reflectorNestedGenDir.getStaticSymbol('main.ts', 'foo');
|
||||
expect(foo1).toBe(foo2);
|
||||
});
|
||||
|
||||
it('should be able to produce a symbol for a module with no file', () => {
|
||||
expect(reflectorNestedGenDir.getStaticSymbol('angularjs', 'SomeAngularSymbol')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should be able to read a metadata file', () => {
|
||||
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/core.d.ts'))
|
||||
.toEqual({__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}});
|
||||
});
|
||||
|
||||
it('should be able to read metadata from an otherwise unused .d.ts file ', () => {
|
||||
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/unused.d.ts'))
|
||||
.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should be able to read empty metadata ', () => {
|
||||
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/empty.d.ts'))
|
||||
.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for missing modules', () => {
|
||||
expect(reflectorNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts'))
|
||||
.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should be able to trace a named export', () => {
|
||||
const symbol = reflectorNestedGenDir.findDeclaration(
|
||||
'./reexport/reexport.d.ts', '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 = reflectorNestedGenDir.findDeclaration(
|
||||
'./reexport/reexport.d.ts', '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 = reflectorNestedGenDir.findDeclaration(
|
||||
'./reexport/reexport.d.ts', '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 = reflectorNestedGenDir.findDeclaration(
|
||||
'./reexport/reexport.d.ts', 'Thirty', '/tmp/src/main.ts');
|
||||
expect(symbol.name).toEqual('Thirty');
|
||||
expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin30.d.ts');
|
||||
});
|
||||
});
|
||||
|
||||
const dummyModule = 'export let foo: any[];';
|
||||
|
||||
const FILES: Entry = {
|
||||
'tmp': {
|
||||
'src': {
|
||||
'main.ts': `
|
||||
import * as c from '@angular/core';
|
||||
import * as r from '@angular/router';
|
||||
import * as u from './lib/utils';
|
||||
import * as cs from './lib/collections';
|
||||
import * as u2 from './lib2/utils2';
|
||||
`,
|
||||
'lib': {
|
||||
'utils.ts': dummyModule,
|
||||
'collections.ts': dummyModule,
|
||||
},
|
||||
'lib2': {'utils2.ts': dummyModule},
|
||||
'reexport': {
|
||||
'reexport.d.ts': `
|
||||
import * as c from '@angular/core';
|
||||
`,
|
||||
'reexport.metadata.json': JSON.stringify({
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
metadata: {},
|
||||
exports: [
|
||||
{from: './src/origin1', export: ['One', 'Two', {name: 'Three', as: 'Four'}]},
|
||||
{from: './src/origin5'}, {from: './src/reexport2'}
|
||||
]
|
||||
}),
|
||||
'src': {
|
||||
'origin1.d.ts': `
|
||||
export class One {}
|
||||
export class Two {}
|
||||
export class Three {}
|
||||
`,
|
||||
'origin1.metadata.json': JSON.stringify({
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
metadata: {
|
||||
One: {__symbolic: 'class'},
|
||||
Two: {__symbolic: 'class'},
|
||||
Three: {__symbolic: 'class'},
|
||||
},
|
||||
}),
|
||||
'origin5.d.ts': `
|
||||
export class Five {}
|
||||
`,
|
||||
'origin5.metadata.json': JSON.stringify({
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
metadata: {
|
||||
Five: {__symbolic: 'class'},
|
||||
},
|
||||
}),
|
||||
'origin30.d.ts': `
|
||||
export class Thirty {}
|
||||
`,
|
||||
'origin30.metadata.json': JSON.stringify({
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
metadata: {
|
||||
Thirty: {__symbolic: 'class'},
|
||||
},
|
||||
}),
|
||||
'originNone.d.ts': dummyModule,
|
||||
'originNone.metadata.json': JSON.stringify({
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
metadata: {},
|
||||
}),
|
||||
'reexport2.d.ts': dummyModule,
|
||||
'reexport2.metadata.json': JSON.stringify({
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
metadata: {},
|
||||
exports: [{from: './originNone'}, {from: './origin30'}]
|
||||
})
|
||||
}
|
||||
},
|
||||
'node_modules': {
|
||||
'@angular': {
|
||||
'core.d.ts': dummyModule,
|
||||
'core.metadata.json':
|
||||
`{"__symbolic":"module", "version": 1, "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;',
|
||||
'empty.metadata.json': '[]',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function clone(entry: Entry): Entry {
|
||||
if (typeof entry === 'string') {
|
||||
return entry;
|
||||
} else {
|
||||
const result: Directory = {};
|
||||
for (const name in entry) {
|
||||
result[name] = clone(entry[name]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -25,11 +25,16 @@ export * from './src/template_parser/template_ast';
|
||||
export {TEMPLATE_TRANSFORMS} from './src/template_parser/template_parser';
|
||||
export {CompilerConfig, RenderTypes} from './src/config';
|
||||
export * from './src/compile_metadata';
|
||||
export * from './src/offline_compiler';
|
||||
export {RuntimeCompiler} from './src/runtime_compiler';
|
||||
export * from './src/aot/compiler_factory';
|
||||
export * from './src/aot/compiler';
|
||||
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 {JitCompiler} from './src/jit/compiler';
|
||||
export * from './src/jit/compiler_factory';
|
||||
export * from './src/url_resolver';
|
||||
export * from './src/resource_loader';
|
||||
export * from './src/compiler';
|
||||
export {DirectiveResolver} from './src/directive_resolver';
|
||||
export {PipeResolver} from './src/pipe_resolver';
|
||||
export {NgModuleResolver} from './src/ng_module_resolver';
|
||||
|
@ -41,7 +41,9 @@ const _ANIMATION_TIME_VAR = o.variable('totalTime');
|
||||
const _ANIMATION_START_STATE_STYLES_VAR = o.variable('startStateStyles');
|
||||
const _ANIMATION_END_STATE_STYLES_VAR = o.variable('endStateStyles');
|
||||
const _ANIMATION_COLLECTED_STYLES = o.variable('collectedStyles');
|
||||
const EMPTY_MAP = o.literalMap([]);
|
||||
const _PREVIOUS_ANIMATION_PLAYERS = o.variable('previousPlayers');
|
||||
const _EMPTY_MAP = o.literalMap([]);
|
||||
const _EMPTY_ARRAY = o.literalArr([]);
|
||||
|
||||
class _AnimationBuilder implements AnimationAstVisitor {
|
||||
private _fnVarName: string;
|
||||
@ -110,10 +112,15 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
_callAnimateMethod(
|
||||
ast: AnimationStepAst, startingStylesExpr: any, keyframesExpr: any,
|
||||
context: _AnimationBuilderContext) {
|
||||
let previousStylesValue: o.Expression = _EMPTY_ARRAY;
|
||||
if (context.isExpectingFirstAnimateStep) {
|
||||
previousStylesValue = _PREVIOUS_ANIMATION_PLAYERS;
|
||||
context.isExpectingFirstAnimateStep = false;
|
||||
}
|
||||
context.totalTransitionTime += ast.duration + ast.delay;
|
||||
return _ANIMATION_FACTORY_RENDERER_VAR.callMethod('animate', [
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR, startingStylesExpr, keyframesExpr, o.literal(ast.duration),
|
||||
o.literal(ast.delay), o.literal(ast.easing)
|
||||
o.literal(ast.delay), o.literal(ast.easing), previousStylesValue
|
||||
]);
|
||||
}
|
||||
|
||||
@ -150,6 +157,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
|
||||
context.totalTransitionTime = 0;
|
||||
context.isExpectingFirstStyleStep = true;
|
||||
context.isExpectingFirstAnimateStep = true;
|
||||
|
||||
const stateChangePreconditions: o.Expression[] = [];
|
||||
|
||||
@ -187,17 +195,16 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
context.stateMap.registerState(DEFAULT_STATE, {});
|
||||
|
||||
const statements: o.Statement[] = [];
|
||||
statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT
|
||||
.callMethod(
|
||||
'cancelActiveAnimation',
|
||||
statements.push(_PREVIOUS_ANIMATION_PLAYERS
|
||||
.set(_ANIMATION_FACTORY_VIEW_CONTEXT.callMethod(
|
||||
'getAnimationPlayers',
|
||||
[
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
|
||||
_ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE))
|
||||
])
|
||||
.toStmt());
|
||||
]))
|
||||
.toDeclStmt());
|
||||
|
||||
|
||||
statements.push(_ANIMATION_COLLECTED_STYLES.set(EMPTY_MAP).toDeclStmt());
|
||||
statements.push(_ANIMATION_COLLECTED_STYLES.set(_EMPTY_MAP).toDeclStmt());
|
||||
statements.push(_ANIMATION_PLAYER_VAR.set(o.NULL_EXPR).toDeclStmt());
|
||||
statements.push(_ANIMATION_TIME_VAR.set(o.literal(0)).toDeclStmt());
|
||||
|
||||
@ -223,17 +230,6 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
|
||||
const RENDER_STYLES_FN = o.importExpr(resolveIdentifier(Identifiers.renderStyles));
|
||||
|
||||
// before we start any animation we want to clear out the starting
|
||||
// styles from the element's style property (since they were placed
|
||||
// there at the end of the last animation
|
||||
statements.push(RENDER_STYLES_FN
|
||||
.callFn([
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
|
||||
o.importExpr(resolveIdentifier(Identifiers.clearStyles))
|
||||
.callFn([_ANIMATION_START_STATE_STYLES_VAR])
|
||||
])
|
||||
.toStmt());
|
||||
|
||||
ast.stateTransitions.forEach(transAst => statements.push(transAst.visit(this, context)));
|
||||
|
||||
// this check ensures that the animation factory always returns a player
|
||||
@ -269,6 +265,22 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
])])
|
||||
.toStmt());
|
||||
|
||||
statements.push(o.importExpr(resolveIdentifier(Identifiers.AnimationSequencePlayer))
|
||||
.instantiate([_PREVIOUS_ANIMATION_PLAYERS])
|
||||
.callMethod('destroy', [])
|
||||
.toStmt());
|
||||
|
||||
// before we start any animation we want to clear out the starting
|
||||
// styles from the element's style property (since they were placed
|
||||
// there at the end of the last animation
|
||||
statements.push(RENDER_STYLES_FN
|
||||
.callFn([
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
|
||||
o.importExpr(resolveIdentifier(Identifiers.clearStyles))
|
||||
.callFn([_ANIMATION_START_STATE_STYLES_VAR])
|
||||
])
|
||||
.toStmt());
|
||||
|
||||
statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT
|
||||
.callMethod(
|
||||
'queueAnimation',
|
||||
@ -304,7 +316,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
const lookupMap: any[] = [];
|
||||
Object.keys(context.stateMap.states).forEach(stateName => {
|
||||
const value = context.stateMap.states[stateName];
|
||||
let variableValue = EMPTY_MAP;
|
||||
let variableValue = _EMPTY_MAP;
|
||||
if (isPresent(value)) {
|
||||
const styleMap: any[] = [];
|
||||
Object.keys(value).forEach(key => { styleMap.push([key, o.literal(value[key])]); });
|
||||
@ -324,6 +336,7 @@ class _AnimationBuilderContext {
|
||||
stateMap = new _AnimationBuilderStateMap();
|
||||
endStateAnimateStep: AnimationStepAst = null;
|
||||
isExpectingFirstStyleStep = false;
|
||||
isExpectingFirstAnimateStep = false;
|
||||
totalTransitionTime = 0;
|
||||
}
|
||||
|
||||
|
@ -8,84 +8,30 @@
|
||||
|
||||
import {SchemaMetadata} from '@angular/core';
|
||||
|
||||
import {AnimationCompiler} from './animation/animation_compiler';
|
||||
import {AnimationParser} from './animation/animation_parser';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata';
|
||||
import {DirectiveNormalizer} from './directive_normalizer';
|
||||
import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from './directive_wrapper_compiler';
|
||||
import {ListWrapper} from './facade/collection';
|
||||
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers';
|
||||
import {CompileMetadataResolver} from './metadata_resolver';
|
||||
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 {TemplateParser} from './template_parser/template_parser';
|
||||
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from './view_compiler/view_compiler';
|
||||
import {AnimationCompiler} from '../animation/animation_compiler';
|
||||
import {AnimationParser} from '../animation/animation_parser';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, createHostComponentMeta} from '../compile_metadata';
|
||||
import {DirectiveNormalizer} from '../directive_normalizer';
|
||||
import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {Identifiers, resolveIdentifier, resolveIdentifierToken} from '../identifiers';
|
||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||
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 {TemplateParser} from '../template_parser/template_parser';
|
||||
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
|
||||
|
||||
import {AotCompilerOptions} from './compiler_options';
|
||||
import {StaticReflector} from './static_reflector';
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
|
||||
export class SourceModule {
|
||||
constructor(public fileUrl: string, public moduleUrl: string, public source: string) {}
|
||||
}
|
||||
|
||||
// Returns all the source files and a mapping from modules to directives
|
||||
export function analyzeNgModules(
|
||||
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
|
||||
metadataResolver: CompileMetadataResolver): Promise<{
|
||||
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>
|
||||
}> {
|
||||
return _loadNgModules(programStaticSymbols, options, metadataResolver).then(_analyzeNgModules);
|
||||
}
|
||||
|
||||
function _analyzeNgModules(ngModuleMetas: CompileNgModuleMetadata[]) {
|
||||
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 filePaths = new Set<string>();
|
||||
|
||||
// Looping over all modules to construct:
|
||||
// - a map from file to modules `ngModulesByFile`,
|
||||
// - a map from file to directives `ngDirectivesByFile`,
|
||||
// - a map from directive/pipe to module `ngModuleByPipeOrDirective`.
|
||||
ngModuleMetas.forEach((ngModuleMeta) => {
|
||||
const srcFileUrl = ngModuleMeta.type.reference.filePath;
|
||||
filePaths.add(srcFileUrl);
|
||||
ngModulesByFile.set(
|
||||
srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference));
|
||||
|
||||
ngModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
|
||||
const fileUrl = dirIdentifier.reference.filePath;
|
||||
filePaths.add(fileUrl);
|
||||
ngDirectivesByFile.set(
|
||||
fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirIdentifier.reference));
|
||||
ngModuleByPipeOrDirective.set(dirIdentifier.reference, ngModuleMeta);
|
||||
});
|
||||
ngModuleMeta.declaredPipes.forEach((pipeIdentifier) => {
|
||||
const fileUrl = pipeIdentifier.reference.filePath;
|
||||
filePaths.add(fileUrl);
|
||||
ngModuleByPipeOrDirective.set(pipeIdentifier.reference, ngModuleMeta);
|
||||
});
|
||||
});
|
||||
|
||||
const files: {srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}[] = [];
|
||||
|
||||
filePaths.forEach((srcUrl) => {
|
||||
const directives = ngDirectivesByFile.get(srcUrl) || [];
|
||||
const ngModules = ngModulesByFile.get(srcUrl) || [];
|
||||
files.push({srcUrl, directives, ngModules});
|
||||
});
|
||||
|
||||
return {
|
||||
// map directive/pipe to module
|
||||
ngModuleByPipeOrDirective,
|
||||
// list modules and directives for every source file
|
||||
files,
|
||||
};
|
||||
}
|
||||
|
||||
export class OfflineCompiler {
|
||||
export class AotCompiler {
|
||||
private _animationCompiler = new AnimationCompiler();
|
||||
|
||||
constructor(
|
||||
@ -94,19 +40,21 @@ export class OfflineCompiler {
|
||||
private _dirWrapperCompiler: DirectiveWrapperCompiler,
|
||||
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
|
||||
private _localeId: string, private _translationFormat: string,
|
||||
private _animationParser: AnimationParser) {}
|
||||
private _animationParser: AnimationParser, private _staticReflector: StaticReflector,
|
||||
private _options: AotCompilerOptions) {}
|
||||
|
||||
clearCache() { this._metadataResolver.clearCache(); }
|
||||
|
||||
compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}):
|
||||
Promise<SourceModule[]> {
|
||||
return analyzeNgModules(staticSymbols, options, this._metadataResolver)
|
||||
.then(({ngModuleByPipeOrDirective, files}) => {
|
||||
const sourceModules = files.map(
|
||||
file => this._compileSrcFile(
|
||||
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules));
|
||||
return ListWrapper.flatten(sourceModules);
|
||||
});
|
||||
compileAll(rootFiles: string[]): Promise<SourceModule[]> {
|
||||
const programSymbols = extractProgramSymbols(this._staticReflector, rootFiles, this._options);
|
||||
const {ngModuleByPipeOrDirective, files, ngModules} =
|
||||
analyzeAndValidateNgModules(programSymbols, this._options, this._metadataResolver);
|
||||
return loadNgModuleDirectives(ngModules).then(() => {
|
||||
const sourceModules = files.map(
|
||||
file => this._compileSrcFile(
|
||||
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules));
|
||||
return ListWrapper.flatten(sourceModules);
|
||||
});
|
||||
}
|
||||
|
||||
private _compileSrcFile(
|
||||
@ -325,31 +273,147 @@ function _splitTypescriptSuffix(path: string): string[] {
|
||||
return [path, ''];
|
||||
}
|
||||
|
||||
export interface NgAnalyzedModules {
|
||||
ngModules: CompileNgModuleMetadata[];
|
||||
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>;
|
||||
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>;
|
||||
symbolsMissingModule?: StaticSymbol[];
|
||||
}
|
||||
|
||||
// Returns all the source files and a mapping from modules to directives
|
||||
export function analyzeNgModules(
|
||||
programStaticSymbols: StaticSymbol[],
|
||||
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
|
||||
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
|
||||
const {ngModules, symbolsMissingModule} =
|
||||
_createNgModules(programStaticSymbols, options, metadataResolver);
|
||||
return _analyzeNgModules(ngModules, symbolsMissingModule);
|
||||
}
|
||||
|
||||
export function analyzeAndValidateNgModules(
|
||||
programStaticSymbols: StaticSymbol[],
|
||||
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
|
||||
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
|
||||
const result = analyzeNgModules(programStaticSymbols, options, metadataResolver);
|
||||
if (result.symbolsMissingModule && result.symbolsMissingModule.length) {
|
||||
const messages = result.symbolsMissingModule.map(
|
||||
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
|
||||
throw new Error(messages.join('\n'));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Wait for the directives in the given modules have been loaded
|
||||
export function loadNgModuleDirectives(ngModules: CompileNgModuleMetadata[]) {
|
||||
return Promise
|
||||
.all(ListWrapper.flatten(ngModules.map(
|
||||
(ngModule) => ngModule.transitiveModule.directiveLoaders.map(loader => loader()))))
|
||||
.then(() => {});
|
||||
}
|
||||
|
||||
function _analyzeNgModules(
|
||||
ngModuleMetas: CompileNgModuleMetadata[],
|
||||
symbolsMissingModule: StaticSymbol[]): 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 filePaths = new Set<string>();
|
||||
|
||||
// Looping over all modules to construct:
|
||||
// - a map from file to modules `ngModulesByFile`,
|
||||
// - a map from file to directives `ngDirectivesByFile`,
|
||||
// - a map from directive/pipe to module `ngModuleByPipeOrDirective`.
|
||||
ngModuleMetas.forEach((ngModuleMeta) => {
|
||||
const srcFileUrl = ngModuleMeta.type.reference.filePath;
|
||||
filePaths.add(srcFileUrl);
|
||||
ngModulesByFile.set(
|
||||
srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference));
|
||||
|
||||
ngModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
|
||||
const fileUrl = dirIdentifier.reference.filePath;
|
||||
filePaths.add(fileUrl);
|
||||
ngDirectivesByFile.set(
|
||||
fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirIdentifier.reference));
|
||||
ngModuleByPipeOrDirective.set(dirIdentifier.reference, ngModuleMeta);
|
||||
});
|
||||
ngModuleMeta.declaredPipes.forEach((pipeIdentifier) => {
|
||||
const fileUrl = pipeIdentifier.reference.filePath;
|
||||
filePaths.add(fileUrl);
|
||||
ngModuleByPipeOrDirective.set(pipeIdentifier.reference, ngModuleMeta);
|
||||
});
|
||||
});
|
||||
|
||||
const files: {srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}[] = [];
|
||||
|
||||
filePaths.forEach((srcUrl) => {
|
||||
const directives = ngDirectivesByFile.get(srcUrl) || [];
|
||||
const ngModules = ngModulesByFile.get(srcUrl) || [];
|
||||
files.push({srcUrl, directives, ngModules});
|
||||
});
|
||||
|
||||
return {
|
||||
// map directive/pipe to module
|
||||
ngModuleByPipeOrDirective,
|
||||
// list modules and directives for every source file
|
||||
files,
|
||||
ngModules: ngModuleMetas, symbolsMissingModule
|
||||
};
|
||||
}
|
||||
|
||||
export function extractProgramSymbols(
|
||||
staticReflector: StaticReflector, files: string[],
|
||||
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}): StaticSymbol[] {
|
||||
const staticSymbols: StaticSymbol[] = [];
|
||||
files.filter(fileName => _filterFileByPatterns(fileName, options)).forEach(sourceFile => {
|
||||
const moduleMetadata = staticReflector.getModuleMetadata(sourceFile);
|
||||
if (!moduleMetadata) {
|
||||
console.log(`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;
|
||||
}
|
||||
staticSymbols.push(staticReflector.getStaticSymbol(sourceFile, symbol));
|
||||
}
|
||||
});
|
||||
|
||||
return staticSymbols;
|
||||
}
|
||||
|
||||
// Load the NgModules and check
|
||||
// that all directives / pipes that are present in the program
|
||||
// are also declared by a module.
|
||||
function _loadNgModules(
|
||||
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
|
||||
metadataResolver: CompileMetadataResolver): Promise<CompileNgModuleMetadata[]> {
|
||||
function _createNgModules(
|
||||
programStaticSymbols: StaticSymbol[],
|
||||
options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp},
|
||||
metadataResolver: CompileMetadataResolver):
|
||||
{ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} {
|
||||
const ngModules = new Map<any, CompileNgModuleMetadata>();
|
||||
const programPipesAndDirectives: StaticSymbol[] = [];
|
||||
const ngModulePipesAndDirective = new Set<StaticSymbol>();
|
||||
const loadingPromises: Promise<any>[] = [];
|
||||
|
||||
const addNgModule = (staticSymbol: any) => {
|
||||
if (ngModules.has(staticSymbol)) {
|
||||
if (ngModules.has(staticSymbol) || !_filterFileByPatterns(staticSymbol.filePath, options)) {
|
||||
return false;
|
||||
}
|
||||
const {ngModule, loading} = metadataResolver.loadNgModuleMetadata(staticSymbol, false, false);
|
||||
const ngModule = metadataResolver.getUnloadedNgModuleMetadata(staticSymbol, false, false);
|
||||
if (ngModule) {
|
||||
ngModules.set(ngModule.type.reference, ngModule);
|
||||
loadingPromises.push(loading);
|
||||
ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference));
|
||||
ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference));
|
||||
if (options.transitiveModules) {
|
||||
// For every input modules add the list of transitively included modules
|
||||
ngModule.transitiveModule.modules.forEach(modMeta => addNgModule(modMeta.type.reference));
|
||||
}
|
||||
// For every input module add the list of transitively included modules
|
||||
ngModule.transitiveModule.modules.forEach(modMeta => addNgModule(modMeta.type.reference));
|
||||
}
|
||||
return !!ngModule;
|
||||
};
|
||||
@ -364,11 +428,17 @@ function _loadNgModules(
|
||||
const symbolsMissingModule =
|
||||
programPipesAndDirectives.filter(s => !ngModulePipesAndDirective.has(s));
|
||||
|
||||
if (symbolsMissingModule.length) {
|
||||
const messages = symbolsMissingModule.map(
|
||||
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
|
||||
throw new Error(messages.join('\n'));
|
||||
}
|
||||
|
||||
return Promise.all(loadingPromises).then(() => Array.from(ngModules.values()));
|
||||
return {ngModules: Array.from(ngModules.values()), symbolsMissingModule};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
75
modules/@angular/compiler/src/aot/compiler_factory.ts
Normal file
75
modules/@angular/compiler/src/aot/compiler_factory.ts
Normal file
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @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 {ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {AnimationParser} from '../animation/animation_parser';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {DirectiveNormalizer} from '../directive_normalizer';
|
||||
import {DirectiveResolver} from '../directive_resolver';
|
||||
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
|
||||
import {Lexer} from '../expression_parser/lexer';
|
||||
import {Parser} from '../expression_parser/parser';
|
||||
import {I18NHtmlParser} from '../i18n/i18n_html_parser';
|
||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||
import {HtmlParser} from '../ml_parser/html_parser';
|
||||
import {NgModuleCompiler} from '../ng_module_compiler';
|
||||
import {NgModuleResolver} from '../ng_module_resolver';
|
||||
import {TypeScriptEmitter} from '../output/ts_emitter';
|
||||
import {PipeResolver} from '../pipe_resolver';
|
||||
import {Console} from '../private_import_core';
|
||||
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
|
||||
import {StyleCompiler} from '../style_compiler';
|
||||
import {TemplateParser} from '../template_parser/template_parser';
|
||||
import {createOfflineCompileUrlResolver} from '../url_resolver';
|
||||
import {ViewCompiler} from '../view_compiler/view_compiler';
|
||||
|
||||
import {AotCompiler} from './compiler';
|
||||
import {AotCompilerHost} from './compiler_host';
|
||||
import {AotCompilerOptions} from './compiler_options';
|
||||
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
||||
import {StaticReflector} from './static_reflector';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new AotCompiler based on options and a host.
|
||||
*/
|
||||
export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCompilerOptions):
|
||||
{compiler: AotCompiler, reflector: StaticReflector} {
|
||||
let translations: string = options.translations || '';
|
||||
|
||||
const urlResolver = createOfflineCompileUrlResolver();
|
||||
const staticReflector = new StaticReflector(compilerHost);
|
||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||
const htmlParser = new I18NHtmlParser(new HtmlParser(), translations, options.i18nFormat);
|
||||
const config = new CompilerConfig({
|
||||
genDebugInfo: options.debug === true,
|
||||
defaultEncapsulation: ViewEncapsulation.Emulated,
|
||||
logBindingUpdate: false,
|
||||
useJit: false
|
||||
});
|
||||
const normalizer = new DirectiveNormalizer(
|
||||
{get: (url: string) => compilerHost.loadResource(url)}, urlResolver, htmlParser, config);
|
||||
const expressionParser = new Parser(new Lexer());
|
||||
const elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||
const console = new Console();
|
||||
const tmplParser =
|
||||
new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []);
|
||||
const resolver = new CompileMetadataResolver(
|
||||
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
|
||||
new PipeResolver(staticReflector), elementSchemaRegistry, normalizer, staticReflector);
|
||||
// TODO(vicb): do not pass options.i18nFormat here
|
||||
const compiler = new AotCompiler(
|
||||
resolver, tmplParser, new StyleCompiler(urlResolver),
|
||||
new ViewCompiler(config, elementSchemaRegistry),
|
||||
new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console),
|
||||
new NgModuleCompiler(), new TypeScriptEmitter(compilerHost), options.locale,
|
||||
options.i18nFormat, new AnimationParser(elementSchemaRegistry), staticReflector, options);
|
||||
return {compiler, reflector: staticReflector};
|
||||
}
|
24
modules/@angular/compiler/src/aot/compiler_host.ts
Normal file
24
modules/@angular/compiler/src/aot/compiler_host.ts
Normal file
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @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 {ImportResolver} from '../output/path_util';
|
||||
|
||||
import {StaticReflectorHost} from './static_reflector';
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
|
||||
|
||||
/**
|
||||
* The host of the AotCompiler disconnects the implementation from TypeScript / other language
|
||||
* services and from underlying file systems.
|
||||
*/
|
||||
export interface AotCompilerHost extends StaticReflectorHost, ImportResolver {
|
||||
/**
|
||||
* Loads a resource (e.g. html / css)
|
||||
*/
|
||||
loadResource(path: string): Promise<string>;
|
||||
}
|
16
modules/@angular/compiler/src/aot/compiler_options.ts
Normal file
16
modules/@angular/compiler/src/aot/compiler_options.ts
Normal 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
|
||||
*/
|
||||
|
||||
export interface AotCompilerOptions {
|
||||
debug?: boolean;
|
||||
locale?: string;
|
||||
i18nFormat?: string;
|
||||
translations?: string;
|
||||
includeFilePattern?: RegExp;
|
||||
excludeFilePattern?: RegExp;
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {GetterFn, MethodFn, ReflectionCapabilities, SetterFn, reflector} from './private_import_core';
|
||||
import {GetterFn, MethodFn, ReflectionCapabilities, SetterFn, reflector} from '../private_import_core';
|
||||
import {StaticReflector} from './static_reflector';
|
||||
|
||||
export class StaticAndDynamicReflectionCapabilities {
|
@ -7,54 +7,60 @@
|
||||
*/
|
||||
|
||||
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';
|
||||
|
||||
import {ReflectorReader} from './private_import_core';
|
||||
|
||||
const SUPPORTED_SCHEMA_VERSION = 1;
|
||||
const SUPPORTED_SCHEMA_VERSION = 2;
|
||||
const ANGULAR_IMPORT_LOCATIONS = {
|
||||
coreDecorators: '@angular/core/src/metadata',
|
||||
diDecorators: '@angular/core/src/di/metadata',
|
||||
diMetadata: '@angular/core/src/di/metadata',
|
||||
diOpaqueToken: '@angular/core/src/di/opaque_token',
|
||||
animationMetadata: '@angular/core/src/animation/metadata',
|
||||
provider: '@angular/core/src/di/provider'
|
||||
};
|
||||
|
||||
/**
|
||||
* The host of the static resolver is expected to be able to provide module metadata in the form of
|
||||
* ModuleMetadata. 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.
|
||||
* 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}|{[key: string]: any}[];
|
||||
getMetadataFor(modulePath: string): {[key: string]: any}[];
|
||||
|
||||
/**
|
||||
* Resolve a symbol from an import statement form, to the file where it is declared.
|
||||
* @param module the location imported from
|
||||
* @param containingFile for relative imports, the path of the file containing the import
|
||||
* 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'`.
|
||||
*/
|
||||
findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol;
|
||||
|
||||
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol;
|
||||
|
||||
angularImportLocations(): {
|
||||
coreDecorators: string,
|
||||
diDecorators: string,
|
||||
diMetadata: string,
|
||||
diOpaqueToken: string,
|
||||
animationMetadata: string,
|
||||
provider: string
|
||||
};
|
||||
|
||||
getCanonicalFileName(fileName: string): string;
|
||||
moduleNameToFileName(moduleName: string, containingFile: string): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A token representing the a reference to a static type.
|
||||
*
|
||||
* This token is unique for a filePath and name and can be used as a hash table key.
|
||||
* A cache of static symbol used by the StaticReflector to return the same symbol for the
|
||||
* same symbol values.
|
||||
*/
|
||||
export class StaticSymbol {
|
||||
constructor(public filePath: string, public name: string, public members?: string[]) {}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -62,6 +68,7 @@ export class StaticSymbol {
|
||||
* 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[]>();
|
||||
@ -69,20 +76,24 @@ export class StaticReflector implements ReflectorReader {
|
||||
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
|
||||
private opaqueToken: StaticSymbol;
|
||||
|
||||
constructor(private host: StaticReflectorHost) { this.initializeConversionMap(); }
|
||||
constructor(
|
||||
private host: StaticReflectorHost,
|
||||
private staticSymbolCache: StaticSymbolCache = new StaticSymbolCache()) {
|
||||
this.initializeConversionMap();
|
||||
}
|
||||
|
||||
importUri(typeOrFunc: StaticSymbol): string {
|
||||
const staticSymbol = this.host.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, '');
|
||||
const staticSymbol = this.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, '');
|
||||
return staticSymbol ? staticSymbol.filePath : null;
|
||||
}
|
||||
|
||||
resolveIdentifier(name: string, moduleUrl: string, runtime: any): any {
|
||||
return this.host.findDeclaration(moduleUrl, name, '');
|
||||
return this.findDeclaration(moduleUrl, name, '');
|
||||
}
|
||||
|
||||
resolveEnum(enumIdentifier: any, name: string): any {
|
||||
const staticSymbol: StaticSymbol = enumIdentifier;
|
||||
return this.host.getStaticSymbol(staticSymbol.filePath, staticSymbol.name, [name]);
|
||||
return this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name, [name]);
|
||||
}
|
||||
|
||||
public annotations(type: StaticSymbol): any[] {
|
||||
@ -132,7 +143,6 @@ export class StaticReflector implements ReflectorReader {
|
||||
const ctor = (<any[]>ctorData).find(a => a['__symbolic'] == 'constructor');
|
||||
const parameterTypes = <any[]>this.simplify(type, ctor['parameters'] || []);
|
||||
const parameterDecorators = <any[]>this.simplify(type, ctor['parameterDecorators'] || []);
|
||||
|
||||
parameters = [];
|
||||
parameterTypes.forEach((paramType, index) => {
|
||||
const nestedResult: any[] = [];
|
||||
@ -180,59 +190,145 @@ export class StaticReflector implements ReflectorReader {
|
||||
|
||||
private initializeConversionMap(): void {
|
||||
const {coreDecorators, diDecorators, diMetadata, diOpaqueToken, animationMetadata, provider} =
|
||||
this.host.angularImportLocations();
|
||||
this.opaqueToken = this.host.findDeclaration(diOpaqueToken, 'OpaqueToken');
|
||||
ANGULAR_IMPORT_LOCATIONS;
|
||||
this.opaqueToken = this.findDeclaration(diOpaqueToken, 'OpaqueToken');
|
||||
|
||||
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Host'), Host);
|
||||
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Host'), Host);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this.host.findDeclaration(diDecorators, 'Injectable'), Injectable);
|
||||
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Self'), Self);
|
||||
this.findDeclaration(diDecorators, 'Injectable'), Injectable);
|
||||
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Self'), Self);
|
||||
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf);
|
||||
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Inject'), Inject);
|
||||
this.registerDecoratorOrConstructor(this.findDeclaration(diDecorators, 'Optional'), Optional);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this.host.findDeclaration(diDecorators, 'SkipSelf'), SkipSelf);
|
||||
this.registerDecoratorOrConstructor(this.host.findDeclaration(diDecorators, 'Inject'), Inject);
|
||||
this.findDeclaration(coreDecorators, 'Attribute'), Attribute);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this.host.findDeclaration(diDecorators, 'Optional'), Optional);
|
||||
this.findDeclaration(coreDecorators, 'ContentChild'), ContentChild);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this.host.findDeclaration(coreDecorators, 'Attribute'), Attribute);
|
||||
this.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this.host.findDeclaration(coreDecorators, 'ContentChild'), ContentChild);
|
||||
this.findDeclaration(coreDecorators, 'ViewChild'), ViewChild);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this.host.findDeclaration(coreDecorators, 'ContentChildren'), ContentChildren);
|
||||
this.findDeclaration(coreDecorators, 'ViewChildren'), ViewChildren);
|
||||
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Input'), Input);
|
||||
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Output'), Output);
|
||||
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'Pipe'), Pipe);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this.host.findDeclaration(coreDecorators, 'ViewChild'), ViewChild);
|
||||
this.findDeclaration(coreDecorators, 'HostBinding'), HostBinding);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this.host.findDeclaration(coreDecorators, 'ViewChildren'), ViewChildren);
|
||||
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Input'), Input);
|
||||
this.findDeclaration(coreDecorators, 'HostListener'), HostListener);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this.host.findDeclaration(coreDecorators, 'Output'), Output);
|
||||
this.registerDecoratorOrConstructor(this.host.findDeclaration(coreDecorators, 'Pipe'), Pipe);
|
||||
this.findDeclaration(coreDecorators, 'Directive'), Directive);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this.host.findDeclaration(coreDecorators, 'HostBinding'), HostBinding);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this.host.findDeclaration(coreDecorators, 'HostListener'), HostListener);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this.host.findDeclaration(coreDecorators, 'Directive'), Directive);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this.host.findDeclaration(coreDecorators, 'Component'), Component);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this.host.findDeclaration(coreDecorators, 'NgModule'), NgModule);
|
||||
this.findDeclaration(coreDecorators, 'Component'), Component);
|
||||
this.registerDecoratorOrConstructor(this.findDeclaration(coreDecorators, 'NgModule'), NgModule);
|
||||
|
||||
// Note: Some metadata classes can be used directly with Provider.deps.
|
||||
this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'Host'), Host);
|
||||
this.registerDecoratorOrConstructor(this.host.findDeclaration(diMetadata, 'Self'), Self);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this.host.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf);
|
||||
this.registerDecoratorOrConstructor(
|
||||
this.host.findDeclaration(diMetadata, 'Optional'), Optional);
|
||||
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Host'), Host);
|
||||
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Self'), Self);
|
||||
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'SkipSelf'), SkipSelf);
|
||||
this.registerDecoratorOrConstructor(this.findDeclaration(diMetadata, 'Optional'), Optional);
|
||||
|
||||
this.registerFunction(this.host.findDeclaration(animationMetadata, 'trigger'), trigger);
|
||||
this.registerFunction(this.host.findDeclaration(animationMetadata, 'state'), state);
|
||||
this.registerFunction(this.host.findDeclaration(animationMetadata, 'transition'), transition);
|
||||
this.registerFunction(this.host.findDeclaration(animationMetadata, 'style'), style);
|
||||
this.registerFunction(this.host.findDeclaration(animationMetadata, 'animate'), animate);
|
||||
this.registerFunction(this.host.findDeclaration(animationMetadata, 'keyframes'), keyframes);
|
||||
this.registerFunction(this.host.findDeclaration(animationMetadata, 'sequence'), sequence);
|
||||
this.registerFunction(this.host.findDeclaration(animationMetadata, 'group'), group);
|
||||
this.registerFunction(this.findDeclaration(animationMetadata, 'trigger'), trigger);
|
||||
this.registerFunction(this.findDeclaration(animationMetadata, 'state'), state);
|
||||
this.registerFunction(this.findDeclaration(animationMetadata, 'transition'), transition);
|
||||
this.registerFunction(this.findDeclaration(animationMetadata, 'style'), style);
|
||||
this.registerFunction(this.findDeclaration(animationMetadata, 'animate'), animate);
|
||||
this.registerFunction(this.findDeclaration(animationMetadata, 'keyframes'), keyframes);
|
||||
this.registerFunction(this.findDeclaration(animationMetadata, 'sequence'), sequence);
|
||||
this.registerFunction(this.findDeclaration(animationMetadata, 'group'), group);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
private resolveExportedSymbol(filePath: string, symbolName: string): StaticSymbol {
|
||||
const resolveModule = (moduleName: string): string => {
|
||||
const resolvedModulePath = this.host.moduleNameToFileName(moduleName, filePath);
|
||||
if (!resolvedModulePath) {
|
||||
throw new Error(`Could not resolve module '${moduleName}' relative to file ${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;
|
||||
}
|
||||
staticSymbol = this.resolveExportedSymbol(resolveModule(moduleExport.from), symName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!staticSymbol) {
|
||||
// Try to find the symbol via export * directives.
|
||||
for (const moduleExport of metadata['exports']) {
|
||||
if (!moduleExport.export) {
|
||||
const resolvedModule = resolveModule(moduleExport.from);
|
||||
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 */
|
||||
@ -245,10 +341,10 @@ export class StaticReflector implements ReflectorReader {
|
||||
function resolveReference(context: StaticSymbol, expression: any): StaticSymbol {
|
||||
let staticSymbol: StaticSymbol;
|
||||
if (expression['module']) {
|
||||
staticSymbol = _this.host.findDeclaration(
|
||||
expression['module'], expression['name'], context.filePath);
|
||||
staticSymbol =
|
||||
_this.findDeclaration(expression['module'], expression['name'], context.filePath);
|
||||
} else {
|
||||
staticSymbol = _this.host.getStaticSymbol(context.filePath, expression['name']);
|
||||
staticSymbol = _this.getStaticSymbol(context.filePath, expression['name']);
|
||||
}
|
||||
return staticSymbol;
|
||||
}
|
||||
@ -457,8 +553,7 @@ export class StaticReflector implements ReflectorReader {
|
||||
const members = selectTarget.members ?
|
||||
(selectTarget.members as string[]).concat(member) :
|
||||
[member];
|
||||
return _this.host.getStaticSymbol(
|
||||
selectTarget.filePath, selectTarget.name, members);
|
||||
return _this.getStaticSymbol(selectTarget.filePath, selectTarget.name, members);
|
||||
}
|
||||
}
|
||||
const member = simplify(expression['member']);
|
||||
@ -493,10 +588,10 @@ export class StaticReflector implements ReflectorReader {
|
||||
// Determine if the function is a built-in conversion
|
||||
let target = expression['expression'];
|
||||
if (target['module']) {
|
||||
staticSymbol = _this.host.findDeclaration(
|
||||
target['module'], target['name'], context.filePath);
|
||||
staticSymbol =
|
||||
_this.findDeclaration(target['module'], target['name'], context.filePath);
|
||||
} else {
|
||||
staticSymbol = _this.host.getStaticSymbol(context.filePath, target['name']);
|
||||
staticSymbol = _this.getStaticSymbol(context.filePath, target['name']);
|
||||
}
|
||||
let converter = _this.conversionMap.get(staticSymbol);
|
||||
if (converter) {
|
||||
@ -552,10 +647,15 @@ export class StaticReflector implements ReflectorReader {
|
||||
public getModuleMetadata(module: string): {[key: string]: any} {
|
||||
let moduleMetadata = this.metadataCache.get(module);
|
||||
if (!moduleMetadata) {
|
||||
moduleMetadata = this.host.getMetadataFor(module);
|
||||
if (Array.isArray(moduleMetadata)) {
|
||||
moduleMetadata = moduleMetadata.find(md => md['version'] === SUPPORTED_SCHEMA_VERSION) ||
|
||||
moduleMetadata[0];
|
||||
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 =
|
||||
@ -601,6 +701,7 @@ function expandedMessage(error: any): string {
|
||||
if (error.context && error.context.name) {
|
||||
return `Reference to a local (non-exported) symbol '${error.context.name}'. Consider exporting the symbol`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return error.message;
|
||||
}
|
20
modules/@angular/compiler/src/aot/static_symbol.ts
Normal file
20
modules/@angular/compiler/src/aot/static_symbol.ts
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @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 isStaticSymbol(value: any): value is StaticSymbol {
|
||||
return typeof value === 'object' && value !== null && value['name'] && value['filePath'];
|
||||
}
|
||||
|
||||
/**
|
||||
* A token representing the a reference to a static type.
|
||||
*
|
||||
* This token is unique for a filePath and name and can be used as a hash table key.
|
||||
*/
|
||||
export class StaticSymbol {
|
||||
constructor(public filePath: string, public name: string, public members?: string[]) {}
|
||||
}
|
@ -589,7 +589,7 @@ export interface CompileNgModuleDirectiveSummary extends CompileSummary {
|
||||
exportedDirectives: CompileIdentifierMetadata[];
|
||||
exportedPipes: CompileIdentifierMetadata[];
|
||||
exportedModules: CompileNgModuleDirectiveSummary[];
|
||||
loadingPromises: Promise<any>[];
|
||||
directiveLoaders: (() => Promise<void>)[];
|
||||
}
|
||||
|
||||
export type CompileNgModuleSummary =
|
||||
@ -661,7 +661,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
|
||||
exportedModules: this.exportedModules,
|
||||
exportedDirectives: this.exportedDirectives,
|
||||
exportedPipes: this.exportedPipes,
|
||||
loadingPromises: this.transitiveModule.loadingPromises
|
||||
directiveLoaders: this.transitiveModule.directiveLoaders
|
||||
};
|
||||
}
|
||||
|
||||
@ -682,7 +682,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
|
||||
exportedDirectives: this.exportedDirectives,
|
||||
exportedPipes: this.exportedPipes,
|
||||
exportedModules: this.exportedModules,
|
||||
loadingPromises: this.transitiveModule.loadingPromises
|
||||
directiveLoaders: this.transitiveModule.directiveLoaders
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -695,7 +695,7 @@ export class TransitiveCompileNgModuleMetadata {
|
||||
public modules: CompileNgModuleInjectorSummary[], public providers: CompileProviderMetadata[],
|
||||
public entryComponents: CompileIdentifierMetadata[],
|
||||
public directives: CompileIdentifierMetadata[], public pipes: CompileIdentifierMetadata[],
|
||||
public loadingPromises: Promise<any>[]) {
|
||||
public directiveLoaders: (() => Promise<void>)[]) {
|
||||
directives.forEach(dir => this.directivesSet.add(dir.reference));
|
||||
pipes.forEach(pipe => this.pipesSet.add(pipe.reference));
|
||||
}
|
||||
@ -718,15 +718,6 @@ function _normalizeArray(obj: any[]): any[] {
|
||||
return obj || [];
|
||||
}
|
||||
|
||||
export function isStaticSymbol(value: any): value is StaticSymbol {
|
||||
return typeof value === 'object' && value !== null && value['name'] && value['filePath'];
|
||||
}
|
||||
|
||||
export interface StaticSymbol {
|
||||
name: string;
|
||||
filePath: string;
|
||||
}
|
||||
|
||||
export class ProviderMeta {
|
||||
token: any;
|
||||
useClass: Type<any>;
|
||||
|
@ -8,10 +8,16 @@
|
||||
|
||||
import * as i18n from './i18n_ast';
|
||||
|
||||
export function digestMessage(message: i18n.Message): string {
|
||||
export function digest(message: i18n.Message): string {
|
||||
return sha1(serializeNodes(message.nodes).join('') + `[${message.meaning}]`);
|
||||
}
|
||||
|
||||
export function decimalDigest(message: i18n.Message): string {
|
||||
const visitor = new _SerializerIgnoreIcuExpVisitor();
|
||||
const parts = message.nodes.map(a => a.visit(visitor, null));
|
||||
return computeMsgId(parts.join(''), message.meaning);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the i18n ast to something xml-like in order to generate an UID.
|
||||
*
|
||||
@ -39,7 +45,7 @@ class _SerializerVisitor implements i18n.Visitor {
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, context: any): any {
|
||||
return `<ph name="${ph.name}">${ph.value}</ph>`;
|
||||
return ph.value ? `<ph name="${ph.name}">${ph.value}</ph>` : `<ph name="${ph.name}"/>`;
|
||||
}
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any {
|
||||
@ -53,6 +59,21 @@ export function serializeNodes(nodes: i18n.Node[]): string[] {
|
||||
return nodes.map(a => a.visit(serializerVisitor, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the i18n ast to something xml-like in order to generate an UID.
|
||||
*
|
||||
* Ignore the ICU expressions so that message IDs stays identical if only the expression changes.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class _SerializerIgnoreIcuExpVisitor extends _SerializerVisitor {
|
||||
visitIcu(icu: i18n.Icu, context: any): any {
|
||||
let strCases = Object.keys(icu.cases).map((k: string) => `${k} {${icu.cases[k].visit(this)}}`);
|
||||
// Do not take the expression into account
|
||||
return `{${icu.type}, ${strCases.join(', ')}}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the SHA1 of the given string
|
||||
*
|
||||
@ -63,7 +84,7 @@ export function serializeNodes(nodes: i18n.Node[]): string[] {
|
||||
*/
|
||||
export function sha1(str: string): string {
|
||||
const utf8 = utf8Encode(str);
|
||||
const words32 = stringToWords32(utf8);
|
||||
const words32 = stringToWords32(utf8, Endian.Big);
|
||||
const len = utf8.length * 8;
|
||||
|
||||
const w = new Array(80);
|
||||
@ -90,15 +111,99 @@ export function sha1(str: string): string {
|
||||
[a, b, c, d, e] = [add32(a, h0), add32(b, h1), add32(c, h2), add32(d, h3), add32(e, h4)];
|
||||
}
|
||||
|
||||
const sha1 = words32ToString([a, b, c, d, e]);
|
||||
return byteStringToHexString(words32ToByteString([a, b, c, d, e]));
|
||||
}
|
||||
|
||||
let hex: string = '';
|
||||
for (let i = 0; i < sha1.length; i++) {
|
||||
const b = sha1.charCodeAt(i);
|
||||
hex += (b >>> 4 & 0x0f).toString(16) + (b & 0x0f).toString(16);
|
||||
function fk(index: number, b: number, c: number, d: number): [number, number] {
|
||||
if (index < 20) {
|
||||
return [(b & c) | (~b & d), 0x5a827999];
|
||||
}
|
||||
|
||||
return hex.toLowerCase();
|
||||
if (index < 40) {
|
||||
return [b ^ c ^ d, 0x6ed9eba1];
|
||||
}
|
||||
|
||||
if (index < 60) {
|
||||
return [(b & c) | (b & d) | (c & d), 0x8f1bbcdc];
|
||||
}
|
||||
|
||||
return [b ^ c ^ d, 0xca62c1d6];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the fingerprint of the given string
|
||||
*
|
||||
* The output is 64 bit number encoded as a decimal string
|
||||
*
|
||||
* based on:
|
||||
* https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/GoogleJsMessageIdGenerator.java
|
||||
*/
|
||||
export function fingerprint(str: string): [number, number] {
|
||||
const utf8 = utf8Encode(str);
|
||||
|
||||
let [hi, lo] = [hash32(utf8, 0), hash32(utf8, 102072)];
|
||||
|
||||
if (hi == 0 && (lo == 0 || lo == 1)) {
|
||||
hi = hi ^ 0x130f9bef;
|
||||
lo = lo ^ -0x6b5f56d8;
|
||||
}
|
||||
|
||||
return [hi, lo];
|
||||
}
|
||||
|
||||
export function computeMsgId(msg: string, meaning: string): string {
|
||||
let [hi, lo] = fingerprint(msg);
|
||||
|
||||
if (meaning) {
|
||||
const [him, lom] = fingerprint(meaning);
|
||||
[hi, lo] = add64(rol64([hi, lo], 1), [him, lom]);
|
||||
}
|
||||
|
||||
return byteStringToDecString(words32ToByteString([hi & 0x7fffffff, lo]));
|
||||
}
|
||||
|
||||
function hash32(str: string, c: number): number {
|
||||
let [a, b] = [0x9e3779b9, 0x9e3779b9];
|
||||
let i: number;
|
||||
|
||||
const len = str.length;
|
||||
|
||||
for (i = 0; i + 12 <= len; i += 12) {
|
||||
a = add32(a, wordAt(str, i, Endian.Little));
|
||||
b = add32(b, wordAt(str, i + 4, Endian.Little));
|
||||
c = add32(c, wordAt(str, i + 8, Endian.Little));
|
||||
[a, b, c] = mix([a, b, c]);
|
||||
}
|
||||
|
||||
a = add32(a, wordAt(str, i, Endian.Little));
|
||||
b = add32(b, wordAt(str, i + 4, Endian.Little));
|
||||
// the first byte of c is reserved for the length
|
||||
c = add32(c, len);
|
||||
c = add32(c, wordAt(str, i + 8, Endian.Little) << 8);
|
||||
|
||||
return mix([a, b, c])[2];
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
function mix([a, b, c]: [number, number, number]): [number, number, number] {
|
||||
a = sub32(a, b); a = sub32(a, c); a ^= c >>> 13;
|
||||
b = sub32(b, c); b = sub32(b, a); b ^= a << 8;
|
||||
c = sub32(c, a); c = sub32(c, b); c ^= b >>> 13;
|
||||
a = sub32(a, b); a = sub32(a, c); a ^= c >>> 12;
|
||||
b = sub32(b, c); b = sub32(b, a); b ^= a << 16;
|
||||
c = sub32(c, a); c = sub32(c, b); c ^= b >>> 5;
|
||||
a = sub32(a, b); a = sub32(a, c); a ^= c >>> 3;
|
||||
b = sub32(b, c); b = sub32(b, a); b ^= a << 10;
|
||||
c = sub32(c, a); c = sub32(c, b); c ^= b >>> 15;
|
||||
return [a, b, c];
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
// Utils
|
||||
|
||||
enum Endian {
|
||||
Little,
|
||||
Big,
|
||||
}
|
||||
|
||||
function utf8Encode(str: string): string {
|
||||
@ -131,10 +236,9 @@ function decodeSurrogatePairs(str: string, index: number): number {
|
||||
}
|
||||
|
||||
const high = str.charCodeAt(index);
|
||||
let low: number;
|
||||
|
||||
if (high >= 0xd800 && high <= 0xdfff && str.length > index + 1) {
|
||||
low = str.charCodeAt(index + 1);
|
||||
const low = byteAt(str, index + 1);
|
||||
if (low >= 0xdc00 && low <= 0xdfff) {
|
||||
return (high - 0xd800) * 0x400 + low - 0xdc00 + 0x10000;
|
||||
}
|
||||
@ -143,50 +247,126 @@ function decodeSurrogatePairs(str: string, index: number): number {
|
||||
return high;
|
||||
}
|
||||
|
||||
function stringToWords32(str: string): number[] {
|
||||
const words32 = Array(str.length >>> 2);
|
||||
function add32(a: number, b: number): number {
|
||||
return add32to64(a, b)[1];
|
||||
}
|
||||
|
||||
function add32to64(a: number, b: number): [number, number] {
|
||||
const low = (a & 0xffff) + (b & 0xffff);
|
||||
const high = (a >>> 16) + (b >>> 16) + (low >>> 16);
|
||||
return [high >>> 16, (high << 16) | (low & 0xffff)];
|
||||
}
|
||||
|
||||
function add64([ah, al]: [number, number], [bh, bl]: [number, number]): [number, number] {
|
||||
const [carry, l] = add32to64(al, bl);
|
||||
const h = add32(add32(ah, bh), carry);
|
||||
return [h, l];
|
||||
}
|
||||
|
||||
function sub32(a: number, b: number): number {
|
||||
const low = (a & 0xffff) - (b & 0xffff);
|
||||
const high = (a >> 16) - (b >> 16) + (low >> 16);
|
||||
return (high << 16) | (low & 0xffff);
|
||||
}
|
||||
|
||||
// Rotate a 32b number left `count` position
|
||||
function rol32(a: number, count: number): number {
|
||||
return (a << count) | (a >>> (32 - count));
|
||||
}
|
||||
|
||||
// Rotate a 64b number left `count` position
|
||||
function rol64([hi, lo]: [number, number], count: number): [number, number] {
|
||||
const h = (hi << count) | (lo >>> (32 - count));
|
||||
const l = (lo << count) | (hi >>> (32 - count));
|
||||
return [h, l];
|
||||
}
|
||||
|
||||
function stringToWords32(str: string, endian: Endian): number[] {
|
||||
const words32 = Array((str.length + 3) >>> 2);
|
||||
|
||||
for (let i = 0; i < words32.length; i++) {
|
||||
words32[i] = 0;
|
||||
}
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
words32[i >>> 2] |= (str.charCodeAt(i) & 0xff) << 8 * (3 - i & 0x3);
|
||||
words32[i] = wordAt(str, i * 4, endian);
|
||||
}
|
||||
|
||||
return words32;
|
||||
}
|
||||
|
||||
function words32ToString(words32: number[]): string {
|
||||
function byteAt(str: string, index: number): number {
|
||||
return index >= str.length ? 0 : str.charCodeAt(index) & 0xff;
|
||||
}
|
||||
|
||||
function wordAt(str: string, index: number, endian: Endian): number {
|
||||
let word = 0;
|
||||
if (endian === Endian.Big) {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
word += byteAt(str, index + i) << (24 - 8 * i);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
word += byteAt(str, index + i) << 8 * i;
|
||||
}
|
||||
}
|
||||
return word;
|
||||
}
|
||||
|
||||
function words32ToByteString(words32: number[]): string {
|
||||
return words32.reduce((str, word) => str + word32ToByteString(word), '');
|
||||
}
|
||||
|
||||
function word32ToByteString(word: number): string {
|
||||
let str = '';
|
||||
for (let i = 0; i < words32.length * 4; i++) {
|
||||
str += String.fromCharCode((words32[i >>> 2] >>> 8 * (3 - i & 0x3)) & 0xff);
|
||||
for (let i = 0; i < 4; i++) {
|
||||
str += String.fromCharCode((word >>> 8 * (3 - i)) & 0xff);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function fk(index: number, b: number, c: number, d: number): [number, number] {
|
||||
if (index < 20) {
|
||||
return [(b & c) | (~b & d), 0x5a827999];
|
||||
function byteStringToHexString(str: string): string {
|
||||
let hex: string = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const b = byteAt(str, i);
|
||||
hex += (b >>> 4).toString(16) + (b & 0x0f).toString(16);
|
||||
}
|
||||
|
||||
if (index < 40) {
|
||||
return [b ^ c ^ d, 0x6ed9eba1];
|
||||
}
|
||||
|
||||
if (index < 60) {
|
||||
return [(b & c) | (b & d) | (c & d), 0x8f1bbcdc];
|
||||
}
|
||||
|
||||
return [b ^ c ^ d, 0xca62c1d6];
|
||||
return hex.toLowerCase();
|
||||
}
|
||||
|
||||
function add32(a: number, b: number): number {
|
||||
const low = (a & 0xffff) + (b & 0xffff);
|
||||
const high = (a >> 16) + (b >> 16) + (low >> 16);
|
||||
return (high << 16) | (low & 0xffff);
|
||||
// based on http://www.danvk.org/hex2dec.html (JS can not handle more than 56b)
|
||||
function byteStringToDecString(str: string): string {
|
||||
let decimal = '';
|
||||
let toThePower = '1';
|
||||
|
||||
for (let i = str.length - 1; i >= 0; i--) {
|
||||
decimal = addBigInt(decimal, numberTimesBigInt(byteAt(str, i), toThePower));
|
||||
toThePower = numberTimesBigInt(256, toThePower);
|
||||
}
|
||||
|
||||
return decimal.split('').reverse().join('');
|
||||
}
|
||||
|
||||
function rol32(a: number, count: number): number {
|
||||
return (a << count) | (a >>> (32 - count));
|
||||
}
|
||||
// x and y decimal, lowest significant digit first
|
||||
function addBigInt(x: string, y: string): string {
|
||||
let sum = '';
|
||||
const len = Math.max(x.length, y.length);
|
||||
for (let i = 0, carry = 0; i < len || carry; i++) {
|
||||
const tmpSum = carry + +(x[i] || 0) + +(y[i] || 0);
|
||||
if (tmpSum >= 10) {
|
||||
carry = 1;
|
||||
sum += tmpSum - 10;
|
||||
} else {
|
||||
carry = 0;
|
||||
sum += tmpSum;
|
||||
}
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
function numberTimesBigInt(num: number, b: string): string {
|
||||
let product = '';
|
||||
let bToThePower = b;
|
||||
for (; num !== 0; num = num >>> 1) {
|
||||
if (num & 1) product = addBigInt(product, bToThePower);
|
||||
bToThePower = addBigInt(bToThePower, bToThePower);
|
||||
}
|
||||
return product;
|
||||
}
|
||||
|
117
modules/@angular/compiler/src/i18n/extractor.ts
Normal file
117
modules/@angular/compiler/src/i18n/extractor.ts
Normal file
@ -0,0 +1,117 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Extract i18n messages from source code
|
||||
*/
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {analyzeAndValidateNgModules, extractProgramSymbols, loadNgModuleDirectives} from '../aot/compiler';
|
||||
import {StaticAndDynamicReflectionCapabilities} from '../aot/static_reflection_capabilities';
|
||||
import {StaticReflector, StaticReflectorHost} from '../aot/static_reflector';
|
||||
import {CompileDirectiveMetadata} from '../compile_metadata';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {DirectiveNormalizer} from '../directive_normalizer';
|
||||
import {DirectiveResolver} from '../directive_resolver';
|
||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||
import {HtmlParser} from '../ml_parser/html_parser';
|
||||
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 {
|
||||
/**
|
||||
* Loads a resource (e.g. html / css)
|
||||
*/
|
||||
loadResource(path: string): Promise<string>;
|
||||
}
|
||||
|
||||
export class Extractor {
|
||||
constructor(
|
||||
private options: ExtractorOptions, public host: ExtractorHost,
|
||||
private staticReflector: StaticReflector, private messageBundle: MessageBundle,
|
||||
private metadataResolver: CompileMetadataResolver) {}
|
||||
|
||||
extract(rootFiles: string[]): Promise<MessageBundle> {
|
||||
const programSymbols = extractProgramSymbols(this.staticReflector, rootFiles, this.options);
|
||||
const {ngModuleByPipeOrDirective, files, ngModules} =
|
||||
analyzeAndValidateNgModules(programSymbols, this.options, this.metadataResolver);
|
||||
return loadNgModuleDirectives(ngModules).then(() => {
|
||||
const errors: ParseError[] = [];
|
||||
|
||||
files.forEach(file => {
|
||||
const compMetas: CompileDirectiveMetadata[] = [];
|
||||
file.directives.forEach(directiveType => {
|
||||
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
|
||||
if (dirMeta && dirMeta.isComponent) {
|
||||
compMetas.push(dirMeta);
|
||||
}
|
||||
});
|
||||
compMetas.forEach(compMeta => {
|
||||
const html = compMeta.template.template;
|
||||
const interpolationConfig =
|
||||
InterpolationConfig.fromArray(compMeta.template.interpolation);
|
||||
errors.push(
|
||||
...this.messageBundle.updateFromTemplate(html, file.srcUrl, interpolationConfig));
|
||||
});
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(errors.map(e => e.toString()).join('\n'));
|
||||
}
|
||||
|
||||
return this.messageBundle;
|
||||
});
|
||||
}
|
||||
|
||||
static create(host: ExtractorHost, options: ExtractorOptions):
|
||||
{extractor: Extractor, staticReflector: StaticReflector} {
|
||||
const htmlParser = new I18NHtmlParser(new HtmlParser());
|
||||
|
||||
const urlResolver = createOfflineCompileUrlResolver();
|
||||
const staticReflector = new StaticReflector(host);
|
||||
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
||||
|
||||
const config = new CompilerConfig({
|
||||
genDebugInfo: false,
|
||||
defaultEncapsulation: ViewEncapsulation.Emulated,
|
||||
logBindingUpdate: false,
|
||||
useJit: false
|
||||
});
|
||||
|
||||
const normalizer = new DirectiveNormalizer(
|
||||
{get: (url: string) => host.loadResource(url)}, urlResolver, htmlParser, config);
|
||||
const elementSchemaRegistry = new DomElementSchemaRegistry();
|
||||
const resolver = new CompileMetadataResolver(
|
||||
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
|
||||
new PipeResolver(staticReflector), elementSchemaRegistry, normalizer, staticReflector);
|
||||
|
||||
// TODO(vicb): implicit tags & attributes
|
||||
const messageBundle = new MessageBundle(htmlParser, [], {});
|
||||
|
||||
const extractor = new Extractor(options, host, staticReflector, messageBundle, resolver);
|
||||
return {extractor, staticReflector};
|
||||
}
|
||||
}
|
@ -10,7 +10,6 @@ import * as html from '../ml_parser/ast';
|
||||
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {ParseTreeResult} from '../ml_parser/parser';
|
||||
|
||||
import {digestMessage} from './digest';
|
||||
import * as i18n from './i18n_ast';
|
||||
import {createI18nMessageFactory} from './i18n_parser';
|
||||
import {I18nError} from './parse_util';
|
||||
@ -214,8 +213,8 @@ class _Visitor implements html.Visitor {
|
||||
// Extract only top level nodes with the (implicit) "i18n" attribute if not in a block or an ICU
|
||||
// message
|
||||
const i18nAttr = _getI18nAttr(el);
|
||||
const isImplicit = this._implicitTags.some((tag: string): boolean => el.name === tag) &&
|
||||
!this._inIcu && !this._isInTranslatableSection;
|
||||
const isImplicit = this._implicitTags.some(tag => el.name === tag) && !this._inIcu &&
|
||||
!this._isInTranslatableSection;
|
||||
const isTopLevelImplicit = !wasInImplicitNode && isImplicit;
|
||||
this._inImplicitNode = this._inImplicitNode || isImplicit;
|
||||
|
||||
@ -348,14 +347,14 @@ class _Visitor implements html.Visitor {
|
||||
// no-op when called in extraction mode (returns [])
|
||||
private _translateMessage(el: html.Node, message: i18n.Message): html.Node[] {
|
||||
if (message && this._mode === _VisitorMode.Merge) {
|
||||
const id = digestMessage(message);
|
||||
const nodes = this._translations.get(id);
|
||||
const nodes = this._translations.get(message);
|
||||
|
||||
if (nodes) {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
this._reportError(el, `Translation unavailable for message id="${id}"`);
|
||||
this._reportError(
|
||||
el, `Translation unavailable for message id="${this._translations.digest(message)}"`);
|
||||
}
|
||||
|
||||
return [];
|
||||
@ -384,19 +383,20 @@ class _Visitor implements html.Visitor {
|
||||
if (attr.value && attr.value != '' && i18nAttributeMeanings.hasOwnProperty(attr.name)) {
|
||||
const meaning = i18nAttributeMeanings[attr.name];
|
||||
const message: i18n.Message = this._createI18nMessage([attr], meaning, '');
|
||||
const id = digestMessage(message);
|
||||
const nodes = this._translations.get(id);
|
||||
const nodes = this._translations.get(message);
|
||||
if (nodes) {
|
||||
if (nodes[0] instanceof html.Text) {
|
||||
const value = (nodes[0] as html.Text).value;
|
||||
translatedAttributes.push(new html.Attribute(attr.name, value, attr.sourceSpan));
|
||||
} else {
|
||||
this._reportError(
|
||||
el, `Unexpected translation for attribute "${attr.name}" (id="${id}")`);
|
||||
el,
|
||||
`Unexpected translation for attribute "${attr.name}" (id="${this._translations.digest(message)}")`);
|
||||
}
|
||||
} else {
|
||||
this._reportError(
|
||||
el, `Translation unavailable for attribute "${attr.name}" (id="${id}")`);
|
||||
el,
|
||||
`Translation unavailable for attribute "${attr.name}" (id="${this._translations.digest(message)}")`);
|
||||
}
|
||||
} else {
|
||||
translatedAttributes.push(attr);
|
||||
|
@ -12,18 +12,20 @@ export class Message {
|
||||
/**
|
||||
* @param nodes message AST
|
||||
* @param placeholders maps placeholder names to static content
|
||||
* @param placeholderToMsgIds maps placeholder names to translatable message IDs (used for ICU
|
||||
* messages)
|
||||
* @param placeholderToMessage maps placeholder names to messages (used for nested ICU messages)
|
||||
* @param meaning
|
||||
* @param description
|
||||
*/
|
||||
constructor(
|
||||
public nodes: Node[], public placeholders: {[name: string]: string},
|
||||
public placeholderToMsgIds: {[name: string]: string}, public meaning: string,
|
||||
public nodes: Node[], public placeholders: {[phName: string]: string},
|
||||
public placeholderToMessage: {[phName: string]: Message}, public meaning: string,
|
||||
public description: string) {}
|
||||
}
|
||||
|
||||
export interface Node { visit(visitor: Visitor, context?: any): any; }
|
||||
export interface Node {
|
||||
sourceSpan: ParseSourceSpan;
|
||||
visit(visitor: Visitor, context?: any): any;
|
||||
}
|
||||
|
||||
export class Text implements Node {
|
||||
constructor(public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||
@ -31,6 +33,7 @@ export class Text implements Node {
|
||||
visit(visitor: Visitor, context?: any): any { return visitor.visitText(this, context); }
|
||||
}
|
||||
|
||||
// TODO(vicb): do we really need this node (vs an array) ?
|
||||
export class Container implements Node {
|
||||
constructor(public children: Node[], public sourceSpan: ParseSourceSpan) {}
|
||||
|
||||
@ -38,6 +41,7 @@ export class Container implements Node {
|
||||
}
|
||||
|
||||
export class Icu implements Node {
|
||||
public expressionPlaceholder: string;
|
||||
constructor(
|
||||
public expression: string, public type: string, public cases: {[k: string]: Node},
|
||||
public sourceSpan: ParseSourceSpan) {}
|
||||
@ -55,13 +59,13 @@ export class TagPlaceholder implements Node {
|
||||
}
|
||||
|
||||
export class Placeholder implements Node {
|
||||
constructor(public value: string, public name: string = '', public sourceSpan: ParseSourceSpan) {}
|
||||
constructor(public value: string, public name: string, public sourceSpan: ParseSourceSpan) {}
|
||||
|
||||
visit(visitor: Visitor, context?: any): any { return visitor.visitPlaceholder(this, context); }
|
||||
}
|
||||
|
||||
export class IcuPlaceholder implements Node {
|
||||
constructor(public value: Icu, public name: string = '', public sourceSpan: ParseSourceSpan) {}
|
||||
constructor(public value: Icu, public name: string, public sourceSpan: ParseSourceSpan) {}
|
||||
|
||||
visit(visitor: Visitor, context?: any): any { return visitor.visitIcuPlaceholder(this, context); }
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/in
|
||||
import {ParseTreeResult} from '../ml_parser/parser';
|
||||
|
||||
import {mergeTranslations} from './extractor_merger';
|
||||
import {MessageBundle} from './message_bundle';
|
||||
import {Serializer} from './serializers/serializer';
|
||||
import {Xliff} from './serializers/xliff';
|
||||
import {Xmb} from './serializers/xmb';
|
||||
@ -41,32 +40,29 @@ export class I18NHtmlParser implements HtmlParser {
|
||||
}
|
||||
|
||||
// TODO(vicb): add support for implicit tags / attributes
|
||||
const messageBundle = new MessageBundle(this._htmlParser, [], {});
|
||||
const errors = messageBundle.updateFromTemplate(source, url, interpolationConfig);
|
||||
|
||||
if (errors && errors.length) {
|
||||
return new ParseTreeResult(parseResult.rootNodes, parseResult.errors.concat(errors));
|
||||
if (parseResult.errors.length) {
|
||||
return new ParseTreeResult(parseResult.rootNodes, parseResult.errors);
|
||||
}
|
||||
|
||||
const serializer = this._createSerializer(interpolationConfig);
|
||||
const translationBundle =
|
||||
TranslationBundle.load(this._translations, url, messageBundle, serializer);
|
||||
const serializer = this._createSerializer();
|
||||
const translationBundle = TranslationBundle.load(this._translations, url, serializer);
|
||||
|
||||
return mergeTranslations(parseResult.rootNodes, translationBundle, interpolationConfig, [], {});
|
||||
}
|
||||
|
||||
private _createSerializer(interpolationConfig: InterpolationConfig): Serializer {
|
||||
private _createSerializer(): Serializer {
|
||||
const format = (this._translationsFormat || 'xlf').toLowerCase();
|
||||
|
||||
switch (format) {
|
||||
case 'xmb':
|
||||
return new Xmb();
|
||||
case 'xtb':
|
||||
return new Xtb(this._htmlParser, interpolationConfig);
|
||||
return new Xtb();
|
||||
case 'xliff':
|
||||
case 'xlf':
|
||||
default:
|
||||
return new Xliff(this._htmlParser, interpolationConfig);
|
||||
return new Xliff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import * as html from '../ml_parser/ast';
|
||||
import {getHtmlTagDefinition} from '../ml_parser/html_tags';
|
||||
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
import {digestMessage} from './digest';
|
||||
|
||||
import * as i18n from './i18n_ast';
|
||||
import {PlaceholderRegistry} from './serializers/placeholder';
|
||||
@ -34,8 +33,8 @@ class _I18nVisitor implements html.Visitor {
|
||||
private _isIcu: boolean;
|
||||
private _icuDepth: number;
|
||||
private _placeholderRegistry: PlaceholderRegistry;
|
||||
private _placeholderToContent: {[name: string]: string};
|
||||
private _placeholderToIds: {[name: string]: string};
|
||||
private _placeholderToContent: {[phName: string]: string};
|
||||
private _placeholderToMessage: {[phName: string]: i18n.Message};
|
||||
|
||||
constructor(
|
||||
private _expressionParser: ExpressionParser,
|
||||
@ -46,12 +45,12 @@ class _I18nVisitor implements html.Visitor {
|
||||
this._icuDepth = 0;
|
||||
this._placeholderRegistry = new PlaceholderRegistry();
|
||||
this._placeholderToContent = {};
|
||||
this._placeholderToIds = {};
|
||||
this._placeholderToMessage = {};
|
||||
|
||||
const i18nodes: i18n.Node[] = html.visitAll(this, nodes, {});
|
||||
|
||||
return new i18n.Message(
|
||||
i18nodes, this._placeholderToContent, this._placeholderToIds, meaning, description);
|
||||
i18nodes, this._placeholderToContent, this._placeholderToMessage, meaning, description);
|
||||
}
|
||||
|
||||
visitElement(el: html.Element, context: any): i18n.Node {
|
||||
@ -99,7 +98,13 @@ class _I18nVisitor implements html.Visitor {
|
||||
this._icuDepth--;
|
||||
|
||||
if (this._isIcu || this._icuDepth > 0) {
|
||||
// If the message (vs a part of the message) is an ICU message returns it
|
||||
// Returns an ICU node when:
|
||||
// - the message (vs a part of the message) is an ICU message, or
|
||||
// - the ICU message is nested.
|
||||
const expPh = this._placeholderRegistry.getUniquePlaceholder(`VAR_${icu.type}`);
|
||||
i18nIcu.expressionPlaceholder = expPh;
|
||||
this._placeholderToContent[expPh] = icu.switchValue;
|
||||
|
||||
return i18nIcu;
|
||||
}
|
||||
|
||||
@ -110,7 +115,7 @@ class _I18nVisitor implements html.Visitor {
|
||||
// TODO(vicb): add a html.Node -> i18n.Message cache to avoid having to re-create the msg
|
||||
const phName = this._placeholderRegistry.getPlaceholderName('ICU', icu.sourceSpan.toString());
|
||||
const visitor = new _I18nVisitor(this._expressionParser, this._interpolationConfig);
|
||||
this._placeholderToIds[phName] = digestMessage(visitor.toI18nMessage([icu], '', ''));
|
||||
this._placeholderToMessage[phName] = visitor.toI18nMessage([icu], '', '');
|
||||
return new i18n.IcuPlaceholder(i18nIcu, phName, icu.sourceSpan);
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export {Extractor, ExtractorHost, ExtractorOptions} from './extractor';
|
||||
export {I18NHtmlParser} from './i18n_html_parser';
|
||||
export {MessageBundle} from './message_bundle';
|
||||
export {Serializer} from './serializers/serializer';
|
||||
|
@ -10,7 +10,6 @@ import {HtmlParser} from '../ml_parser/html_parser';
|
||||
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {ParseError} from '../parse_util';
|
||||
|
||||
import {digestMessage} from './digest';
|
||||
import {extractMessages} from './extractor_merger';
|
||||
import {Message} from './i18n_ast';
|
||||
import {Serializer} from './serializers/serializer';
|
||||
@ -19,7 +18,7 @@ import {Serializer} from './serializers/serializer';
|
||||
* A container for message extracted from the templates.
|
||||
*/
|
||||
export class MessageBundle {
|
||||
private _messageMap: {[id: string]: Message} = {};
|
||||
private _messages: Message[] = [];
|
||||
|
||||
constructor(
|
||||
private _htmlParser: HtmlParser, private _implicitTags: string[],
|
||||
@ -40,11 +39,10 @@ export class MessageBundle {
|
||||
return i18nParserResult.errors;
|
||||
}
|
||||
|
||||
i18nParserResult.messages.forEach(
|
||||
(message) => { this._messageMap[digestMessage(message)] = message; });
|
||||
this._messages.push(...i18nParserResult.messages);
|
||||
}
|
||||
|
||||
getMessageMap(): {[id: string]: Message} { return this._messageMap; }
|
||||
getMessages(): Message[] { return this._messages; }
|
||||
|
||||
write(serializer: Serializer): string { return serializer.write(this._messageMap); }
|
||||
write(serializer: Serializer): string { return serializer.write(this._messages); }
|
||||
}
|
||||
|
@ -40,7 +40,9 @@ const TAG_TO_PLACEHOLDER_NAMES: {[k: string]: string} = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates unique names for placeholder with different content
|
||||
* Creates unique names for placeholder with different content.
|
||||
*
|
||||
* Returns the same placeholder name when the content is identical.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
@ -93,6 +95,10 @@ export class PlaceholderRegistry {
|
||||
return uniqueName;
|
||||
}
|
||||
|
||||
getUniquePlaceholder(name: string): string {
|
||||
return this._generateUniqueName(name.toUpperCase());
|
||||
}
|
||||
|
||||
// Generate a hash for a tag - does not take attribute order into account
|
||||
private _hashTag(tag: string, attrs: {[k: string]: string}, isVoid: boolean): string {
|
||||
const start = `<${tag}`;
|
||||
@ -105,18 +111,8 @@ export class PlaceholderRegistry {
|
||||
private _hashClosingTag(tag: string): string { return this._hashTag(`/${tag}`, {}, false); }
|
||||
|
||||
private _generateUniqueName(base: string): string {
|
||||
let name = base;
|
||||
let next = this._placeHolderNameCounts[name];
|
||||
|
||||
if (!next) {
|
||||
next = 1;
|
||||
} else {
|
||||
name += `_${next}`;
|
||||
next++;
|
||||
}
|
||||
|
||||
this._placeHolderNameCounts[base] = next;
|
||||
|
||||
return name;
|
||||
const next = this._placeHolderNameCounts[base];
|
||||
this._placeHolderNameCounts[base] = next ? next + 1 : 1;
|
||||
return next ? `${base}_${next}` : base;
|
||||
}
|
||||
}
|
||||
|
@ -6,36 +6,12 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as html from '../../ml_parser/ast';
|
||||
import * as i18n from '../i18n_ast';
|
||||
import {MessageBundle} from '../message_bundle';
|
||||
|
||||
export interface Serializer {
|
||||
write(messageMap: {[id: string]: i18n.Message}): string;
|
||||
write(messages: i18n.Message[]): string;
|
||||
|
||||
load(content: string, url: string, messageBundle: MessageBundle): {[id: string]: html.Node[]};
|
||||
}
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]};
|
||||
|
||||
// Generate a map of placeholder to content indexed by message ids
|
||||
export function extractPlaceholders(messageBundle: MessageBundle) {
|
||||
const messageMap = messageBundle.getMessageMap();
|
||||
const placeholders: {[id: string]: {[name: string]: string}} = {};
|
||||
|
||||
Object.keys(messageMap).forEach(msgId => {
|
||||
placeholders[msgId] = messageMap[msgId].placeholders;
|
||||
});
|
||||
|
||||
return placeholders;
|
||||
}
|
||||
|
||||
// Generate a map of placeholder to message ids indexed by message ids
|
||||
export function extractPlaceholderToIds(messageBundle: MessageBundle) {
|
||||
const messageMap = messageBundle.getMessageMap();
|
||||
const placeholderToIds: {[id: string]: {[name: string]: string}} = {};
|
||||
|
||||
Object.keys(messageMap).forEach(msgId => {
|
||||
placeholderToIds[msgId] = messageMap[msgId].placeholderToMsgIds;
|
||||
});
|
||||
|
||||
return placeholderToIds;
|
||||
digest(message: i18n.Message): string;
|
||||
}
|
@ -6,17 +6,13 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ListWrapper} from '../../facade/collection';
|
||||
import * as ml from '../../ml_parser/ast';
|
||||
import {HtmlParser} from '../../ml_parser/html_parser';
|
||||
import {InterpolationConfig} from '../../ml_parser/interpolation_config';
|
||||
import {XmlParser} from '../../ml_parser/xml_parser';
|
||||
import {ParseError} from '../../parse_util';
|
||||
import {digest} from '../digest';
|
||||
import * as i18n from '../i18n_ast';
|
||||
import {MessageBundle} from '../message_bundle';
|
||||
import {I18nError} from '../parse_util';
|
||||
|
||||
import {Serializer, extractPlaceholderToIds, extractPlaceholders} from './serializer';
|
||||
import {Serializer} from './serializer';
|
||||
import * as xml from './xml_helper';
|
||||
|
||||
const _VERSION = '1.2';
|
||||
@ -24,6 +20,7 @@ const _XMLNS = 'urn:oasis:names:tc:xliff:document:1.2';
|
||||
// TODO(vicb): make this a param (s/_/-/)
|
||||
const _SOURCE_LANG = 'en';
|
||||
const _PLACEHOLDER_TAG = 'x';
|
||||
|
||||
const _SOURCE_TAG = 'source';
|
||||
const _TARGET_TAG = 'target';
|
||||
const _UNIT_TAG = 'trans-unit';
|
||||
@ -31,17 +28,19 @@ const _UNIT_TAG = 'trans-unit';
|
||||
// http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html
|
||||
// http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html
|
||||
export class Xliff implements Serializer {
|
||||
constructor(private _htmlParser: HtmlParser, private _interpolationConfig: InterpolationConfig) {}
|
||||
|
||||
write(messageMap: {[id: string]: i18n.Message}): string {
|
||||
write(messages: i18n.Message[]): string {
|
||||
const visitor = new _WriteVisitor();
|
||||
|
||||
const visited: {[id: string]: boolean} = {};
|
||||
const transUnits: xml.Node[] = [];
|
||||
|
||||
Object.keys(messageMap).forEach((id) => {
|
||||
const message = messageMap[id];
|
||||
messages.forEach(message => {
|
||||
const id = this.digest(message);
|
||||
|
||||
const transUnit = new xml.Tag(_UNIT_TAG, {id: id, datatype: 'html'});
|
||||
// deduplicate messages
|
||||
if (visited[id]) return;
|
||||
visited[id] = true;
|
||||
|
||||
const transUnit = new xml.Tag(_UNIT_TAG, {id, datatype: 'html'});
|
||||
transUnit.children.push(
|
||||
new xml.CR(8), new xml.Tag(_SOURCE_TAG, {}, visitor.serialize(message.nodes)),
|
||||
new xml.CR(8), new xml.Tag(_TARGET_TAG));
|
||||
@ -76,38 +75,28 @@ export class Xliff implements Serializer {
|
||||
]);
|
||||
}
|
||||
|
||||
load(content: string, url: string, messageBundle: MessageBundle): {[id: string]: ml.Node[]} {
|
||||
// Parse the xtb file into xml nodes
|
||||
const result = new XmlParser().parse(content, url);
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
|
||||
// xliff to xml nodes
|
||||
const xliffParser = new XliffParser();
|
||||
const {mlNodesByMsgId, errors} = xliffParser.parse(content, url);
|
||||
|
||||
if (result.errors.length) {
|
||||
throw new Error(`xtb parse errors:\n${result.errors.join('\n')}`);
|
||||
}
|
||||
|
||||
// Replace the placeholders, messages are now string
|
||||
const {messages, errors} = new _LoadVisitor().parse(result.rootNodes, messageBundle);
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(`xtb parse errors:\n${errors.join('\n')}`);
|
||||
}
|
||||
|
||||
// Convert the string messages to html ast
|
||||
// TODO(vicb): map error message back to the original message in xtb
|
||||
const messageMap: {[id: string]: ml.Node[]} = {};
|
||||
const parseErrors: ParseError[] = [];
|
||||
|
||||
Object.keys(messages).forEach((id) => {
|
||||
const res = this._htmlParser.parse(messages[id], url, true, this._interpolationConfig);
|
||||
parseErrors.push(...res.errors);
|
||||
messageMap[id] = res.rootNodes;
|
||||
// xml nodes to i18n nodes
|
||||
const i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {};
|
||||
const converter = new XmlToI18n();
|
||||
Object.keys(mlNodesByMsgId).forEach(msgId => {
|
||||
const {i18nNodes, errors: e} = converter.convert(mlNodesByMsgId[msgId]);
|
||||
errors.push(...e);
|
||||
i18nNodesByMsgId[msgId] = i18nNodes;
|
||||
});
|
||||
|
||||
if (parseErrors.length) {
|
||||
throw new Error(`xtb parse errors:\n${parseErrors.join('\n')}`);
|
||||
if (errors.length) {
|
||||
throw new Error(`xliff parse errors:\n${errors.join('\n')}`);
|
||||
}
|
||||
|
||||
return messageMap;
|
||||
return i18nNodesByMsgId;
|
||||
}
|
||||
|
||||
digest(message: i18n.Message): string { return digest(message); }
|
||||
}
|
||||
|
||||
class _WriteVisitor implements i18n.Visitor {
|
||||
@ -162,80 +151,51 @@ class _WriteVisitor implements i18n.Visitor {
|
||||
|
||||
serialize(nodes: i18n.Node[]): xml.Node[] {
|
||||
this._isInIcu = false;
|
||||
return ListWrapper.flatten(nodes.map(node => node.visit(this)));
|
||||
return [].concat(...nodes.map(node => node.visit(this)));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(vicb): add error management (structure)
|
||||
// TODO(vicb): factorize (xtb) ?
|
||||
class _LoadVisitor implements ml.Visitor {
|
||||
private _messageNodes: [string, ml.Node[]][];
|
||||
private _translatedMessages: {[id: string]: string};
|
||||
private _msgId: string;
|
||||
private _target: ml.Node[];
|
||||
// Extract messages as xml nodes from the xliff file
|
||||
class XliffParser implements ml.Visitor {
|
||||
private _unitMlNodes: ml.Node[];
|
||||
private _errors: I18nError[];
|
||||
private _placeholders: {[name: string]: string};
|
||||
private _placeholderToIds: {[name: string]: string};
|
||||
private _mlNodesByMsgId: {[msgId: string]: ml.Node[]};
|
||||
|
||||
parse(nodes: ml.Node[], messageBundle: MessageBundle):
|
||||
{messages: {[k: string]: string}, errors: I18nError[]} {
|
||||
this._messageNodes = [];
|
||||
this._translatedMessages = {};
|
||||
this._msgId = '';
|
||||
this._target = [];
|
||||
this._errors = [];
|
||||
parse(xliff: string, url: string) {
|
||||
this._unitMlNodes = [];
|
||||
this._mlNodesByMsgId = {};
|
||||
|
||||
// Find all messages
|
||||
ml.visitAll(this, nodes, null);
|
||||
const xml = new XmlParser().parse(xliff, url, false);
|
||||
|
||||
const messageMap = messageBundle.getMessageMap();
|
||||
const placeholders = extractPlaceholders(messageBundle);
|
||||
const placeholderToIds = extractPlaceholderToIds(messageBundle);
|
||||
this._errors = xml.errors;
|
||||
ml.visitAll(this, xml.rootNodes, null);
|
||||
|
||||
this._messageNodes
|
||||
.filter(message => {
|
||||
// Remove any messages that is not present in the source message bundle.
|
||||
return messageMap.hasOwnProperty(message[0]);
|
||||
})
|
||||
.sort((a, b) => {
|
||||
// Because there could be no ICU placeholders inside an ICU message,
|
||||
// we do not need to take into account the `placeholderToMsgIds` of the referenced
|
||||
// messages, those would always be empty
|
||||
// TODO(vicb): overkill - create 2 buckets and [...woDeps, ...wDeps].process()
|
||||
if (Object.keys(messageMap[a[0]].placeholderToMsgIds).length == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (Object.keys(messageMap[b[0]].placeholderToMsgIds).length == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
})
|
||||
.forEach(message => {
|
||||
const id = message[0];
|
||||
this._placeholders = placeholders[id] || {};
|
||||
this._placeholderToIds = placeholderToIds[id] || {};
|
||||
// TODO(vicb): make sure there is no `_TRANSLATIONS_TAG` nor `_TRANSLATION_TAG`
|
||||
this._translatedMessages[id] = ml.visitAll(this, message[1]).join('');
|
||||
});
|
||||
|
||||
return {messages: this._translatedMessages, errors: this._errors};
|
||||
return {
|
||||
mlNodesByMsgId: this._mlNodesByMsgId,
|
||||
errors: this._errors,
|
||||
};
|
||||
}
|
||||
|
||||
visitElement(element: ml.Element, context: any): any {
|
||||
switch (element.name) {
|
||||
case _UNIT_TAG:
|
||||
this._target = null;
|
||||
const msgId = element.attrs.find((attr) => attr.name === 'id');
|
||||
if (!msgId) {
|
||||
this._unitMlNodes = null;
|
||||
const idAttr = element.attrs.find((attr) => attr.name === 'id');
|
||||
if (!idAttr) {
|
||||
this._addError(element, `<${_UNIT_TAG}> misses the "id" attribute`);
|
||||
} else {
|
||||
this._msgId = msgId.value;
|
||||
}
|
||||
ml.visitAll(this, element.children, null);
|
||||
if (this._msgId !== null) {
|
||||
this._messageNodes.push([this._msgId, this._target]);
|
||||
const id = idAttr.value;
|
||||
if (this._mlNodesByMsgId.hasOwnProperty(id)) {
|
||||
this._addError(element, `Duplicated translations for msg ${id}`);
|
||||
} else {
|
||||
ml.visitAll(this, element.children, null);
|
||||
if (this._unitMlNodes) {
|
||||
this._mlNodesByMsgId[id] = this._unitMlNodes;
|
||||
} else {
|
||||
this._addError(element, `Message ${id} misses a translation`);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -244,48 +204,65 @@ class _LoadVisitor implements ml.Visitor {
|
||||
break;
|
||||
|
||||
case _TARGET_TAG:
|
||||
this._target = element.children;
|
||||
break;
|
||||
|
||||
case _PLACEHOLDER_TAG:
|
||||
const idAttr = element.attrs.find((attr) => attr.name === 'id');
|
||||
if (!idAttr) {
|
||||
this._addError(element, `<${_PLACEHOLDER_TAG}> misses the "id" attribute`);
|
||||
} else {
|
||||
const id = idAttr.value;
|
||||
if (this._placeholders.hasOwnProperty(id)) {
|
||||
return this._placeholders[id];
|
||||
}
|
||||
if (this._placeholderToIds.hasOwnProperty(id) &&
|
||||
this._translatedMessages.hasOwnProperty(this._placeholderToIds[id])) {
|
||||
return this._translatedMessages[this._placeholderToIds[id]];
|
||||
}
|
||||
// TODO(vicb): better error message for when
|
||||
// !this._translatedMessages.hasOwnProperty(this._placeholderToIds[id])
|
||||
this._addError(element, `The placeholder "${id}" does not exists in the source message`);
|
||||
}
|
||||
this._unitMlNodes = element.children;
|
||||
break;
|
||||
|
||||
default:
|
||||
// TODO(vicb): assert file structure, xliff version
|
||||
// For now only recurse on unhandled nodes
|
||||
ml.visitAll(this, element.children, null);
|
||||
}
|
||||
}
|
||||
|
||||
visitAttribute(attribute: ml.Attribute, context: any): any {
|
||||
throw new Error('unreachable code');
|
||||
visitAttribute(attribute: ml.Attribute, context: any): any {}
|
||||
|
||||
visitText(text: ml.Text, context: any): any {}
|
||||
|
||||
visitComment(comment: ml.Comment, context: any): any {}
|
||||
|
||||
visitExpansion(expansion: ml.Expansion, context: any): any {}
|
||||
|
||||
visitExpansionCase(expansionCase: ml.ExpansionCase, context: any): any {}
|
||||
|
||||
private _addError(node: ml.Node, message: string): void {
|
||||
this._errors.push(new I18nError(node.sourceSpan, message));
|
||||
}
|
||||
}
|
||||
|
||||
// Convert ml nodes (xliff syntax) to i18n nodes
|
||||
class XmlToI18n implements ml.Visitor {
|
||||
private _errors: I18nError[];
|
||||
|
||||
convert(nodes: ml.Node[]) {
|
||||
this._errors = [];
|
||||
return {
|
||||
i18nNodes: ml.visitAll(this, nodes),
|
||||
errors: this._errors,
|
||||
};
|
||||
}
|
||||
|
||||
visitText(text: ml.Text, context: any): any { return text.value; }
|
||||
visitText(text: ml.Text, context: any) { return new i18n.Text(text.value, text.sourceSpan); }
|
||||
|
||||
visitComment(comment: ml.Comment, context: any): any { return ''; }
|
||||
visitElement(el: ml.Element, context: any): i18n.Placeholder {
|
||||
if (el.name === _PLACEHOLDER_TAG) {
|
||||
const nameAttr = el.attrs.find((attr) => attr.name === 'id');
|
||||
if (nameAttr) {
|
||||
return new i18n.Placeholder('', nameAttr.value, el.sourceSpan);
|
||||
}
|
||||
|
||||
visitExpansion(expansion: ml.Expansion, context: any): any {
|
||||
throw new Error('unreachable code');
|
||||
this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "id" attribute`);
|
||||
} else {
|
||||
this._addError(el, `Unexpected tag`);
|
||||
}
|
||||
}
|
||||
|
||||
visitExpansionCase(expansionCase: ml.ExpansionCase, context: any): any {
|
||||
throw new Error('unreachable code');
|
||||
}
|
||||
visitExpansion(icu: ml.Expansion, context: any) {}
|
||||
|
||||
visitExpansionCase(icuCase: ml.ExpansionCase, context: any): any {}
|
||||
|
||||
visitComment(comment: ml.Comment, context: any) {}
|
||||
|
||||
visitAttribute(attribute: ml.Attribute, context: any) {}
|
||||
|
||||
private _addError(node: ml.Node, message: string): void {
|
||||
this._errors.push(new I18nError(node.sourceSpan, message));
|
||||
|
@ -6,10 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ListWrapper} from '../../facade/collection';
|
||||
import * as html from '../../ml_parser/ast';
|
||||
import {decimalDigest} from '../digest';
|
||||
import * as i18n from '../i18n_ast';
|
||||
import {MessageBundle} from '../message_bundle';
|
||||
|
||||
import {Serializer} from './serializer';
|
||||
import * as xml from './xml_helper';
|
||||
@ -40,12 +38,18 @@ const _DOCTYPE = `<!ELEMENT messagebundle (msg)*>
|
||||
<!ELEMENT ex (#PCDATA)>`;
|
||||
|
||||
export class Xmb implements Serializer {
|
||||
write(messageMap: {[k: string]: i18n.Message}): string {
|
||||
write(messages: i18n.Message[]): string {
|
||||
const visitor = new _Visitor();
|
||||
const rootNode = new xml.Tag(_MESSAGES_TAG);
|
||||
const visited: {[id: string]: boolean} = {};
|
||||
let rootNode = new xml.Tag(_MESSAGES_TAG);
|
||||
|
||||
messages.forEach(message => {
|
||||
const id = this.digest(message);
|
||||
|
||||
// deduplicate messages
|
||||
if (visited[id]) return;
|
||||
visited[id] = true;
|
||||
|
||||
Object.keys(messageMap).forEach((id) => {
|
||||
const message = messageMap[id];
|
||||
const attrs: {[k: string]: string} = {id};
|
||||
|
||||
if (message.description) {
|
||||
@ -72,9 +76,11 @@ export class Xmb implements Serializer {
|
||||
]);
|
||||
}
|
||||
|
||||
load(content: string, url: string, messageBundle: MessageBundle): {[id: string]: html.Node[]} {
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
|
||||
throw new Error('Unsupported');
|
||||
}
|
||||
|
||||
digest(message: i18n.Message): string { return digest(message); }
|
||||
}
|
||||
|
||||
class _Visitor implements i18n.Visitor {
|
||||
@ -87,7 +93,7 @@ class _Visitor implements i18n.Visitor {
|
||||
}
|
||||
|
||||
visitIcu(icu: i18n.Icu, context?: any): xml.Node[] {
|
||||
const nodes = [new xml.Text(`{${icu.expression}, ${icu.type}, `)];
|
||||
const nodes = [new xml.Text(`{${icu.expressionPlaceholder}, ${icu.type}, `)];
|
||||
|
||||
Object.keys(icu.cases).forEach((c: string) => {
|
||||
nodes.push(new xml.Text(`${c} {`), ...icu.cases[c].visit(this), new xml.Text(`} `));
|
||||
@ -121,6 +127,10 @@ class _Visitor implements i18n.Visitor {
|
||||
}
|
||||
|
||||
serialize(nodes: i18n.Node[]): xml.Node[] {
|
||||
return ListWrapper.flatten(nodes.map(node => node.visit(this)));
|
||||
return [].concat(...nodes.map(node => node.visit(this)));
|
||||
}
|
||||
}
|
||||
|
||||
export function digest(message: i18n.Message): string {
|
||||
return decimalDigest(message);
|
||||
}
|
@ -7,112 +7,63 @@
|
||||
*/
|
||||
|
||||
import * as ml from '../../ml_parser/ast';
|
||||
import {HtmlParser} from '../../ml_parser/html_parser';
|
||||
import {InterpolationConfig} from '../../ml_parser/interpolation_config';
|
||||
import {XmlParser} from '../../ml_parser/xml_parser';
|
||||
import {ParseError} from '../../parse_util';
|
||||
import * as i18n from '../i18n_ast';
|
||||
import {MessageBundle} from '../message_bundle';
|
||||
import {I18nError} from '../parse_util';
|
||||
|
||||
import {Serializer, extractPlaceholderToIds, extractPlaceholders} from './serializer';
|
||||
import {Serializer} from './serializer';
|
||||
import {digest} from './xmb';
|
||||
|
||||
const _TRANSLATIONS_TAG = 'translationbundle';
|
||||
const _TRANSLATION_TAG = 'translation';
|
||||
const _PLACEHOLDER_TAG = 'ph';
|
||||
|
||||
export class Xtb implements Serializer {
|
||||
constructor(private _htmlParser: HtmlParser, private _interpolationConfig: InterpolationConfig) {}
|
||||
write(messages: i18n.Message[]): string { throw new Error('Unsupported'); }
|
||||
|
||||
write(messageMap: {[id: string]: i18n.Message}): string { throw new Error('Unsupported'); }
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
|
||||
// xtb to xml nodes
|
||||
const xtbParser = new XtbParser();
|
||||
const {mlNodesByMsgId, errors} = xtbParser.parse(content, url);
|
||||
|
||||
load(content: string, url: string, messageBundle: MessageBundle): {[id: string]: ml.Node[]} {
|
||||
// Parse the xtb file into xml nodes
|
||||
const result = new XmlParser().parse(content, url);
|
||||
|
||||
if (result.errors.length) {
|
||||
throw new Error(`xtb parse errors:\n${result.errors.join('\n')}`);
|
||||
}
|
||||
|
||||
// Replace the placeholders, messages are now string
|
||||
const {messages, errors} = new _Visitor().parse(result.rootNodes, messageBundle);
|
||||
// xml nodes to i18n nodes
|
||||
const i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {};
|
||||
const converter = new XmlToI18n();
|
||||
Object.keys(mlNodesByMsgId).forEach(msgId => {
|
||||
const {i18nNodes, errors: e} = converter.convert(mlNodesByMsgId[msgId]);
|
||||
errors.push(...e);
|
||||
i18nNodesByMsgId[msgId] = i18nNodes;
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(`xtb parse errors:\n${errors.join('\n')}`);
|
||||
}
|
||||
|
||||
// Convert the string messages to html ast
|
||||
// TODO(vicb): map error message back to the original message in xtb
|
||||
const messageMap: {[id: string]: ml.Node[]} = {};
|
||||
const parseErrors: ParseError[] = [];
|
||||
|
||||
Object.keys(messages).forEach((id) => {
|
||||
const res = this._htmlParser.parse(messages[id], url, true, this._interpolationConfig);
|
||||
parseErrors.push(...res.errors);
|
||||
messageMap[id] = res.rootNodes;
|
||||
});
|
||||
|
||||
if (parseErrors.length) {
|
||||
throw new Error(`xtb parse errors:\n${parseErrors.join('\n')}`);
|
||||
}
|
||||
|
||||
return messageMap;
|
||||
return i18nNodesByMsgId;
|
||||
}
|
||||
|
||||
digest(message: i18n.Message): string { return digest(message); }
|
||||
}
|
||||
|
||||
class _Visitor implements ml.Visitor {
|
||||
private _messageNodes: [string, ml.Node[]][];
|
||||
private _translatedMessages: {[id: string]: string};
|
||||
// Extract messages as xml nodes from the xtb file
|
||||
class XtbParser implements ml.Visitor {
|
||||
private _bundleDepth: number;
|
||||
private _translationDepth: number;
|
||||
private _errors: I18nError[];
|
||||
private _placeholders: {[name: string]: string};
|
||||
private _placeholderToIds: {[name: string]: string};
|
||||
private _mlNodesByMsgId: {[msgId: string]: ml.Node[]};
|
||||
|
||||
parse(nodes: ml.Node[], messageBundle: MessageBundle):
|
||||
{messages: {[k: string]: string}, errors: I18nError[]} {
|
||||
this._messageNodes = [];
|
||||
this._translatedMessages = {};
|
||||
parse(xtb: string, url: string) {
|
||||
this._bundleDepth = 0;
|
||||
this._translationDepth = 0;
|
||||
this._errors = [];
|
||||
this._mlNodesByMsgId = {};
|
||||
|
||||
// Find all messages
|
||||
ml.visitAll(this, nodes, null);
|
||||
const xml = new XmlParser().parse(xtb, url, true);
|
||||
|
||||
const messageMap = messageBundle.getMessageMap();
|
||||
const placeholders = extractPlaceholders(messageBundle);
|
||||
const placeholderToIds = extractPlaceholderToIds(messageBundle);
|
||||
this._errors = xml.errors;
|
||||
ml.visitAll(this, xml.rootNodes);
|
||||
|
||||
this._messageNodes
|
||||
.filter(message => {
|
||||
// Remove any messages that is not present in the source message bundle.
|
||||
return messageMap.hasOwnProperty(message[0]);
|
||||
})
|
||||
.sort((a, b) => {
|
||||
// Because there could be no ICU placeholders inside an ICU message,
|
||||
// we do not need to take into account the `placeholderToMsgIds` of the referenced
|
||||
// messages, those would always be empty
|
||||
// TODO(vicb): overkill - create 2 buckets and [...woDeps, ...wDeps].process()
|
||||
if (Object.keys(messageMap[a[0]].placeholderToMsgIds).length == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (Object.keys(messageMap[b[0]].placeholderToMsgIds).length == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
})
|
||||
.forEach(message => {
|
||||
const id = message[0];
|
||||
this._placeholders = placeholders[id] || {};
|
||||
this._placeholderToIds = placeholderToIds[id] || {};
|
||||
// TODO(vicb): make sure there is no `_TRANSLATIONS_TAG` nor `_TRANSLATION_TAG`
|
||||
this._translatedMessages[id] = ml.visitAll(this, message[1]).join('');
|
||||
});
|
||||
|
||||
return {messages: this._translatedMessages, errors: this._errors};
|
||||
return {
|
||||
mlNodesByMsgId: this._mlNodesByMsgId,
|
||||
errors: this._errors,
|
||||
};
|
||||
}
|
||||
|
||||
visitElement(element: ml.Element, context: any): any {
|
||||
@ -127,40 +78,16 @@ class _Visitor implements ml.Visitor {
|
||||
break;
|
||||
|
||||
case _TRANSLATION_TAG:
|
||||
this._translationDepth++;
|
||||
if (this._translationDepth > 1) {
|
||||
this._addError(element, `<${_TRANSLATION_TAG}> elements can not be nested`);
|
||||
}
|
||||
const idAttr = element.attrs.find((attr) => attr.name === 'id');
|
||||
if (!idAttr) {
|
||||
this._addError(element, `<${_TRANSLATION_TAG}> misses the "id" attribute`);
|
||||
} else {
|
||||
// ICU placeholders are reference to other messages.
|
||||
// The referenced message might not have been decoded yet.
|
||||
// We need to have all messages available to make sure deps are decoded first.
|
||||
// TODO(vicb): report an error on duplicate id
|
||||
this._messageNodes.push([idAttr.value, element.children]);
|
||||
}
|
||||
this._translationDepth--;
|
||||
break;
|
||||
|
||||
case _PLACEHOLDER_TAG:
|
||||
const nameAttr = element.attrs.find((attr) => attr.name === 'name');
|
||||
if (!nameAttr) {
|
||||
this._addError(element, `<${_PLACEHOLDER_TAG}> misses the "name" attribute`);
|
||||
} else {
|
||||
const name = nameAttr.value;
|
||||
if (this._placeholders.hasOwnProperty(name)) {
|
||||
return this._placeholders[name];
|
||||
const id = idAttr.value;
|
||||
if (this._mlNodesByMsgId.hasOwnProperty(id)) {
|
||||
this._addError(element, `Duplicated translations for msg ${id}`);
|
||||
} else {
|
||||
this._mlNodesByMsgId[id] = element.children;
|
||||
}
|
||||
if (this._placeholderToIds.hasOwnProperty(name) &&
|
||||
this._translatedMessages.hasOwnProperty(this._placeholderToIds[name])) {
|
||||
return this._translatedMessages[this._placeholderToIds[name]];
|
||||
}
|
||||
// TODO(vicb): better error message for when
|
||||
// !this._translatedMessages.hasOwnProperty(this._placeholderToIds[name])
|
||||
this._addError(
|
||||
element, `The placeholder "${name}" does not exists in the source message`);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -169,23 +96,68 @@ class _Visitor implements ml.Visitor {
|
||||
}
|
||||
}
|
||||
|
||||
visitAttribute(attribute: ml.Attribute, context: any): any {
|
||||
throw new Error('unreachable code');
|
||||
}
|
||||
visitAttribute(attribute: ml.Attribute, context: any): any {}
|
||||
|
||||
visitText(text: ml.Text, context: any): any { return text.value; }
|
||||
visitText(text: ml.Text, context: any): any {}
|
||||
|
||||
visitComment(comment: ml.Comment, context: any): any { return ''; }
|
||||
visitComment(comment: ml.Comment, context: any): any {}
|
||||
|
||||
visitExpansion(expansion: ml.Expansion, context: any): any {
|
||||
const strCases = expansion.cases.map(c => c.visit(this, null));
|
||||
visitExpansion(expansion: ml.Expansion, context: any): any {}
|
||||
|
||||
return `{${expansion.switchValue}, ${expansion.type}, strCases.join(' ')}`;
|
||||
}
|
||||
|
||||
visitExpansionCase(expansionCase: ml.ExpansionCase, context: any): any {
|
||||
return `${expansionCase.value} {${ml.visitAll(this, expansionCase.expression, null)}}`;
|
||||
}
|
||||
visitExpansionCase(expansionCase: ml.ExpansionCase, context: any): any {}
|
||||
|
||||
private _addError(node: ml.Node, message: string): void {
|
||||
this._errors.push(new I18nError(node.sourceSpan, message));
|
||||
}
|
||||
}
|
||||
|
||||
// Convert ml nodes (xtb syntax) to i18n nodes
|
||||
class XmlToI18n implements ml.Visitor {
|
||||
private _errors: I18nError[];
|
||||
|
||||
convert(nodes: ml.Node[]) {
|
||||
this._errors = [];
|
||||
return {
|
||||
i18nNodes: ml.visitAll(this, nodes),
|
||||
errors: this._errors,
|
||||
};
|
||||
}
|
||||
|
||||
visitText(text: ml.Text, context: any) { return new i18n.Text(text.value, text.sourceSpan); }
|
||||
|
||||
visitExpansion(icu: ml.Expansion, context: any) {
|
||||
const caseMap: {[value: string]: i18n.Node} = {};
|
||||
|
||||
ml.visitAll(this, icu.cases).forEach(c => {
|
||||
caseMap[c.value] = new i18n.Container(c.nodes, icu.sourceSpan);
|
||||
});
|
||||
|
||||
return new i18n.Icu(icu.switchValue, icu.type, caseMap, icu.sourceSpan);
|
||||
}
|
||||
|
||||
visitExpansionCase(icuCase: ml.ExpansionCase, context: any): any {
|
||||
return {
|
||||
value: icuCase.value,
|
||||
nodes: ml.visitAll(this, icuCase.expression),
|
||||
};
|
||||
}
|
||||
|
||||
visitElement(el: ml.Element, context: any): i18n.Placeholder {
|
||||
if (el.name === _PLACEHOLDER_TAG) {
|
||||
const nameAttr = el.attrs.find((attr) => attr.name === 'name');
|
||||
if (nameAttr) {
|
||||
return new i18n.Placeholder('', nameAttr.value, el.sourceSpan);
|
||||
}
|
||||
|
||||
this._addError(el, `<${_PLACEHOLDER_TAG}> misses the "name" attribute`);
|
||||
} else {
|
||||
this._addError(el, `Unexpected tag`);
|
||||
}
|
||||
}
|
||||
|
||||
visitComment(comment: ml.Comment, context: any) {}
|
||||
|
||||
visitAttribute(attribute: ml.Attribute, context: any) {}
|
||||
|
||||
private _addError(node: ml.Node, message: string): void {
|
||||
this._errors.push(new I18nError(node.sourceSpan, message));
|
||||
|
@ -7,22 +7,120 @@
|
||||
*/
|
||||
|
||||
import * as html from '../ml_parser/ast';
|
||||
import {HtmlParser} from '../ml_parser/html_parser';
|
||||
|
||||
import {MessageBundle} from './message_bundle';
|
||||
import * as i18n from './i18n_ast';
|
||||
import {I18nError} from './parse_util';
|
||||
import {Serializer} from './serializers/serializer';
|
||||
|
||||
/**
|
||||
* A container for translated messages
|
||||
*/
|
||||
export class TranslationBundle {
|
||||
constructor(private _messageMap: {[id: string]: html.Node[]} = {}) {}
|
||||
private _i18nToHtml: I18nToHtmlVisitor;
|
||||
|
||||
static load(content: string, url: string, messageBundle: MessageBundle, serializer: Serializer):
|
||||
TranslationBundle {
|
||||
return new TranslationBundle(serializer.load(content, url, messageBundle));
|
||||
constructor(
|
||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
|
||||
public digest: (m: i18n.Message) => string) {
|
||||
this._i18nToHtml = new I18nToHtmlVisitor(_i18nNodesByMsgId, digest);
|
||||
}
|
||||
|
||||
get(id: string): html.Node[] { return this._messageMap[id]; }
|
||||
static load(content: string, url: string, serializer: Serializer): TranslationBundle {
|
||||
const i18nNodesByMsgId = serializer.load(content, url);
|
||||
const digestFn = (m: i18n.Message) => serializer.digest(m);
|
||||
return new TranslationBundle(i18nNodesByMsgId, digestFn);
|
||||
}
|
||||
|
||||
has(id: string): boolean { return id in this._messageMap; }
|
||||
get(srcMsg: i18n.Message): html.Node[] {
|
||||
const html = this._i18nToHtml.convert(srcMsg);
|
||||
|
||||
if (html.errors.length) {
|
||||
throw new Error(html.errors.join('\n'));
|
||||
}
|
||||
|
||||
return html.nodes;
|
||||
}
|
||||
|
||||
has(srcMsg: i18n.Message): boolean { return this.digest(srcMsg) in this._i18nNodesByMsgId; }
|
||||
}
|
||||
|
||||
class I18nToHtmlVisitor implements i18n.Visitor {
|
||||
private _srcMsg: i18n.Message;
|
||||
private _srcMsgStack: i18n.Message[] = [];
|
||||
private _errors: I18nError[] = [];
|
||||
|
||||
constructor(
|
||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
|
||||
private _digest: (m: i18n.Message) => string) {}
|
||||
|
||||
convert(srcMsg: i18n.Message): {nodes: html.Node[], errors: I18nError[]} {
|
||||
this._srcMsgStack.length = 0;
|
||||
this._errors.length = 0;
|
||||
// i18n to text
|
||||
const text = this._convertToText(srcMsg);
|
||||
|
||||
// text to html
|
||||
const url = srcMsg.nodes[0].sourceSpan.start.file.url;
|
||||
const html = new HtmlParser().parse(text, url, true);
|
||||
|
||||
return {
|
||||
nodes: html.rootNodes,
|
||||
errors: [...this._errors, ...html.errors],
|
||||
};
|
||||
}
|
||||
|
||||
visitText(text: i18n.Text, context?: any): string { return text.value; }
|
||||
|
||||
visitContainer(container: i18n.Container, context?: any): any {
|
||||
return container.children.map(n => n.visit(this)).join('');
|
||||
}
|
||||
|
||||
visitIcu(icu: i18n.Icu, context?: any): any {
|
||||
const cases = Object.keys(icu.cases).map(k => `${k} {${icu.cases[k].visit(this)}}`);
|
||||
|
||||
// TODO(vicb): Once all format switch to using expression placeholders
|
||||
// we should throw when the placeholder is not in the source message
|
||||
const exp = this._srcMsg.placeholders.hasOwnProperty(icu.expression) ?
|
||||
this._srcMsg.placeholders[icu.expression] :
|
||||
icu.expression;
|
||||
|
||||
return `{${exp}, ${icu.type}, ${cases.join(' ')}}`;
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, context?: any): string {
|
||||
const phName = ph.name;
|
||||
if (this._srcMsg.placeholders.hasOwnProperty(phName)) {
|
||||
return this._srcMsg.placeholders[phName];
|
||||
}
|
||||
|
||||
if (this._srcMsg.placeholderToMessage.hasOwnProperty(phName)) {
|
||||
return this._convertToText(this._srcMsg.placeholderToMessage[phName]);
|
||||
}
|
||||
|
||||
this._addError(ph, `Unknown placeholder`);
|
||||
return '';
|
||||
}
|
||||
|
||||
visitTagPlaceholder(ph: i18n.TagPlaceholder, context?: any): any { throw 'unreachable code'; }
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any { throw 'unreachable code'; }
|
||||
|
||||
private _convertToText(srcMsg: i18n.Message): string {
|
||||
const digest = this._digest(srcMsg);
|
||||
if (this._i18nNodesByMsgId.hasOwnProperty(digest)) {
|
||||
this._srcMsgStack.push(this._srcMsg);
|
||||
this._srcMsg = srcMsg;
|
||||
const nodes = this._i18nNodesByMsgId[digest];
|
||||
const text = nodes.map(node => node.visit(this)).join('');
|
||||
this._srcMsg = this._srcMsgStack.pop();
|
||||
return text;
|
||||
}
|
||||
|
||||
this._addError(srcMsg.nodes[0], `Missing translation for message ${digest}`);
|
||||
return '';
|
||||
}
|
||||
|
||||
private _addError(el: i18n.Node, msg: string) {
|
||||
this._errors.push(new I18nError(el.sourceSpan, msg));
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@
|
||||
|
||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, AnimationTransitionEvent, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {StaticSymbol, isStaticSymbol} from './aot/static_symbol';
|
||||
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
|
||||
import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AnimationTransition, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, ComponentRef_, DebugAppView, DebugContext, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewContainer, ViewType, balanceAnimationKeyframes, clearStyles, collectAndResolveStyles, devModeEqual, prepareFinalAnimationStyles, reflector, registerModuleFactory, renderStyles, view_utils} from './private_import_core';
|
||||
|
||||
@ -339,19 +340,21 @@ export class Identifiers {
|
||||
|
||||
export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string {
|
||||
if (path == null) {
|
||||
return `asset:@angular/lib/${pkg}/index`;
|
||||
return `@angular/${pkg}/index`;
|
||||
} else {
|
||||
return `asset:@angular/lib/${pkg}/src/${path}`;
|
||||
return `@angular/${pkg}/${type}/${path}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveIdentifier(identifier: IdentifierSpec) {
|
||||
return new CompileIdentifierMetadata({
|
||||
name: identifier.name,
|
||||
moduleUrl: identifier.moduleUrl,
|
||||
reference:
|
||||
reflector.resolveIdentifier(identifier.name, identifier.moduleUrl, identifier.runtime)
|
||||
});
|
||||
let moduleUrl = identifier.moduleUrl;
|
||||
const reference =
|
||||
reflector.resolveIdentifier(identifier.name, identifier.moduleUrl, identifier.runtime);
|
||||
if (isStaticSymbol(reference)) {
|
||||
moduleUrl = reference.filePath;
|
||||
}
|
||||
return new CompileIdentifierMetadata(
|
||||
{name: identifier.name, moduleUrl: moduleUrl, reference: reference});
|
||||
}
|
||||
|
||||
export function identifierToken(identifier: CompileIdentifierMetadata): CompileTokenMetadata {
|
||||
|
@ -8,22 +8,22 @@
|
||||
|
||||
import {Compiler, ComponentFactory, Injectable, Injector, ModuleWithComponentFactories, NgModuleFactory, SchemaMetadata, Type} from '@angular/core';
|
||||
|
||||
import {AnimationCompiler} from './animation/animation_compiler';
|
||||
import {AnimationParser} from './animation/animation_parser';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, ProviderMeta, createHostComponentMeta} from './compile_metadata';
|
||||
import {CompilerConfig} from './config';
|
||||
import {DirectiveNormalizer} from './directive_normalizer';
|
||||
import {DirectiveWrapperCompiler} from './directive_wrapper_compiler';
|
||||
import {stringify} from './facade/lang';
|
||||
import {CompileMetadataResolver} from './metadata_resolver';
|
||||
import {NgModuleCompiler} from './ng_module_compiler';
|
||||
import * as ir from './output/output_ast';
|
||||
import {interpretStatements} from './output/output_interpreter';
|
||||
import {jitStatements} from './output/output_jit';
|
||||
import {CompiledStylesheet, StyleCompiler} from './style_compiler';
|
||||
import {TemplateParser} from './template_parser/template_parser';
|
||||
import {SyncAsyncResult} from './util';
|
||||
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompiler} from './view_compiler/view_compiler';
|
||||
import {AnimationCompiler} from '../animation/animation_compiler';
|
||||
import {AnimationParser} from '../animation/animation_parser';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, ProviderMeta, createHostComponentMeta} from '../compile_metadata';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {DirectiveNormalizer} from '../directive_normalizer';
|
||||
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
|
||||
import {stringify} from '../facade/lang';
|
||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||
import {NgModuleCompiler} from '../ng_module_compiler';
|
||||
import * as ir from '../output/output_ast';
|
||||
import {interpretStatements} from '../output/output_interpreter';
|
||||
import {jitStatements} from '../output/output_jit';
|
||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||
import {TemplateParser} from '../template_parser/template_parser';
|
||||
import {SyncAsyncResult} from '../util';
|
||||
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompiler} from '../view_compiler/view_compiler';
|
||||
|
||||
|
||||
|
||||
@ -37,7 +37,7 @@ import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDepende
|
||||
* application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security).
|
||||
*/
|
||||
@Injectable()
|
||||
export class RuntimeCompiler implements Compiler {
|
||||
export class JitCompiler implements Compiler {
|
||||
private _compiledTemplateCache = new Map<Type<any>, CompiledTemplate>();
|
||||
private _compiledHostTemplateCache = new Map<Type<any>, CompiledTemplate>();
|
||||
private _compiledDirectiveWrapperCache = new Map<Type<any>, Type<any>>();
|
||||
@ -385,10 +385,10 @@ function assertComponent(meta: CompileDirectiveMetadata) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements `Compiler` by delegating to the RuntimeCompiler using a known module.
|
||||
* Implements `Compiler` by delegating to the JitCompiler using a known module.
|
||||
*/
|
||||
class ModuleBoundCompiler implements Compiler {
|
||||
constructor(private _delegate: RuntimeCompiler, private _ngModule: Type<any>) {}
|
||||
constructor(private _delegate: JitCompiler, private _ngModule: Type<any>) {}
|
||||
|
||||
get _injector(): Injector { return this._delegate.injector; }
|
||||
|
@ -8,28 +8,29 @@
|
||||
|
||||
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 {AnimationParser} from './animation/animation_parser';
|
||||
import {CompilerConfig} from './config';
|
||||
import {DirectiveNormalizer} from './directive_normalizer';
|
||||
import {DirectiveResolver} from './directive_resolver';
|
||||
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 {CompileMetadataResolver} from './metadata_resolver';
|
||||
import {HtmlParser} from './ml_parser/html_parser';
|
||||
import {NgModuleCompiler} from './ng_module_compiler';
|
||||
import {NgModuleResolver} from './ng_module_resolver';
|
||||
import {PipeResolver} from './pipe_resolver';
|
||||
import {Console, ReflectionCapabilities, Reflector, ReflectorReader, reflector} from './private_import_core';
|
||||
import {ResourceLoader} from './resource_loader';
|
||||
import {RuntimeCompiler} from './runtime_compiler';
|
||||
import {DomElementSchemaRegistry} from './schema/dom_element_schema_registry';
|
||||
import {ElementSchemaRegistry} from './schema/element_schema_registry';
|
||||
import {StyleCompiler} from './style_compiler';
|
||||
import {TemplateParser} from './template_parser/template_parser';
|
||||
import {DEFAULT_PACKAGE_URL_PROVIDER, UrlResolver} from './url_resolver';
|
||||
import {ViewCompiler} from './view_compiler/view_compiler';
|
||||
import {AnimationParser} from '../animation/animation_parser';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {DirectiveNormalizer} from '../directive_normalizer';
|
||||
import {DirectiveResolver} from '../directive_resolver';
|
||||
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 {CompileMetadataResolver} from '../metadata_resolver';
|
||||
import {HtmlParser} from '../ml_parser/html_parser';
|
||||
import {NgModuleCompiler} from '../ng_module_compiler';
|
||||
import {NgModuleResolver} from '../ng_module_resolver';
|
||||
import {PipeResolver} from '../pipe_resolver';
|
||||
import {Console, ReflectionCapabilities, Reflector, ReflectorReader, reflector} from '../private_import_core';
|
||||
import {ResourceLoader} from '../resource_loader';
|
||||
import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry';
|
||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||
import {StyleCompiler} from '../style_compiler';
|
||||
import {TemplateParser} from '../template_parser/template_parser';
|
||||
import {DEFAULT_PACKAGE_URL_PROVIDER, UrlResolver} from '../url_resolver';
|
||||
import {ViewCompiler} from '../view_compiler/view_compiler';
|
||||
|
||||
import {JitCompiler} from './compiler';
|
||||
|
||||
const _NO_RESOURCE_LOADER: ResourceLoader = {
|
||||
get(url: string): Promise<string>{
|
||||
@ -38,7 +39,7 @@ const _NO_RESOURCE_LOADER: ResourceLoader = {
|
||||
};
|
||||
|
||||
/**
|
||||
* A set of providers that provide `RuntimeCompiler` and its dependencies to use for
|
||||
* A set of providers that provide `JitCompiler` and its dependencies to use for
|
||||
* template compilation.
|
||||
*/
|
||||
export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> = [
|
||||
@ -68,8 +69,8 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
|
||||
NgModuleCompiler,
|
||||
DirectiveWrapperCompiler,
|
||||
{provide: CompilerConfig, useValue: new CompilerConfig()},
|
||||
RuntimeCompiler,
|
||||
{provide: Compiler, useExisting: RuntimeCompiler},
|
||||
JitCompiler,
|
||||
{provide: Compiler, useExisting: JitCompiler},
|
||||
DomElementSchemaRegistry,
|
||||
{provide: ElementSchemaRegistry, useExisting: DomElementSchemaRegistry},
|
||||
UrlResolver,
|
||||
@ -81,7 +82,7 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class RuntimeCompilerFactory implements CompilerFactory {
|
||||
export class JitCompilerFactory implements CompilerFactory {
|
||||
private _defaultOptions: CompilerOptions[];
|
||||
constructor(@Inject(COMPILER_OPTIONS) defaultOptions: CompilerOptions[]) {
|
||||
this._defaultOptions = [<CompilerOptions>{
|
||||
@ -128,7 +129,7 @@ function _initReflector() {
|
||||
*/
|
||||
export const platformCoreDynamic = createPlatformFactory(platformCore, 'coreDynamic', [
|
||||
{provide: COMPILER_OPTIONS, useValue: {}, multi: true},
|
||||
{provide: CompilerFactory, useClass: RuntimeCompilerFactory},
|
||||
{provide: CompilerFactory, useClass: JitCompilerFactory},
|
||||
{provide: PLATFORM_INITIALIZER, useValue: _initReflector, multi: true},
|
||||
]);
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationStateDeclarationMetadata, AnimationStateMetadata, AnimationStateTransitionMetadata, AnimationStyleMetadata, AnimationWithStepsMetadata, Attribute, ChangeDetectionStrategy, Component, Host, Inject, Injectable, ModuleWithProviders, Optional, Provider, Query, SchemaMetadata, Self, SkipSelf, Type, resolveForwardRef} from '@angular/core';
|
||||
|
||||
import {isStaticSymbol} from './aot/static_symbol';
|
||||
import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions';
|
||||
import * as cpl from './compile_metadata';
|
||||
import {DirectiveNormalizer} from './directive_normalizer';
|
||||
@ -146,97 +147,43 @@ export class CompileMetadataResolver {
|
||||
return;
|
||||
}
|
||||
directiveType = resolveForwardRef(directiveType);
|
||||
const dirMeta = this._directiveResolver.resolve(directiveType);
|
||||
if (!dirMeta) {
|
||||
return null;
|
||||
}
|
||||
let moduleUrl = staticTypeModuleUrl(directiveType);
|
||||
const nonNormalizedMetadata = this.getNonNormalizedDirectiveMetadata(directiveType);
|
||||
|
||||
const createDirectiveMetadata = (templateMeta: cpl.CompileTemplateMetadata) => {
|
||||
let changeDetectionStrategy: ChangeDetectionStrategy = null;
|
||||
let viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
||||
let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = [];
|
||||
let selector = dirMeta.selector;
|
||||
|
||||
if (dirMeta instanceof Component) {
|
||||
// Component
|
||||
changeDetectionStrategy = dirMeta.changeDetection;
|
||||
if (dirMeta.viewProviders) {
|
||||
viewProviders = this._getProvidersMetadata(
|
||||
dirMeta.viewProviders, entryComponentMetadata,
|
||||
`viewProviders for "${stringify(directiveType)}"`);
|
||||
}
|
||||
if (dirMeta.entryComponents) {
|
||||
entryComponentMetadata =
|
||||
flattenAndDedupeArray(dirMeta.entryComponents)
|
||||
.map((type) => this._getIdentifierMetadata(type, staticTypeModuleUrl(type)))
|
||||
.concat(entryComponentMetadata);
|
||||
}
|
||||
if (!selector) {
|
||||
selector = this._schemaRegistry.getDefaultComponentElementName();
|
||||
}
|
||||
} else {
|
||||
// Directive
|
||||
if (!selector) {
|
||||
throw new Error(`Directive ${stringify(directiveType)} has no selector, please add it!`);
|
||||
}
|
||||
}
|
||||
|
||||
let providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
||||
if (isPresent(dirMeta.providers)) {
|
||||
providers = this._getProvidersMetadata(
|
||||
dirMeta.providers, entryComponentMetadata,
|
||||
`providers for "${stringify(directiveType)}"`);
|
||||
}
|
||||
let queries: cpl.CompileQueryMetadata[] = [];
|
||||
let viewQueries: cpl.CompileQueryMetadata[] = [];
|
||||
if (isPresent(dirMeta.queries)) {
|
||||
queries = this._getQueriesMetadata(dirMeta.queries, false, directiveType);
|
||||
viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType);
|
||||
}
|
||||
|
||||
const meta = cpl.CompileDirectiveMetadata.create({
|
||||
selector: selector,
|
||||
exportAs: dirMeta.exportAs,
|
||||
isComponent: !!templateMeta,
|
||||
type: this._getTypeMetadata(directiveType, moduleUrl),
|
||||
template: templateMeta,
|
||||
changeDetection: changeDetectionStrategy,
|
||||
inputs: dirMeta.inputs,
|
||||
outputs: dirMeta.outputs,
|
||||
host: dirMeta.host,
|
||||
providers: providers,
|
||||
viewProviders: viewProviders,
|
||||
queries: queries,
|
||||
viewQueries: viewQueries,
|
||||
entryComponents: entryComponentMetadata
|
||||
const createDirectiveMetadata = (templateMetadata: cpl.CompileTemplateMetadata) => {
|
||||
const normalizedDirMeta = new cpl.CompileDirectiveMetadata({
|
||||
type: nonNormalizedMetadata.type,
|
||||
isComponent: nonNormalizedMetadata.isComponent,
|
||||
selector: nonNormalizedMetadata.selector,
|
||||
exportAs: nonNormalizedMetadata.exportAs,
|
||||
changeDetection: nonNormalizedMetadata.changeDetection,
|
||||
inputs: nonNormalizedMetadata.inputs,
|
||||
outputs: nonNormalizedMetadata.outputs,
|
||||
hostListeners: nonNormalizedMetadata.hostListeners,
|
||||
hostProperties: nonNormalizedMetadata.hostProperties,
|
||||
hostAttributes: nonNormalizedMetadata.hostAttributes,
|
||||
providers: nonNormalizedMetadata.providers,
|
||||
viewProviders: nonNormalizedMetadata.viewProviders,
|
||||
queries: nonNormalizedMetadata.queries,
|
||||
viewQueries: nonNormalizedMetadata.viewQueries,
|
||||
entryComponents: nonNormalizedMetadata.entryComponents,
|
||||
template: templateMetadata
|
||||
});
|
||||
this._directiveCache.set(directiveType, meta);
|
||||
this._directiveSummaryCache.set(directiveType, meta.toSummary());
|
||||
return meta;
|
||||
this._directiveCache.set(directiveType, normalizedDirMeta);
|
||||
this._directiveSummaryCache.set(directiveType, normalizedDirMeta.toSummary());
|
||||
return normalizedDirMeta;
|
||||
};
|
||||
|
||||
if (dirMeta instanceof Component) {
|
||||
// component
|
||||
moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta);
|
||||
assertArrayOfStrings('styles', dirMeta.styles);
|
||||
assertArrayOfStrings('styleUrls', dirMeta.styleUrls);
|
||||
assertInterpolationSymbols('interpolation', dirMeta.interpolation);
|
||||
|
||||
const animations = dirMeta.animations ?
|
||||
dirMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
|
||||
null;
|
||||
|
||||
if (nonNormalizedMetadata.isComponent) {
|
||||
const templateMeta = this._directiveNormalizer.normalizeTemplate({
|
||||
componentType: directiveType,
|
||||
moduleUrl: moduleUrl,
|
||||
encapsulation: dirMeta.encapsulation,
|
||||
template: dirMeta.template,
|
||||
templateUrl: dirMeta.templateUrl,
|
||||
styles: dirMeta.styles,
|
||||
styleUrls: dirMeta.styleUrls,
|
||||
animations: animations,
|
||||
interpolation: dirMeta.interpolation
|
||||
moduleUrl: nonNormalizedMetadata.type.moduleUrl,
|
||||
encapsulation: nonNormalizedMetadata.template.encapsulation,
|
||||
template: nonNormalizedMetadata.template.template,
|
||||
templateUrl: nonNormalizedMetadata.template.templateUrl,
|
||||
styles: nonNormalizedMetadata.template.styles,
|
||||
styleUrls: nonNormalizedMetadata.template.styleUrls,
|
||||
animations: nonNormalizedMetadata.template.animations,
|
||||
interpolation: nonNormalizedMetadata.template.interpolation
|
||||
});
|
||||
if (templateMeta.syncResult) {
|
||||
createDirectiveMetadata(templateMeta.syncResult);
|
||||
@ -254,6 +201,96 @@ export class CompileMetadataResolver {
|
||||
}
|
||||
}
|
||||
|
||||
getNonNormalizedDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
|
||||
directiveType = resolveForwardRef(directiveType);
|
||||
const dirMeta = this._directiveResolver.resolve(directiveType);
|
||||
if (!dirMeta) {
|
||||
return null;
|
||||
}
|
||||
let moduleUrl = staticTypeModuleUrl(directiveType);
|
||||
let nonNormalizedTemplateMetadata: cpl.CompileTemplateMetadata;
|
||||
|
||||
if (dirMeta instanceof Component) {
|
||||
// component
|
||||
moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta);
|
||||
assertArrayOfStrings('styles', dirMeta.styles);
|
||||
assertArrayOfStrings('styleUrls', dirMeta.styleUrls);
|
||||
assertInterpolationSymbols('interpolation', dirMeta.interpolation);
|
||||
|
||||
const animations = dirMeta.animations ?
|
||||
dirMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
|
||||
null;
|
||||
|
||||
nonNormalizedTemplateMetadata = new cpl.CompileTemplateMetadata({
|
||||
encapsulation: dirMeta.encapsulation,
|
||||
template: dirMeta.template,
|
||||
templateUrl: dirMeta.templateUrl,
|
||||
styles: dirMeta.styles,
|
||||
styleUrls: dirMeta.styleUrls,
|
||||
animations: animations,
|
||||
interpolation: dirMeta.interpolation
|
||||
});
|
||||
}
|
||||
|
||||
let changeDetectionStrategy: ChangeDetectionStrategy = null;
|
||||
let viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
||||
let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = [];
|
||||
let selector = dirMeta.selector;
|
||||
|
||||
if (dirMeta instanceof Component) {
|
||||
// Component
|
||||
changeDetectionStrategy = dirMeta.changeDetection;
|
||||
if (dirMeta.viewProviders) {
|
||||
viewProviders = this._getProvidersMetadata(
|
||||
dirMeta.viewProviders, entryComponentMetadata,
|
||||
`viewProviders for "${stringify(directiveType)}"`);
|
||||
}
|
||||
if (dirMeta.entryComponents) {
|
||||
entryComponentMetadata =
|
||||
flattenAndDedupeArray(dirMeta.entryComponents)
|
||||
.map((type) => this._getIdentifierMetadata(type, staticTypeModuleUrl(type)))
|
||||
.concat(entryComponentMetadata);
|
||||
}
|
||||
if (!selector) {
|
||||
selector = this._schemaRegistry.getDefaultComponentElementName();
|
||||
}
|
||||
} else {
|
||||
// Directive
|
||||
if (!selector) {
|
||||
throw new Error(`Directive ${stringify(directiveType)} has no selector, please add it!`);
|
||||
}
|
||||
}
|
||||
|
||||
let providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
||||
if (isPresent(dirMeta.providers)) {
|
||||
providers = this._getProvidersMetadata(
|
||||
dirMeta.providers, entryComponentMetadata, `providers for "${stringify(directiveType)}"`);
|
||||
}
|
||||
let queries: cpl.CompileQueryMetadata[] = [];
|
||||
let viewQueries: cpl.CompileQueryMetadata[] = [];
|
||||
if (isPresent(dirMeta.queries)) {
|
||||
queries = this._getQueriesMetadata(dirMeta.queries, false, directiveType);
|
||||
viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType);
|
||||
}
|
||||
|
||||
return cpl.CompileDirectiveMetadata.create({
|
||||
selector: selector,
|
||||
exportAs: dirMeta.exportAs,
|
||||
isComponent: !!nonNormalizedTemplateMetadata,
|
||||
type: this._getTypeMetadata(directiveType, moduleUrl),
|
||||
template: nonNormalizedTemplateMetadata,
|
||||
changeDetection: changeDetectionStrategy,
|
||||
inputs: dirMeta.inputs,
|
||||
outputs: dirMeta.outputs,
|
||||
host: dirMeta.host,
|
||||
providers: providers,
|
||||
viewProviders: viewProviders,
|
||||
queries: queries,
|
||||
viewQueries: viewQueries,
|
||||
entryComponents: entryComponentMetadata
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the metadata for the given directive.
|
||||
* This assumes `loadNgModuleMetadata` has been called first.
|
||||
@ -309,11 +346,20 @@ export class CompileMetadataResolver {
|
||||
loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
|
||||
{ngModule: cpl.CompileNgModuleMetadata, loading: Promise<any>} {
|
||||
const ngModule = this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound);
|
||||
const loading =
|
||||
ngModule ? Promise.all(ngModule.transitiveModule.loadingPromises) : Promise.resolve(null);
|
||||
const loading = ngModule ?
|
||||
Promise.all(ngModule.transitiveModule.directiveLoaders.map(loader => loader())) :
|
||||
Promise.resolve(null);
|
||||
return {ngModule, loading};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the NgModule metadata without loading the directives.
|
||||
*/
|
||||
getUnloadedNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
|
||||
cpl.CompileNgModuleMetadata {
|
||||
return this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound);
|
||||
}
|
||||
|
||||
private _loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
|
||||
cpl.CompileNgModuleMetadata {
|
||||
moduleType = resolveForwardRef(moduleType);
|
||||
@ -396,10 +442,8 @@ export class CompileMetadataResolver {
|
||||
transitiveModule.directives.push(declaredIdentifier);
|
||||
declaredDirectives.push(declaredIdentifier);
|
||||
this._addTypeToModule(declaredType, moduleType);
|
||||
const loadingPromise = this._loadDirectiveMetadata(declaredType, isSync);
|
||||
if (loadingPromise) {
|
||||
transitiveModule.loadingPromises.push(loadingPromise);
|
||||
}
|
||||
transitiveModule.directiveLoaders.push(
|
||||
() => this._loadDirectiveMetadata(declaredType, isSync));
|
||||
} else if (this._pipeResolver.isPipe(declaredType)) {
|
||||
transitiveModule.pipesSet.add(declaredType);
|
||||
transitiveModule.pipes.push(declaredIdentifier);
|
||||
@ -525,10 +569,10 @@ export class CompileMetadataResolver {
|
||||
const directives =
|
||||
flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedDirectives));
|
||||
const pipes = flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedPipes));
|
||||
const loadingPromises =
|
||||
ListWrapper.flatten(transitiveExportedModules.map(ngModule => ngModule.loadingPromises));
|
||||
const directiveLoaders =
|
||||
ListWrapper.flatten(transitiveExportedModules.map(ngModule => ngModule.directiveLoaders));
|
||||
return new cpl.TransitiveCompileNgModuleMetadata(
|
||||
transitiveModules, providers, entryComponents, directives, pipes, loadingPromises);
|
||||
transitiveModules, providers, entryComponents, directives, pipes, directiveLoaders);
|
||||
}
|
||||
|
||||
private _getIdentifierMetadata(type: Type<any>, moduleUrl: string):
|
||||
@ -584,20 +628,26 @@ export class CompileMetadataResolver {
|
||||
return pipeSummary;
|
||||
}
|
||||
|
||||
private _loadPipeMetadata(pipeType: Type<any>): void {
|
||||
pipeType = resolveForwardRef(pipeType);
|
||||
const pipeMeta = this._pipeResolver.resolve(pipeType);
|
||||
getOrLoadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
|
||||
let pipeMeta = this._pipeCache.get(pipeType);
|
||||
if (!pipeMeta) {
|
||||
return null;
|
||||
pipeMeta = this._loadPipeMetadata(pipeType);
|
||||
}
|
||||
return pipeMeta;
|
||||
}
|
||||
|
||||
const meta = new cpl.CompilePipeMetadata({
|
||||
private _loadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
|
||||
pipeType = resolveForwardRef(pipeType);
|
||||
const pipeAnnotation = this._pipeResolver.resolve(pipeType);
|
||||
|
||||
const pipeMeta = new cpl.CompilePipeMetadata({
|
||||
type: this._getTypeMetadata(pipeType, staticTypeModuleUrl(pipeType)),
|
||||
name: pipeMeta.name,
|
||||
pure: pipeMeta.pure
|
||||
name: pipeAnnotation.name,
|
||||
pure: pipeAnnotation.pure
|
||||
});
|
||||
this._pipeCache.set(pipeType, meta);
|
||||
this._pipeSummaryCache.set(pipeType, meta.toSummary());
|
||||
this._pipeCache.set(pipeType, pipeMeta);
|
||||
this._pipeSummaryCache.set(pipeType, pipeMeta.toSummary());
|
||||
return pipeMeta;
|
||||
}
|
||||
|
||||
private _getDependenciesMetadata(typeOrFunc: Type<any>|Function, dependencies: any[]):
|
||||
@ -872,16 +922,16 @@ function flattenAndDedupeArray(tree: any[]): Array<any> {
|
||||
}
|
||||
|
||||
function isValidType(value: any): boolean {
|
||||
return cpl.isStaticSymbol(value) || (value instanceof Type);
|
||||
return isStaticSymbol(value) || (value instanceof Type);
|
||||
}
|
||||
|
||||
function staticTypeModuleUrl(value: any): string {
|
||||
return cpl.isStaticSymbol(value) ? value.filePath : null;
|
||||
return isStaticSymbol(value) ? value.filePath : null;
|
||||
}
|
||||
|
||||
function componentModuleUrl(
|
||||
reflector: ReflectorReader, type: Type<any>, cmpMetadata: Component): string {
|
||||
if (cpl.isStaticSymbol(type)) {
|
||||
if (isStaticSymbol(type)) {
|
||||
return staticTypeModuleUrl(type);
|
||||
}
|
||||
|
||||
@ -907,7 +957,7 @@ function convertToCompileValue(
|
||||
class _CompileValueConverter extends ValueTransformer {
|
||||
visitOther(value: any, targetIdentifiers: cpl.CompileIdentifierMetadata[]): any {
|
||||
let identifier: cpl.CompileIdentifierMetadata;
|
||||
if (cpl.isStaticSymbol(value)) {
|
||||
if (isStaticSymbol(value)) {
|
||||
identifier = new cpl.CompileIdentifierMetadata(
|
||||
{name: value.name, moduleUrl: value.filePath, reference: value});
|
||||
} else {
|
||||
|
@ -12,10 +12,10 @@ import {isBlank, isPresent} from '../facade/lang';
|
||||
import {EmitterVisitorContext, OutputEmitter} from './abstract_emitter';
|
||||
import {AbstractJsEmitterVisitor} from './abstract_js_emitter';
|
||||
import * as o from './output_ast';
|
||||
import {ImportGenerator} from './path_util';
|
||||
import {ImportResolver} from './path_util';
|
||||
|
||||
export class JavaScriptEmitter implements OutputEmitter {
|
||||
constructor(private _importGenerator: ImportGenerator) {}
|
||||
constructor(private _importGenerator: ImportResolver) {}
|
||||
emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string {
|
||||
const converter = new JsEmitterVisitor(moduleUrl);
|
||||
const ctx = EmitterVisitorContext.createRoot(exportedVars);
|
||||
@ -25,7 +25,7 @@ export class JavaScriptEmitter implements OutputEmitter {
|
||||
// Note: can't write the real word for import as it screws up system.js auto detection...
|
||||
srcParts.push(
|
||||
`var ${prefix} = req` +
|
||||
`uire('${this._importGenerator.getImportPath(moduleUrl, importedModuleUrl)}');`);
|
||||
`uire('${this._importGenerator.fileNameToModuleName(importedModuleUrl, moduleUrl)}');`);
|
||||
});
|
||||
srcParts.push(ctx.toSource());
|
||||
return srcParts.join('\n');
|
||||
|
@ -6,30 +6,13 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
// asset:<package-name>/<realm>/<path-to-module>
|
||||
const _ASSET_URL_RE = /asset:([^\/]+)\/([^\/]+)\/(.+)/;
|
||||
|
||||
/**
|
||||
* Interface that defines how import statements should be generated.
|
||||
*/
|
||||
export abstract class ImportGenerator {
|
||||
static parseAssetUrl(url: string): AssetUrl { return AssetUrl.parse(url); }
|
||||
|
||||
abstract getImportPath(moduleUrlStr: string, importedUrlStr: string): string;
|
||||
}
|
||||
|
||||
export class AssetUrl {
|
||||
static parse(url: string, allowNonMatching: boolean = true): AssetUrl {
|
||||
const match = url.match(_ASSET_URL_RE);
|
||||
if (match !== null) {
|
||||
return new AssetUrl(match[1], match[2], match[3]);
|
||||
}
|
||||
if (allowNonMatching) {
|
||||
return null;
|
||||
}
|
||||
throw new Error(`Url ${url} is not a valid asset: url`);
|
||||
}
|
||||
|
||||
constructor(public packageName: string, public firstLevelDir: string, public modulePath: string) {
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -12,9 +12,9 @@ import {isBlank, isPresent} from '../facade/lang';
|
||||
|
||||
import {AbstractEmitterVisitor, CATCH_ERROR_VAR, CATCH_STACK_VAR, EmitterVisitorContext, OutputEmitter} from './abstract_emitter';
|
||||
import * as o from './output_ast';
|
||||
import {ImportGenerator} from './path_util';
|
||||
import {ImportResolver} from './path_util';
|
||||
|
||||
const _debugModuleUrl = 'asset://debug/lib';
|
||||
const _debugModuleUrl = '/debug/lib';
|
||||
|
||||
export function debugOutputAstAsTypeScript(ast: o.Statement | o.Expression | o.Type | any[]):
|
||||
string {
|
||||
@ -37,7 +37,7 @@ export function debugOutputAstAsTypeScript(ast: o.Statement | o.Expression | o.T
|
||||
}
|
||||
|
||||
export class TypeScriptEmitter implements OutputEmitter {
|
||||
constructor(private _importGenerator: ImportGenerator) {}
|
||||
constructor(private _importGenerator: ImportResolver) {}
|
||||
emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string {
|
||||
const converter = new _TsEmitterVisitor(moduleUrl);
|
||||
const ctx = EmitterVisitorContext.createRoot(exportedVars);
|
||||
@ -47,7 +47,7 @@ export class TypeScriptEmitter implements OutputEmitter {
|
||||
// Note: can't write the real word for import as it screws up system.js auto detection...
|
||||
srcParts.push(
|
||||
`imp` +
|
||||
`ort * as ${prefix} from '${this._importGenerator.getImportPath(moduleUrl, importedModuleUrl)}';`);
|
||||
`ort * as ${prefix} from '${this._importGenerator.fileNameToModuleName(importedModuleUrl, moduleUrl)}';`);
|
||||
});
|
||||
srcParts.push(ctx.toSource());
|
||||
return srcParts.join('\n');
|
||||
|
@ -73,3 +73,6 @@ export type ComponentStillLoadingError = typeof r._ComponentStillLoadingError;
|
||||
export const ComponentStillLoadingError: typeof r.ComponentStillLoadingError =
|
||||
r.ComponentStillLoadingError;
|
||||
export const AnimationTransition: typeof r.AnimationTransition = r.AnimationTransition;
|
||||
export type SetterFn = typeof r._SetterFn;
|
||||
export type GetterFn = typeof r._GetterFn;
|
||||
export type MethodFn = typeof r._MethodFn;
|
||||
|
@ -10,9 +10,6 @@ import {Inject, Injectable, PACKAGE_ROOT_URL} from '@angular/core';
|
||||
|
||||
import {isBlank, isPresent} from './facade/lang';
|
||||
|
||||
|
||||
const _ASSET_SCHEME = 'asset:';
|
||||
|
||||
/**
|
||||
* Create a {@link UrlResolver} with no package prefix.
|
||||
*/
|
||||
@ -21,7 +18,7 @@ export function createUrlResolverWithoutPackagePrefix(): UrlResolver {
|
||||
}
|
||||
|
||||
export function createOfflineCompileUrlResolver(): UrlResolver {
|
||||
return new UrlResolver(_ASSET_SCHEME);
|
||||
return new UrlResolver('.');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,14 +67,9 @@ export class UrlResolver {
|
||||
if (isPresent(prefix) && isPresent(resolvedParts) &&
|
||||
resolvedParts[_ComponentIndex.Scheme] == 'package') {
|
||||
let path = resolvedParts[_ComponentIndex.Path];
|
||||
if (this._packagePrefix === _ASSET_SCHEME) {
|
||||
const pathSegements = path.split(/\//);
|
||||
resolvedUrl = `asset:${pathSegements[0]}/lib/${pathSegements.slice(1).join('/')}`;
|
||||
} else {
|
||||
prefix = prefix.replace(/\/+$/, '');
|
||||
path = path.replace(/^\/+/, '');
|
||||
return `${prefix}/${path}`;
|
||||
}
|
||||
prefix = prefix.replace(/\/+$/, '');
|
||||
path = path.replace(/^\/+/, '');
|
||||
return `${prefix}/${path}`;
|
||||
}
|
||||
return resolvedUrl;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isBlank, isPrimitive, isStrictStringMap} from './facade/lang';
|
||||
import {isPrimitive, isStrictStringMap} from './facade/lang';
|
||||
|
||||
export const MODULE_SUFFIX = '';
|
||||
|
||||
@ -48,7 +48,7 @@ export function visitValue(value: any, visitor: ValueVisitor, context: any): any
|
||||
return visitor.visitStringMap(<{[key: string]: any}>value, context);
|
||||
}
|
||||
|
||||
if (isBlank(value) || isPrimitive(value)) {
|
||||
if (value == null || isPrimitive(value)) {
|
||||
return visitor.visitPrimitive(value, context);
|
||||
}
|
||||
|
||||
|
@ -6,8 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler-cli/src/static_reflector';
|
||||
import {HostListener, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
|
||||
import {StaticReflector, StaticReflectorHost, StaticSymbol} 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';
|
||||
|
||||
@ -22,7 +22,7 @@ describe('StaticReflector', () => {
|
||||
let reflector: StaticReflector;
|
||||
|
||||
beforeEach(() => {
|
||||
host = new MockReflectorHost();
|
||||
host = new MockStaticReflectorHost();
|
||||
reflector = new StaticReflector(host);
|
||||
});
|
||||
|
||||
@ -31,7 +31,7 @@ describe('StaticReflector', () => {
|
||||
}
|
||||
|
||||
it('should get annotations for NgFor', () => {
|
||||
const NgFor = host.findDeclaration('angular2/src/common/directives/ng_for', 'NgFor');
|
||||
const NgFor = reflector.findDeclaration('@angular/common/src/directives/ng_for', 'NgFor');
|
||||
const annotations = reflector.annotations(NgFor);
|
||||
expect(annotations.length).toEqual(1);
|
||||
const annotation = annotations[0];
|
||||
@ -40,15 +40,15 @@ describe('StaticReflector', () => {
|
||||
});
|
||||
|
||||
it('should get constructor for NgFor', () => {
|
||||
const NgFor = host.findDeclaration('angular2/src/common/directives/ng_for', 'NgFor');
|
||||
const ViewContainerRef =
|
||||
host.findDeclaration('angular2/src/core/linker/view_container_ref', 'ViewContainerRef');
|
||||
const NgFor = reflector.findDeclaration('@angular/common/src/directives/ng_for', 'NgFor');
|
||||
const ViewContainerRef = reflector.findDeclaration(
|
||||
'@angular/core/src/linker/view_container_ref', 'ViewContainerRef');
|
||||
const TemplateRef =
|
||||
host.findDeclaration('angular2/src/core/linker/template_ref', 'TemplateRef');
|
||||
const IterableDiffers = host.findDeclaration(
|
||||
'angular2/src/core/change_detection/differs/iterable_differs', 'IterableDiffers');
|
||||
const ChangeDetectorRef = host.findDeclaration(
|
||||
'angular2/src/core/change_detection/change_detector_ref', 'ChangeDetectorRef');
|
||||
reflector.findDeclaration('@angular/core/src/linker/template_ref', 'TemplateRef');
|
||||
const IterableDiffers = reflector.findDeclaration(
|
||||
'@angular/core/src/change_detection/differs/iterable_differs', 'IterableDiffers');
|
||||
const ChangeDetectorRef = reflector.findDeclaration(
|
||||
'@angular/core/src/change_detection/change_detector_ref', 'ChangeDetectorRef');
|
||||
|
||||
const parameters = reflector.parameters(NgFor);
|
||||
expect(parameters).toEqual([
|
||||
@ -58,7 +58,7 @@ describe('StaticReflector', () => {
|
||||
|
||||
it('should get annotations for HeroDetailComponent', () => {
|
||||
const HeroDetailComponent =
|
||||
host.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
|
||||
reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
|
||||
const annotations = reflector.annotations(HeroDetailComponent);
|
||||
expect(annotations.length).toEqual(1);
|
||||
const annotation = annotations[0];
|
||||
@ -73,41 +73,40 @@ describe('StaticReflector', () => {
|
||||
])]);
|
||||
});
|
||||
|
||||
it('should throw and exception for unsupported metadata versions', () => {
|
||||
const e = host.findDeclaration('src/version-error', 'e');
|
||||
expect(() => reflector.annotations(e))
|
||||
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 1'));
|
||||
'Metadata version mismatch for module /tmp/src/version-error.d.ts, found version 100, expected 2'));
|
||||
});
|
||||
|
||||
it('should get and empty annotation list for an unknown class', () => {
|
||||
const UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass');
|
||||
const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass');
|
||||
const annotations = reflector.annotations(UnknownClass);
|
||||
expect(annotations).toEqual([]);
|
||||
});
|
||||
|
||||
it('should get propMetadata for HeroDetailComponent', () => {
|
||||
const HeroDetailComponent =
|
||||
host.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
|
||||
reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent');
|
||||
const props = reflector.propMetadata(HeroDetailComponent);
|
||||
expect(props['hero']).toBeTruthy();
|
||||
expect(props['onMouseOver']).toEqual([new HostListener('mouseover', ['$event'])]);
|
||||
});
|
||||
|
||||
it('should get an empty object from propMetadata for an unknown class', () => {
|
||||
const UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass');
|
||||
const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass');
|
||||
const properties = reflector.propMetadata(UnknownClass);
|
||||
expect(properties).toEqual({});
|
||||
});
|
||||
|
||||
it('should get empty parameters list for an unknown class ', () => {
|
||||
const UnknownClass = host.findDeclaration('src/app/app.component', 'UnknownClass');
|
||||
const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass');
|
||||
const parameters = reflector.parameters(UnknownClass);
|
||||
expect(parameters).toEqual([]);
|
||||
});
|
||||
|
||||
it('should provide context for errors reported by the collector', () => {
|
||||
const SomeClass = host.findDeclaration('src/error-reporting', 'SomeClass');
|
||||
const SomeClass = reflector.findDeclaration('src/error-reporting', 'SomeClass');
|
||||
expect(() => reflector.annotations(SomeClass))
|
||||
.toThrow(new Error(
|
||||
'Error encountered resolving symbol values statically. A reasonable error message (position 13:34 in the original .ts file), resolving symbol ErrorSym in /tmp/src/error-references.d.ts, resolving symbol Link2 in /tmp/src/error-references.d.ts, resolving symbol Link1 in /tmp/src/error-references.d.ts, resolving symbol SomeClass in /tmp/src/error-reporting.d.ts, resolving symbol SomeClass in /tmp/src/error-reporting.d.ts'));
|
||||
@ -308,14 +307,14 @@ describe('StaticReflector', () => {
|
||||
expect(simplify(
|
||||
new StaticSymbol('/src/cases', ''),
|
||||
({__symbolic: 'reference', module: './extern', name: 'nonExisting'})))
|
||||
.toEqual(host.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'),
|
||||
({__symbolic: 'function', parameters: ['a'], value: []})))
|
||||
.toEqual(host.getStaticSymbol('/src/cases', 'myFunction'));
|
||||
.toEqual(reflector.getStaticSymbol('/src/cases', 'myFunction'));
|
||||
});
|
||||
|
||||
it('should simplify values initialized with a function call', () => {
|
||||
@ -343,13 +342,11 @@ describe('StaticReflector', () => {
|
||||
try {
|
||||
const metadata = host.getMetadataFor('/tmp/src/invalid-metadata.ts');
|
||||
expect(metadata).toBeDefined();
|
||||
if (!Array.isArray(metadata)) {
|
||||
const moduleMetadata: any = metadata['metadata'];
|
||||
expect(moduleMetadata).toBeDefined();
|
||||
const classData: any = moduleMetadata['InvalidMetadata'];
|
||||
expect(classData).toBeDefined();
|
||||
simplify(new StaticSymbol('/tmp/src/invalid-metadata.ts', ''), classData.decorators[0]);
|
||||
}
|
||||
const moduleMetadata: any = metadata[0]['metadata'];
|
||||
expect(moduleMetadata).toBeDefined();
|
||||
const classData: any = moduleMetadata['InvalidMetadata'];
|
||||
expect(classData).toBeDefined();
|
||||
simplify(new StaticSymbol('/tmp/src/invalid-metadata.ts', ''), classData.decorators[0]);
|
||||
} catch (e) {
|
||||
expect(e.fileName).toBe('/tmp/src/invalid-metadata.ts');
|
||||
threw = true;
|
||||
@ -377,7 +374,7 @@ describe('StaticReflector', () => {
|
||||
const metadata = reflector.getModuleMetadata('/tmp/src/custom-decorator-reference.ts');
|
||||
expect(metadata).toEqual({
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
version: 2,
|
||||
metadata: {
|
||||
Foo: {
|
||||
__symbolic: 'class',
|
||||
@ -406,28 +403,35 @@ describe('StaticReflector', () => {
|
||||
|
||||
it('should be able to get metadata for a class containing a custom decorator', () => {
|
||||
const props = reflector.propMetadata(
|
||||
host.getStaticSymbol('/tmp/src/custom-decorator-reference.ts', 'Foo'));
|
||||
reflector.getStaticSymbol('/tmp/src/custom-decorator-reference.ts', 'Foo'));
|
||||
expect(props).toEqual({foo: []});
|
||||
});
|
||||
|
||||
it('should read ctor parameters with forwardRef', () => {
|
||||
const src = '/tmp/src/forward-ref.ts';
|
||||
const dep = reflector.getStaticSymbol(src, 'Dep');
|
||||
const props = reflector.parameters(reflector.getStaticSymbol(src, 'Forward'));
|
||||
expect(props).toEqual([[dep, new Inject(dep)]]);
|
||||
});
|
||||
|
||||
it('should report an error for invalid function calls', () => {
|
||||
expect(
|
||||
() =>
|
||||
reflector.annotations(host.getStaticSymbol('/tmp/src/invalid-calls.ts', 'MyComponent')))
|
||||
() => reflector.annotations(
|
||||
reflector.getStaticSymbol('/tmp/src/invalid-calls.ts', 'MyComponent')))
|
||||
.toThrow(new Error(
|
||||
`Error encountered resolving symbol values statically. Calling function 'someFunction', function calls are not supported. Consider replacing the function or lambda with a reference to an exported function, resolving symbol MyComponent in /tmp/src/invalid-calls.ts, resolving symbol MyComponent in /tmp/src/invalid-calls.ts`));
|
||||
});
|
||||
|
||||
it('should be able to get metadata for a class containing a static method call', () => {
|
||||
const annotations = reflector.annotations(
|
||||
host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyComponent'));
|
||||
reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyComponent'));
|
||||
expect(annotations.length).toBe(1);
|
||||
expect(annotations[0].providers).toEqual({provider: 'a', useValue: 100});
|
||||
});
|
||||
|
||||
it('should be able to get metadata for a class containing a static field reference', () => {
|
||||
const annotations =
|
||||
reflector.annotations(host.getStaticSymbol('/tmp/src/static-field-reference.ts', 'Foo'));
|
||||
const annotations = reflector.annotations(
|
||||
reflector.getStaticSymbol('/tmp/src/static-field-reference.ts', 'Foo'));
|
||||
expect(annotations.length).toBe(1);
|
||||
expect(annotations[0].providers).toEqual([{provider: 'a', useValue: 'Some string'}]);
|
||||
});
|
||||
@ -435,7 +439,7 @@ describe('StaticReflector', () => {
|
||||
it('should be able to get the metadata for a class calling a method with a conditional expression',
|
||||
() => {
|
||||
const annotations = reflector.annotations(
|
||||
host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyCondComponent'));
|
||||
reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyCondComponent'));
|
||||
expect(annotations.length).toBe(1);
|
||||
expect(annotations[0].providers).toEqual([
|
||||
[{provider: 'a', useValue: '1'}], [{provider: 'a', useValue: '2'}]
|
||||
@ -445,50 +449,81 @@ describe('StaticReflector', () => {
|
||||
it('should be able to get the metadata for a class calling a method with default parameters',
|
||||
() => {
|
||||
const annotations = reflector.annotations(
|
||||
host.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyDefaultsComponent'));
|
||||
reflector.getStaticSymbol('/tmp/src/static-method-call.ts', 'MyDefaultsComponent'));
|
||||
expect(annotations.length).toBe(1);
|
||||
expect(annotations[0].providers).toEqual([['a', true, false]]);
|
||||
});
|
||||
|
||||
it('should be able to get metadata with a reference to a static method', () => {
|
||||
const annotations = reflector.annotations(
|
||||
host.getStaticSymbol('/tmp/src/static-method-ref.ts', 'MethodReference'));
|
||||
reflector.getStaticSymbol('/tmp/src/static-method-ref.ts', 'MethodReference'));
|
||||
expect(annotations.length).toBe(1);
|
||||
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');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
class MockReflectorHost implements StaticReflectorHost {
|
||||
private staticTypeCache = new Map<string, StaticSymbol>();
|
||||
class MockStaticReflectorHost implements StaticReflectorHost {
|
||||
private collector = new MetadataCollector();
|
||||
|
||||
constructor() {}
|
||||
|
||||
angularImportLocations() {
|
||||
return {
|
||||
coreDecorators: 'angular2/src/core/metadata',
|
||||
diDecorators: 'angular2/src/core/di/metadata',
|
||||
diMetadata: 'angular2/src/core/di/metadata',
|
||||
diOpaqueToken: 'angular2/src/core/di/opaque_token',
|
||||
animationMetadata: 'angular2/src/core/animation/metadata',
|
||||
provider: 'angular2/src/core/di/provider'
|
||||
};
|
||||
}
|
||||
|
||||
getCanonicalFileName(fileName: string): string { return fileName; }
|
||||
|
||||
getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
|
||||
const cacheKey = `${declarationFile}:${name}${members?'.'+members.join('.'):''}`;
|
||||
let result = this.staticTypeCache.get(cacheKey);
|
||||
if (!result) {
|
||||
result = new StaticSymbol(declarationFile, name, members);
|
||||
this.staticTypeCache.set(cacheKey, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// In tests, assume that symbols are not re-exported
|
||||
findDeclaration(modulePath: string, symbolName: string, containingFile?: string): StaticSymbol {
|
||||
moduleNameToFileName(modulePath: string, containingFile?: string): string {
|
||||
function splitPath(path: string): string[] { return path.split(/\/|\\/g); }
|
||||
|
||||
function resolvePath(pathParts: string[]): string {
|
||||
@ -522,32 +557,34 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
if (modulePath.indexOf('.') === 0) {
|
||||
const baseName = pathTo(containingFile, modulePath);
|
||||
const tsName = baseName + '.ts';
|
||||
if (this.getMetadataFor(tsName)) {
|
||||
return this.getStaticSymbol(tsName, symbolName);
|
||||
if (this._getMetadataFor(tsName)) {
|
||||
return tsName;
|
||||
}
|
||||
return this.getStaticSymbol(baseName + '.d.ts', symbolName);
|
||||
return baseName + '.d.ts';
|
||||
}
|
||||
return this.getStaticSymbol('/tmp/' + modulePath + '.d.ts', symbolName);
|
||||
return '/tmp/' + modulePath + '.d.ts';
|
||||
}
|
||||
|
||||
getMetadataFor(moduleId: string): any {
|
||||
getMetadataFor(moduleId: string): any { return this._getMetadataFor(moduleId); }
|
||||
|
||||
private _getMetadataFor(moduleId: string): any {
|
||||
const data: {[key: string]: any} = {
|
||||
'/tmp/angular2/src/common/forms-deprecated/directives.d.ts': [{
|
||||
'/tmp/@angular/common/src/forms-deprecated/directives.d.ts': [{
|
||||
'__symbolic': 'module',
|
||||
'version': 1,
|
||||
'version': 2,
|
||||
'metadata': {
|
||||
'FORM_DIRECTIVES': [
|
||||
{
|
||||
'__symbolic': 'reference',
|
||||
'name': 'NgFor',
|
||||
'module': 'angular2/src/common/directives/ng_for'
|
||||
'module': '@angular/common/src/directives/ng_for'
|
||||
}
|
||||
]
|
||||
}
|
||||
}],
|
||||
'/tmp/angular2/src/common/directives/ng_for.d.ts': {
|
||||
'/tmp/@angular/common/src/directives/ng_for.d.ts': {
|
||||
'__symbolic': 'module',
|
||||
'version': 1,
|
||||
'version': 2,
|
||||
'metadata': {
|
||||
'NgFor': {
|
||||
'__symbolic': 'class',
|
||||
@ -557,7 +594,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
'expression': {
|
||||
'__symbolic': 'reference',
|
||||
'name': 'Directive',
|
||||
'module': '../../core/metadata'
|
||||
'module': '@angular/core/src/metadata'
|
||||
},
|
||||
'arguments': [
|
||||
{
|
||||
@ -574,22 +611,22 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
'parameters': [
|
||||
{
|
||||
'__symbolic': 'reference',
|
||||
'module': '../../core/linker/view_container_ref',
|
||||
'module': '@angular/core/src/linker/view_container_ref',
|
||||
'name': 'ViewContainerRef'
|
||||
},
|
||||
{
|
||||
'__symbolic': 'reference',
|
||||
'module': '../../core/linker/template_ref',
|
||||
'module': '@angular/core/src/linker/template_ref',
|
||||
'name': 'TemplateRef'
|
||||
},
|
||||
{
|
||||
'__symbolic': 'reference',
|
||||
'module': '../../core/change_detection/differs/iterable_differs',
|
||||
'module': '@angular/core/src/change_detection/differs/iterable_differs',
|
||||
'name': 'IterableDiffers'
|
||||
},
|
||||
{
|
||||
'__symbolic': 'reference',
|
||||
'module': '../../core/change_detection/change_detector_ref',
|
||||
'module': '@angular/core/src/change_detection/change_detector_ref',
|
||||
'name': 'ChangeDetectorRef'
|
||||
}
|
||||
]
|
||||
@ -599,17 +636,17 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
}
|
||||
}
|
||||
},
|
||||
'/tmp/angular2/src/core/linker/view_container_ref.d.ts':
|
||||
{version: 1, 'metadata': {'ViewContainerRef': {'__symbolic': 'class'}}},
|
||||
'/tmp/angular2/src/core/linker/template_ref.d.ts':
|
||||
{version: 1, 'module': './template_ref', 'metadata': {'TemplateRef': {'__symbolic': 'class'}}},
|
||||
'/tmp/angular2/src/core/change_detection/differs/iterable_differs.d.ts':
|
||||
{version: 1, 'metadata': {'IterableDiffers': {'__symbolic': 'class'}}},
|
||||
'/tmp/angular2/src/core/change_detection/change_detector_ref.d.ts':
|
||||
{version: 1, 'metadata': {'ChangeDetectorRef': {'__symbolic': 'class'}}},
|
||||
'/tmp/@angular/core/src/linker/view_container_ref.d.ts':
|
||||
{version: 2, 'metadata': {'ViewContainerRef': {'__symbolic': 'class'}}},
|
||||
'/tmp/@angular/core/src/linker/template_ref.d.ts':
|
||||
{version: 2, 'module': './template_ref', 'metadata': {'TemplateRef': {'__symbolic': 'class'}}},
|
||||
'/tmp/@angular/core/src/change_detection/differs/iterable_differs.d.ts':
|
||||
{version: 2, 'metadata': {'IterableDiffers': {'__symbolic': 'class'}}},
|
||||
'/tmp/@angular/core/src/change_detection/change_detector_ref.d.ts':
|
||||
{version: 2, 'metadata': {'ChangeDetectorRef': {'__symbolic': 'class'}}},
|
||||
'/tmp/src/app/hero-detail.component.d.ts': {
|
||||
'__symbolic': 'module',
|
||||
'version': 1,
|
||||
'version': 2,
|
||||
'metadata': {
|
||||
'HeroDetailComponent': {
|
||||
'__symbolic': 'class',
|
||||
@ -619,7 +656,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
'expression': {
|
||||
'__symbolic': 'reference',
|
||||
'name': 'Component',
|
||||
'module': 'angular2/src/core/metadata'
|
||||
'module': '@angular/core/src/metadata'
|
||||
},
|
||||
'arguments': [
|
||||
{
|
||||
@ -631,7 +668,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
'expression': {
|
||||
'__symbolic': 'reference',
|
||||
'name': 'trigger',
|
||||
'module': 'angular2/src/core/animation/metadata'
|
||||
'module': '@angular/core/src/animation/metadata'
|
||||
},
|
||||
'arguments': [
|
||||
'myAnimation',
|
||||
@ -639,7 +676,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
'expression': {
|
||||
'__symbolic': 'reference',
|
||||
'name': 'state',
|
||||
'module': 'angular2/src/core/animation/metadata'
|
||||
'module': '@angular/core/src/animation/metadata'
|
||||
},
|
||||
'arguments': [
|
||||
'state1',
|
||||
@ -647,7 +684,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
'expression': {
|
||||
'__symbolic': 'reference',
|
||||
'name': 'style',
|
||||
'module': 'angular2/src/core/animation/metadata'
|
||||
'module': '@angular/core/src/animation/metadata'
|
||||
},
|
||||
'arguments': [
|
||||
{ 'background':'white' }
|
||||
@ -659,7 +696,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
'expression': {
|
||||
'__symbolic':'reference',
|
||||
'name':'transition',
|
||||
'module': 'angular2/src/core/animation/metadata'
|
||||
'module': '@angular/core/src/animation/metadata'
|
||||
},
|
||||
'arguments': [
|
||||
'* => *',
|
||||
@ -668,20 +705,20 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
'expression':{
|
||||
'__symbolic':'reference',
|
||||
'name':'sequence',
|
||||
'module': 'angular2/src/core/animation/metadata'
|
||||
'module': '@angular/core/src/animation/metadata'
|
||||
},
|
||||
'arguments':[[{ '__symbolic': 'call',
|
||||
'expression': {
|
||||
'__symbolic':'reference',
|
||||
'name':'group',
|
||||
'module': 'angular2/src/core/animation/metadata'
|
||||
'module': '@angular/core/src/animation/metadata'
|
||||
},
|
||||
'arguments':[[{
|
||||
'__symbolic': 'call',
|
||||
'expression': {
|
||||
'__symbolic':'reference',
|
||||
'name':'animate',
|
||||
'module': 'angular2/src/core/animation/metadata'
|
||||
'module': '@angular/core/src/animation/metadata'
|
||||
},
|
||||
'arguments':[
|
||||
'1s 0.5s',
|
||||
@ -689,13 +726,13 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
'expression': {
|
||||
'__symbolic':'reference',
|
||||
'name':'keyframes',
|
||||
'module': 'angular2/src/core/animation/metadata'
|
||||
'module': '@angular/core/src/animation/metadata'
|
||||
},
|
||||
'arguments':[[{ '__symbolic': 'call',
|
||||
'expression': {
|
||||
'__symbolic':'reference',
|
||||
'name':'style',
|
||||
'module': 'angular2/src/core/animation/metadata'
|
||||
'module': '@angular/core/src/animation/metadata'
|
||||
},
|
||||
'arguments':[ { 'background': 'blue'} ]
|
||||
}, {
|
||||
@ -703,7 +740,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
'expression': {
|
||||
'__symbolic':'reference',
|
||||
'name':'style',
|
||||
'module': 'angular2/src/core/animation/metadata'
|
||||
'module': '@angular/core/src/animation/metadata'
|
||||
},
|
||||
'arguments':[ { 'background': 'red'} ]
|
||||
}]]
|
||||
@ -729,7 +766,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
'expression': {
|
||||
'__symbolic': 'reference',
|
||||
'name': 'Input',
|
||||
'module': 'angular2/src/core/metadata'
|
||||
'module': '@angular/core/src/metadata'
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -743,7 +780,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
'__symbolic': 'call',
|
||||
'expression': {
|
||||
'__symbolic': 'reference',
|
||||
'module': 'angular2/src/core/metadata',
|
||||
'module': '@angular/core/src/metadata',
|
||||
'name': 'HostListener'
|
||||
},
|
||||
'arguments': [
|
||||
@ -760,11 +797,11 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
}
|
||||
}
|
||||
},
|
||||
'/src/extern.d.ts': {'__symbolic': 'module', 'version': 1, metadata: {s: 's'}},
|
||||
'/src/extern.d.ts': {'__symbolic': 'module', 'version': 2, metadata: {s: 's'}},
|
||||
'/tmp/src/version-error.d.ts': {'__symbolic': 'module', 'version': 100, metadata: {e: 's'}},
|
||||
'/tmp/src/error-reporting.d.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
version: 2,
|
||||
metadata: {
|
||||
SomeClass: {
|
||||
__symbolic: 'class',
|
||||
@ -774,7 +811,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
expression: {
|
||||
__symbolic: 'reference',
|
||||
name: 'Component',
|
||||
module: 'angular2/src/core/metadata'
|
||||
module: '@angular/core/src/metadata'
|
||||
},
|
||||
arguments: [
|
||||
{
|
||||
@ -794,7 +831,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
},
|
||||
'/tmp/src/error-references.d.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
version: 2,
|
||||
metadata: {
|
||||
Link1: {
|
||||
__symbolic: 'reference',
|
||||
@ -816,7 +853,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
},
|
||||
'/tmp/src/function-declaration.d.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
version: 2,
|
||||
metadata: {
|
||||
one: {
|
||||
__symbolic: 'function',
|
||||
@ -845,7 +882,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
},
|
||||
'/tmp/src/function-reference.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
version: 2,
|
||||
metadata: {
|
||||
one: {
|
||||
__symbolic: 'call',
|
||||
@ -887,7 +924,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
},
|
||||
'/tmp/src/function-recursive.d.ts': {
|
||||
__symbolic: 'modules',
|
||||
version: 1,
|
||||
version: 2,
|
||||
metadata: {
|
||||
recursive: {
|
||||
__symbolic: 'function',
|
||||
@ -947,7 +984,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
},
|
||||
'/tmp/src/spread.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 1,
|
||||
version: 2,
|
||||
metadata: {
|
||||
spread: [0, {__symbolic: 'spread', expression: [1, 2, 3, 4]}, 5]
|
||||
}
|
||||
@ -975,8 +1012,8 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
`,
|
||||
'/tmp/src/invalid-calls.ts': `
|
||||
import {someFunction} from './nvalid-calll-definitions.ts';
|
||||
import {Component} from 'angular2/src/core/metadata';
|
||||
import {NgIf} from 'angular2/common';
|
||||
import {Component} from '@angular/core/src/metadata';
|
||||
import {NgIf} from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
@ -992,7 +1029,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
export class MyOtherComponent { }
|
||||
`,
|
||||
'/tmp/src/static-method.ts': `
|
||||
import {Component} from 'angular2/src/core/metadata';
|
||||
import {Component} from '@angular/core/src/metadata';
|
||||
|
||||
@Component({
|
||||
selector: 'stub'
|
||||
@ -1010,7 +1047,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
}
|
||||
`,
|
||||
'/tmp/src/static-method-call.ts': `
|
||||
import {Component} from 'angular2/src/core/metadata';
|
||||
import {Component} from '@angular/core/src/metadata';
|
||||
import {MyModule} from './static-method';
|
||||
|
||||
@Component({
|
||||
@ -1029,7 +1066,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
export class MyDefaultsComponent { }
|
||||
`,
|
||||
'/tmp/src/static-field.ts': `
|
||||
import {Injectable} from 'angular2/core';
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class MyModule {
|
||||
@ -1037,7 +1074,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
}
|
||||
`,
|
||||
'/tmp/src/static-field-reference.ts': `
|
||||
import {Component} from 'angular2/src/core/metadata';
|
||||
import {Component} from '@angular/core/src/metadata';
|
||||
import {MyModule} from './static-field';
|
||||
|
||||
@Component({
|
||||
@ -1051,7 +1088,7 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
}
|
||||
`,
|
||||
'/tmp/src/static-method-ref.ts': `
|
||||
import {Component} from 'angular2/src/core/metadata';
|
||||
import {Component} from '@angular/core/src/metadata';
|
||||
import {ClassWithStatics} from './static-method-def';
|
||||
|
||||
@Component({
|
||||
@ -1062,13 +1099,68 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
}
|
||||
`,
|
||||
'/tmp/src/invalid-metadata.ts': `
|
||||
import {Component} from 'angular2/src/core/metadata';
|
||||
import {Component} from '@angular/core/src/metadata';
|
||||
|
||||
@Component({
|
||||
providers: [ { provider: 'a', useValue: (() => 1)() }]
|
||||
})
|
||||
export class InvalidMetadata {}
|
||||
`
|
||||
`,
|
||||
'/tmp/src/forward-ref.ts': `
|
||||
import {forwardRef} from '@angular/core';
|
||||
import {Component} from '@angular/core/src/metadata';
|
||||
import {Inject} from '@angular/core/src/di/metadata';
|
||||
@Component({})
|
||||
export class Forward {
|
||||
constructor(@Inject(forwardRef(() => Dep)) d: Dep) {}
|
||||
}
|
||||
export class Dep {
|
||||
@Input f: Forward;
|
||||
}
|
||||
`,
|
||||
'/tmp/src/reexport/reexport.d.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
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: 2,
|
||||
metadata: {
|
||||
One: {__symbolic: 'class'},
|
||||
Two: {__symbolic: 'class'},
|
||||
Three: {__symbolic: 'class'},
|
||||
},
|
||||
},
|
||||
'/tmp/src/reexport/src/origin5.d.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
metadata: {
|
||||
Five: {__symbolic: 'class'},
|
||||
},
|
||||
},
|
||||
'/tmp/src/reexport/src/origin30.d.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
metadata: {
|
||||
Thirty: {__symbolic: 'class'},
|
||||
},
|
||||
},
|
||||
'/tmp/src/reexport/src/originNone.d.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
metadata: {},
|
||||
},
|
||||
'/tmp/src/reexport/src/reexport2.d.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
metadata: {},
|
||||
exports: [{from: './originNone'}, {from: './origin30'}]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1081,9 +1173,14 @@ class MockReflectorHost implements StaticReflectorHost {
|
||||
if (diagnostics && diagnostics.length) {
|
||||
throw Error(`Error encountered during parse of file ${moduleId}`);
|
||||
}
|
||||
return this.collector.getMetadata(sf);
|
||||
return [this.collector.getMetadata(sf)];
|
||||
}
|
||||
}
|
||||
return data[moduleId];
|
||||
const result = data[moduleId];
|
||||
if (result) {
|
||||
return Array.isArray(result) ? result : [result];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,53 +6,101 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {sha1} from '../../src/i18n/digest';
|
||||
import {computeMsgId, sha1} from '../../src/i18n/digest';
|
||||
|
||||
export function main(): void {
|
||||
describe('sha1', () => {
|
||||
it('should work on emnpty strings',
|
||||
() => { expect(sha1('')).toEqual('da39a3ee5e6b4b0d3255bfef95601890afd80709'); });
|
||||
describe('digest', () => {
|
||||
describe('sha1', () => {
|
||||
it('should work on empty strings',
|
||||
() => { expect(sha1('')).toEqual('da39a3ee5e6b4b0d3255bfef95601890afd80709'); });
|
||||
|
||||
it('should returns the sha1 of "hello world"',
|
||||
() => { expect(sha1('abc')).toEqual('a9993e364706816aba3e25717850c26c9cd0d89d'); });
|
||||
it('should returns the sha1 of "hello world"',
|
||||
() => { expect(sha1('abc')).toEqual('a9993e364706816aba3e25717850c26c9cd0d89d'); });
|
||||
|
||||
it('should returns the sha1 of unicode strings',
|
||||
() => { expect(sha1('你好,世界')).toEqual('3becb03b015ed48050611c8d7afe4b88f70d5a20'); });
|
||||
it('should returns the sha1 of unicode strings',
|
||||
() => { expect(sha1('你好,世界')).toEqual('3becb03b015ed48050611c8d7afe4b88f70d5a20'); });
|
||||
|
||||
it('should support arbitrary string size', () => {
|
||||
// node.js reference code:
|
||||
//
|
||||
// var crypto = require('crypto');
|
||||
//
|
||||
// function sha1(string) {
|
||||
// var shasum = crypto.createHash('sha1');
|
||||
// shasum.update(string, 'utf8');
|
||||
// return shasum.digest('hex', 'utf8');
|
||||
// }
|
||||
//
|
||||
// var prefix = `你好,世界`;
|
||||
// var result = sha1(prefix);
|
||||
// for (var size = prefix.length; size < 5000; size += 101) {
|
||||
// result = prefix + sha1(result);
|
||||
// while (result.length < size) {
|
||||
// result += result;
|
||||
// }
|
||||
// result = result.slice(-size);
|
||||
// }
|
||||
//
|
||||
// console.log(sha1(result));
|
||||
const prefix = `你好,世界`;
|
||||
let result = sha1(prefix);
|
||||
for (let size = prefix.length; size < 5000; size += 101) {
|
||||
result = prefix + sha1(result);
|
||||
while (result.length < size) {
|
||||
result += result;
|
||||
it('should support arbitrary string size', () => {
|
||||
// node.js reference code:
|
||||
//
|
||||
// var crypto = require('crypto');
|
||||
//
|
||||
// function sha1(string) {
|
||||
// var shasum = crypto.createHash('sha1');
|
||||
// shasum.update(string, 'utf8');
|
||||
// return shasum.digest('hex', 'utf8');
|
||||
// }
|
||||
//
|
||||
// var prefix = `你好,世界`;
|
||||
// var result = sha1(prefix);
|
||||
// for (var size = prefix.length; size < 5000; size += 101) {
|
||||
// result = prefix + sha1(result);
|
||||
// while (result.length < size) {
|
||||
// result += result;
|
||||
// }
|
||||
// result = result.slice(-size);
|
||||
// }
|
||||
//
|
||||
// console.log(sha1(result));
|
||||
const prefix = `你好,世界`;
|
||||
let result = sha1(prefix);
|
||||
for (let size = prefix.length; size < 5000; size += 101) {
|
||||
result = prefix + sha1(result);
|
||||
while (result.length < size) {
|
||||
result += result;
|
||||
}
|
||||
result = result.slice(-size);
|
||||
}
|
||||
result = result.slice(-size);
|
||||
}
|
||||
expect(sha1(result)).toEqual('24c2dae5c1ac6f604dbe670a60290d7ce6320b45');
|
||||
expect(sha1(result)).toEqual('24c2dae5c1ac6f604dbe670a60290d7ce6320b45');
|
||||
});
|
||||
});
|
||||
|
||||
describe('decimal fingerprint', () => {
|
||||
it('should work on well known inputs w/o meaning', () => {
|
||||
const fixtures: {[msg: string]: string} = {
|
||||
' Spaced Out ': '3976450302996657536',
|
||||
'Last Name': '4407559560004943843',
|
||||
'First Name': '6028371114637047813',
|
||||
'View': '2509141182388535183',
|
||||
'START_BOLDNUMEND_BOLD of START_BOLDmillionsEND_BOLD': '29997634073898638',
|
||||
'The customer\'s credit card was authorized for AMOUNT and passed all risk checks.':
|
||||
'6836487644149622036',
|
||||
'Hello world!': '3022994926184248873',
|
||||
'Jalape\u00f1o': '8054366208386598941',
|
||||
'The set of SET_NAME is {XXX, ...}.': '135956960462609535',
|
||||
'NAME took a trip to DESTINATION.': '768490705511913603',
|
||||
'by AUTHOR (YEAR)': '7036633296476174078',
|
||||
'': '4416290763660062288',
|
||||
};
|
||||
|
||||
Object.keys(fixtures).forEach(
|
||||
msg => { expect(computeMsgId(msg, '')).toEqual(fixtures[msg]); });
|
||||
});
|
||||
|
||||
it('should work on well known inputs with meaning', () => {
|
||||
const fixtures: {[msg: string]: [string, string]} = {
|
||||
'7790835225175622807': ['Last Name', 'Gmail UI'],
|
||||
'1809086297585054940': ['First Name', 'Gmail UI'],
|
||||
'3993998469942805487': ['View', 'Gmail UI'],
|
||||
};
|
||||
|
||||
Object.keys(fixtures).forEach(
|
||||
id => { expect(computeMsgId(fixtures[id][0], fixtures[id][1])).toEqual(id); });
|
||||
});
|
||||
|
||||
it('should support arbitrary string size', () => {
|
||||
const prefix = `你好,世界`;
|
||||
let result = computeMsgId(prefix, '');
|
||||
for (let size = prefix.length; size < 5000; size += 101) {
|
||||
result = prefix + computeMsgId(result, '');
|
||||
while (result.length < size) {
|
||||
result += result;
|
||||
}
|
||||
result = result.slice(-size);
|
||||
}
|
||||
expect(computeMsgId(result, '')).toEqual('2122606631351252558');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -6,15 +6,13 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, HtmlParser} from '@angular/compiler';
|
||||
|
||||
import {digestMessage, serializeNodes as serializeI18nNodes} from '../../src/i18n/digest';
|
||||
import {digest, serializeNodes as serializeI18nNodes} from '../../src/i18n/digest';
|
||||
import {extractMessages, mergeTranslations} from '../../src/i18n/extractor_merger';
|
||||
import * as i18n from '../../src/i18n/i18n_ast';
|
||||
import {TranslationBundle} from '../../src/i18n/translation_bundle';
|
||||
import * as html from '../../src/ml_parser/ast';
|
||||
import {HtmlParser} from '../../src/ml_parser/html_parser';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG} from '../../src/ml_parser/interpolation_config';
|
||||
import {serializeNodes as serializeHtmlNodes} from '../ml_parser/ast_serializer_spec';
|
||||
|
||||
export function main() {
|
||||
@ -94,9 +92,10 @@ export function main() {
|
||||
],
|
||||
[
|
||||
[
|
||||
'text',
|
||||
'<ph tag name="START_PARAGRAPH">html, <ph tag name="START_BOLD_TEXT">nested</ph name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">',
|
||||
'<ph icu name="ICU">{count, plural, =0 {[<ph tag name="START_TAG_SPAN">html</ph name="CLOSE_TAG_SPAN">]}}</ph>',
|
||||
'text', '<ph tag name="START_PARAGRAPH">html, <ph tag' +
|
||||
' name="START_BOLD_TEXT">nested</ph name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">',
|
||||
'<ph icu name="ICU">{count, plural, =0 {[<ph tag' +
|
||||
' name="START_TAG_SPAN">html</ph name="CLOSE_TAG_SPAN">]}}</ph>',
|
||||
'[<ph name="INTERPOLATION">interp</ph>]'
|
||||
],
|
||||
'', ''
|
||||
@ -190,9 +189,8 @@ export function main() {
|
||||
it('should extract from attributes in translatable elements', () => {
|
||||
expect(extract('<div i18n><p><b i18n-title="m|d" title="msg"></b></p></div>')).toEqual([
|
||||
[
|
||||
[
|
||||
'<ph tag name="START_PARAGRAPH"><ph tag name="START_BOLD_TEXT"></ph name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">'
|
||||
],
|
||||
['<ph tag name="START_PARAGRAPH"><ph tag name="START_BOLD_TEXT"></ph' +
|
||||
' name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">'],
|
||||
'', ''
|
||||
],
|
||||
[['msg'], 'm', 'd'],
|
||||
@ -204,9 +202,8 @@ export function main() {
|
||||
.toEqual([
|
||||
[['msg'], 'm', 'd'],
|
||||
[
|
||||
[
|
||||
'<ph tag name="START_PARAGRAPH"><ph tag name="START_BOLD_TEXT"></ph name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">'
|
||||
],
|
||||
['<ph tag name="START_PARAGRAPH"><ph tag name="START_BOLD_TEXT"></ph' +
|
||||
' name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">'],
|
||||
'', ''
|
||||
],
|
||||
]);
|
||||
@ -220,7 +217,8 @@ export function main() {
|
||||
[['msg'], 'm', 'd'],
|
||||
[
|
||||
[
|
||||
'{count, plural, =0 {[<ph tag name="START_PARAGRAPH"><ph tag name="START_BOLD_TEXT"></ph name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">]}}'
|
||||
'{count, plural, =0 {[<ph tag name="START_PARAGRAPH"><ph tag' +
|
||||
' name="START_BOLD_TEXT"></ph name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">]}}'
|
||||
],
|
||||
'', ''
|
||||
],
|
||||
@ -351,7 +349,9 @@ export function main() {
|
||||
const HTML = `before<!-- i18n --><p>foo</p><span><i>bar</i></span><!-- /i18n -->after`;
|
||||
expect(fakeTranslate(HTML))
|
||||
.toEqual(
|
||||
'before**<ph tag name="START_PARAGRAPH">foo</ph name="CLOSE_PARAGRAPH"><ph tag name="START_TAG_SPAN"><ph tag name="START_ITALIC_TEXT">bar</ph name="CLOSE_ITALIC_TEXT"></ph name="CLOSE_TAG_SPAN">**after');
|
||||
'before**[ph tag name="START_PARAGRAPH">foo[/ph name="CLOSE_PARAGRAPH">[ph tag' +
|
||||
' name="START_TAG_SPAN">[ph tag name="START_ITALIC_TEXT">bar[/ph' +
|
||||
' name="CLOSE_ITALIC_TEXT">[/ph name="CLOSE_TAG_SPAN">**after');
|
||||
});
|
||||
|
||||
it('should merge nested blocks', () => {
|
||||
@ -359,7 +359,9 @@ export function main() {
|
||||
`<div>before<!-- i18n --><p>foo</p><span><i>bar</i></span><!-- /i18n -->after</div>`;
|
||||
expect(fakeTranslate(HTML))
|
||||
.toEqual(
|
||||
'<div>before**<ph tag name="START_PARAGRAPH">foo</ph name="CLOSE_PARAGRAPH"><ph tag name="START_TAG_SPAN"><ph tag name="START_ITALIC_TEXT">bar</ph name="CLOSE_ITALIC_TEXT"></ph name="CLOSE_TAG_SPAN">**after</div>');
|
||||
'<div>before**[ph tag name="START_PARAGRAPH">foo[/ph name="CLOSE_PARAGRAPH">[ph' +
|
||||
' tag name="START_TAG_SPAN">[ph tag name="START_ITALIC_TEXT">bar[/ph' +
|
||||
' name="CLOSE_ITALIC_TEXT">[/ph name="CLOSE_TAG_SPAN">**after</div>');
|
||||
});
|
||||
});
|
||||
|
||||
@ -400,15 +402,15 @@ function fakeTranslate(
|
||||
extractMessages(htmlNodes, DEFAULT_INTERPOLATION_CONFIG, implicitTags, implicitAttrs)
|
||||
.messages;
|
||||
|
||||
const i18nMsgMap: {[id: string]: html.Node[]} = {};
|
||||
const i18nMsgMap: {[id: string]: i18n.Node[]} = {};
|
||||
|
||||
messages.forEach(message => {
|
||||
const id = digestMessage(message);
|
||||
const text = serializeI18nNodes(message.nodes).join('');
|
||||
i18nMsgMap[id] = [new html.Text(`**${text}**`, null)];
|
||||
const id = digest(message);
|
||||
const text = serializeI18nNodes(message.nodes).join('').replace(/</g, '[');
|
||||
i18nMsgMap[id] = [new i18n.Text(`**${text}**`, null)];
|
||||
});
|
||||
|
||||
const translations = new TranslationBundle(i18nMsgMap);
|
||||
const translations = new TranslationBundle(i18nMsgMap, digest);
|
||||
|
||||
const translatedNodes =
|
||||
mergeTranslations(
|
||||
|
@ -6,9 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {digest} from '@angular/compiler/src/i18n/digest';
|
||||
import {extractMessages} from '@angular/compiler/src/i18n/extractor_merger';
|
||||
import {Message} from '@angular/compiler/src/i18n/i18n_ast';
|
||||
import {describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {serializeNodes} from '../../src/i18n/digest';
|
||||
import {HtmlParser} from '../../src/ml_parser/html_parser';
|
||||
@ -272,11 +272,14 @@ export function main() {
|
||||
[['{count, plural, =1 {[1]}}'], '', ''],
|
||||
]);
|
||||
|
||||
// ICU message placeholders are reference to translations.
|
||||
// As such they have no static content but refs to message ids.
|
||||
expect(_humanizePlaceholders(html)).toEqual(['', '', '', '']);
|
||||
expect(_humanizePlaceholders(html)).toEqual([
|
||||
'',
|
||||
'VAR_PLURAL=count',
|
||||
'VAR_PLURAL=count',
|
||||
'VAR_PLURAL=count',
|
||||
]);
|
||||
|
||||
expect(_humanizePlaceholdersToIds(html)).toEqual([
|
||||
expect(_humanizePlaceholdersToMessage(html)).toEqual([
|
||||
'ICU=f0f76923009914f1b05f41042a5c7231b9496504, ICU_1=73693d1f78d0fc882f0bcbce4cb31a0aa1995cfe',
|
||||
'',
|
||||
'',
|
||||
@ -308,13 +311,13 @@ function _humanizePlaceholders(
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
function _humanizePlaceholdersToIds(
|
||||
function _humanizePlaceholdersToMessage(
|
||||
html: string, implicitTags: string[] = [],
|
||||
implicitAttrs: {[k: string]: string[]} = {}): string[] {
|
||||
// clang-format off
|
||||
// https://github.com/angular/clang-format/issues/35
|
||||
return _extractMessages(html, implicitTags, implicitAttrs).map(
|
||||
msg => Object.keys(msg.placeholderToMsgIds).map(k => `${k}=${msg.placeholderToMsgIds[k]}`).join(', '));
|
||||
msg => Object.keys(msg.placeholderToMessage).map(k => `${k}=${digest(msg.placeholderToMessage[k])}`).join(', '));
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,9 @@ export function main() {
|
||||
expectHtml(el, '#i18n-2').toBe('<div id="i18n-2"><p>imbriqué</p></div>');
|
||||
expectHtml(el, '#i18n-3')
|
||||
.toBe('<div id="i18n-3"><p><i>avec des espaces réservés</i></p></div>');
|
||||
expectHtml(el, '#i18n-3b')
|
||||
.toBe(
|
||||
'<div id="i18n-3b"><p><i class="preserved-on-placeholders">avec des espaces réservés</i></p></div>');
|
||||
expectHtml(el, '#i18n-4')
|
||||
.toBe('<p id="i18n-4" title="sur des balises non traductibles"></p>');
|
||||
expectHtml(el, '#i18n-5').toBe('<p id="i18n-5" title="sur des balises traductibles"></p>');
|
||||
@ -66,8 +69,10 @@ export function main() {
|
||||
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('beaucoup');
|
||||
|
||||
cmp.sex = 'm';
|
||||
cmp.sexB = 'f';
|
||||
tb.detectChanges();
|
||||
expect(el.query(By.css('#i18n-8')).nativeElement).toHaveText('homme');
|
||||
expect(el.query(By.css('#i18n-8b')).nativeElement).toHaveText('femme');
|
||||
cmp.sex = 'f';
|
||||
tb.detectChanges();
|
||||
expect(el.query(By.css('#i18n-8')).nativeElement).toHaveText('femme');
|
||||
@ -106,6 +111,7 @@ function expectHtml(el: DebugElement, cssSelector: string): any {
|
||||
<div id="i18n-2"><p i18n="different meaning|">nested</p></div>
|
||||
|
||||
<div id="i18n-3"><p i18n><i>with placeholders</i></p></div>
|
||||
<div id="i18n-3b"><p i18n><i class="preserved-on-placeholders">with placeholders</i></p></div>
|
||||
|
||||
<div>
|
||||
<p id="i18n-4" i18n-title title="on not translatable node"></p>
|
||||
@ -117,7 +123,10 @@ function expectHtml(el: DebugElement, cssSelector: string): any {
|
||||
<div i18n id="i18n-7">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
|
||||
|
||||
<div i18n id="i18n-8">
|
||||
{sex, sex, m {male} f {female}}
|
||||
{sex, select, m {male} f {female}}
|
||||
</div>
|
||||
<div i18n id="i18n-8b">
|
||||
{sexB, select, m {male} f {female}}
|
||||
</div>
|
||||
|
||||
<div i18n id="i18n-9">{{ "count = " + count }}</div>
|
||||
@ -135,8 +144,9 @@ function expectHtml(el: DebugElement, cssSelector: string): any {
|
||||
`
|
||||
})
|
||||
class I18nComponent {
|
||||
count: number = 0;
|
||||
sex: string = 'm';
|
||||
count: number;
|
||||
sex: string;
|
||||
sexB: string;
|
||||
}
|
||||
|
||||
class FrLocalization extends NgLocalization {
|
||||
@ -153,51 +163,52 @@ class FrLocalization extends NgLocalization {
|
||||
|
||||
const XTB = `
|
||||
<translationbundle>
|
||||
<translation id="3cb04208df1c2f62553ed48e75939cf7107f9dad">attributs i18n sur les balises</translation>
|
||||
<translation id="52895b1221effb3f3585b689f049d2784d714952">imbriqué</translation>
|
||||
<translation id="88d5f22050a9df477ee5646153558b3a4862d47e">imbriqué</translation>
|
||||
<translation id="34fec9cc62e28e8aa6ffb306fa8569ef0a8087fe"><ph name="START_ITALIC_TEXT"/>avec des espaces réservés<ph name="CLOSE_ITALIC_TEXT"/></translation>
|
||||
<translation id="1fe4616cce80a57c7707bac1c97054aa8e244a67">sur des balises non traductibles</translation>
|
||||
<translation id="67162b5af5f15fd0eb6480c88688dafdf952b93a">sur des balises traductibles</translation>
|
||||
<translation id="dc5536bb9e0e07291c185a0d306601a2ecd4813f">{count, plural, =0 {zero} =1 {un} =2 {deux} other {<ph name="START_BOLD_TEXT"/>beaucoup<ph name="CLOSE_BOLD_TEXT"/>}}</translation>
|
||||
<translation id="018efa03821ca41e27611e4a584736810d56ed8a"><ph name="ICU"/></translation>
|
||||
<translation id="fd3186ad2a9aa801fe072ddb16ca34cd98ae93da">{sex, sex, m {homme} f {femme}}</translation>
|
||||
<translation id="d9879678f727b244bc7c7e20f22b63d98cb14890"><ph name="INTERPOLATION"/></translation>
|
||||
<translation id="50dac33dc6fc0578884baac79d875785ed77c928">sexe = <ph name="INTERPOLATION"/></translation>
|
||||
<translation id="a46f833b1fe6ca49e8b97c18f4b7ea0b930c9383"><ph name="CUSTOM_NAME"/></translation>
|
||||
<translation id="2ec983b4893bcd5b24af33bebe3ecba63868453c">dans une section traductible</translation>
|
||||
<translation id="eee74a5be8a75881a4785905bd8302a71f7d9f75">
|
||||
<translation id="615790887472569365">attributs i18n sur les balises</translation>
|
||||
<translation id="3707494640264351337">imbriqué</translation>
|
||||
<translation id="5539162898278769904">imbriqué</translation>
|
||||
<translation id="3780349238193953556"><ph name="START_ITALIC_TEXT"/>avec des espaces réservés<ph name="CLOSE_ITALIC_TEXT"/></translation>
|
||||
<translation id="5525133077318024839">sur des balises non traductibles</translation>
|
||||
<translation id="8670732454866344690">sur des balises traductibles</translation>
|
||||
<translation id="4593805537723189714">{VAR_PLURAL, plural, =0 {zero} =1 {un} =2 {deux} other {<ph name="START_BOLD_TEXT"/>beaucoup<ph name="CLOSE_BOLD_TEXT"/>}}</translation>
|
||||
<translation id="1746565782635215"><ph name="ICU"/></translation>
|
||||
<translation id="5868084092545682515">{VAR_SELECT, select, m {homme} f {femme}}</translation>
|
||||
<translation id="4851788426695310455"><ph name="INTERPOLATION"/></translation>
|
||||
<translation id="9013357158046221374">sexe = <ph name="INTERPOLATION"/></translation>
|
||||
<translation id="8324617391167353662"><ph name="CUSTOM_NAME"/></translation>
|
||||
<translation id="7685649297917455806">dans une section traductible</translation>
|
||||
<translation id="2387287228265107305">
|
||||
<ph name="START_HEADING_LEVEL1"/>Balises dans les commentaires html<ph name="CLOSE_HEADING_LEVEL1"/>
|
||||
<ph name="START_TAG_DIV"/><ph name="CLOSE_TAG_DIV"/>
|
||||
<ph name="START_TAG_DIV_1"/><ph name="ICU"/><ph name="CLOSE_TAG_DIV"></ph>
|
||||
</translation>
|
||||
<translation id="93a30c67d4e6c9b37aecfe2ac0f2b5d366d7b520">ca <ph name="START_BOLD_TEXT"/>devrait<ph name="CLOSE_BOLD_TEXT"/> marcher</translation>
|
||||
<translation id="1491627405349178954">ca <ph name="START_BOLD_TEXT"/>devrait<ph name="CLOSE_BOLD_TEXT"/> marcher</translation>
|
||||
</translationbundle>`;
|
||||
|
||||
// unused, for reference only
|
||||
// can be generated from xmb_spec as follow:
|
||||
// `iit('extract xmb', () => { console.log(toXmb(HTML)); });`
|
||||
// `fit('extract xmb', () => { console.log(toXmb(HTML)); });`
|
||||
const XMB = `
|
||||
<messagebundle>
|
||||
<msg id="3cb04208df1c2f62553ed48e75939cf7107f9dad">i18n attribute on tags</msg>
|
||||
<msg id="52895b1221effb3f3585b689f049d2784d714952">nested</msg>
|
||||
<msg id="88d5f22050a9df477ee5646153558b3a4862d47e" meaning="different meaning">nested</msg>
|
||||
<msg id="34fec9cc62e28e8aa6ffb306fa8569ef0a8087fe"><ph name="START_ITALIC_TEXT"><ex><i></ex></ph>with placeholders<ph name="CLOSE_ITALIC_TEXT"><ex></i></ex></ph></msg>
|
||||
<msg id="1fe4616cce80a57c7707bac1c97054aa8e244a67">on not translatable node</msg>
|
||||
<msg id="67162b5af5f15fd0eb6480c88688dafdf952b93a">on translatable node</msg>
|
||||
<msg id="dc5536bb9e0e07291c185a0d306601a2ecd4813f">{count, plural, =0 {zero}=1 {one}=2 {two}other {<ph name="START_BOLD_TEXT"><ex><b></ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>}}</msg>
|
||||
<msg id="018efa03821ca41e27611e4a584736810d56ed8a">
|
||||
<msg id="615790887472569365">i18n attribute on tags</msg>
|
||||
<msg id="3707494640264351337">nested</msg>
|
||||
<msg id="5539162898278769904" meaning="different meaning">nested</msg>
|
||||
<msg id="3780349238193953556"><ph name="START_ITALIC_TEXT"><ex><i></ex></ph>with placeholders<ph name="CLOSE_ITALIC_TEXT"><ex></i></ex></ph></msg>
|
||||
<msg id="5525133077318024839">on not translatable node</msg>
|
||||
<msg id="8670732454866344690">on translatable node</msg>
|
||||
<msg id="4593805537723189714">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex><b></ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>} }</msg>
|
||||
<msg id="1746565782635215">
|
||||
<ph name="ICU"/>
|
||||
</msg>
|
||||
<msg id="fd3186ad2a9aa801fe072ddb16ca34cd98ae93da">{sex, sex, m {male}f {female}}</msg>
|
||||
<msg id="d9879678f727b244bc7c7e20f22b63d98cb14890"><ph name="INTERPOLATION"/></msg>
|
||||
<msg id="50dac33dc6fc0578884baac79d875785ed77c928">sex = <ph name="INTERPOLATION"/></msg>
|
||||
<msg id="a46f833b1fe6ca49e8b97c18f4b7ea0b930c9383"><ph name="CUSTOM_NAME"/></msg>
|
||||
<msg id="2ec983b4893bcd5b24af33bebe3ecba63868453c">in a translatable section</msg>
|
||||
<msg id="eee74a5be8a75881a4785905bd8302a71f7d9f75">
|
||||
<msg id="5868084092545682515">{VAR_SELECT, select, m {male} f {female} }</msg>
|
||||
<msg id="4851788426695310455"><ph name="INTERPOLATION"/></msg>
|
||||
<msg id="9013357158046221374">sex = <ph name="INTERPOLATION"/></msg>
|
||||
<msg id="8324617391167353662"><ph name="CUSTOM_NAME"/></msg>
|
||||
<msg id="7685649297917455806">in a translatable section</msg>
|
||||
<msg id="2387287228265107305">
|
||||
<ph name="START_HEADING_LEVEL1"><ex><h1></ex></ph>Markers in html comments<ph name="CLOSE_HEADING_LEVEL1"><ex></h1></ex></ph>
|
||||
<ph name="START_TAG_DIV"><ex><div></ex></ph><ph name="CLOSE_TAG_DIV"><ex></div></ex></ph>
|
||||
<ph name="START_TAG_DIV_1"><ex><div></ex></ph><ph name="ICU"/><ph name="CLOSE_TAG_DIV"><ex></div></ex></ph>
|
||||
</msg>
|
||||
<msg id="93a30c67d4e6c9b37aecfe2ac0f2b5d366d7b520">it <ph name="START_BOLD_TEXT"><ex><b></ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph> work</msg>
|
||||
</messagebundle>`;
|
||||
<msg id="1491627405349178954">it <ph name="START_BOLD_TEXT"><ex><b></ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph> work</msg>
|
||||
</messagebundle>
|
||||
`;
|
||||
|
@ -6,12 +6,10 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as i18n from '@angular/compiler/src/i18n/i18n_ast';
|
||||
import {Serializer} from '@angular/compiler/src/i18n/serializers/serializer';
|
||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {serializeNodes} from '../../src/i18n/digest';
|
||||
import * as i18n from '../../src/i18n/i18n_ast';
|
||||
import {MessageBundle} from '../../src/i18n/message_bundle';
|
||||
import {Serializer} from '../../src/i18n/serializers/serializer';
|
||||
import {HtmlParser} from '../../src/ml_parser/html_parser';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG} from '../../src/ml_parser/interpolation_config';
|
||||
|
||||
@ -26,17 +24,18 @@ export function main(): void {
|
||||
messages.updateFromTemplate(
|
||||
'<p i18n="m|d">Translate Me</p>', 'url', DEFAULT_INTERPOLATION_CONFIG);
|
||||
expect(humanizeMessages(messages)).toEqual([
|
||||
'2e791a68a3324ecdd29e252198638dafacec46e9=Translate Me',
|
||||
'Translate Me (m|d)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract the same message with different meaning in different entries', () => {
|
||||
it('should extract the all messages and duplicates', () => {
|
||||
messages.updateFromTemplate(
|
||||
'<p i18n="m|d">Translate Me</p><p i18n>Translate Me</p>', 'url',
|
||||
'<p i18n="m|d">Translate Me</p><p i18n>Translate Me</p><p i18n>Translate Me</p>', 'url',
|
||||
DEFAULT_INTERPOLATION_CONFIG);
|
||||
expect(humanizeMessages(messages)).toEqual([
|
||||
'2e791a68a3324ecdd29e252198638dafacec46e9=Translate Me',
|
||||
'8ca133f957845af1b1868da1b339180d1f519644=Translate Me',
|
||||
'Translate Me (m|d)',
|
||||
'Translate Me (|)',
|
||||
'Translate Me (|)',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -44,13 +43,14 @@ export function main(): void {
|
||||
}
|
||||
|
||||
class _TestSerializer implements Serializer {
|
||||
write(messageMap: {[id: string]: i18n.Message}): string {
|
||||
return Object.keys(messageMap)
|
||||
.map(id => `${id}=${serializeNodes(messageMap[id].nodes)}`)
|
||||
write(messages: i18n.Message[]): string {
|
||||
return messages.map(msg => `${serializeNodes(msg.nodes)} (${msg.meaning}|${msg.description})`)
|
||||
.join('//');
|
||||
}
|
||||
|
||||
load(content: string, url: string, placeholders: {}): {} { return null; }
|
||||
load(content: string, url: string): {} { return null; }
|
||||
|
||||
digest(msg: i18n.Message): string { return 'unused'; }
|
||||
}
|
||||
|
||||
function humanizeMessages(catalog: MessageBundle): string[] {
|
||||
|
@ -6,8 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {PlaceholderRegistry} from '../../../src/i18n/serializers/placeholder';
|
||||
|
||||
export function main(): void {
|
||||
|
@ -6,12 +6,13 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Xliff} from '@angular/compiler/src/i18n/serializers/xliff';
|
||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
import {escapeRegExp} from '@angular/core/src/facade/lang';
|
||||
|
||||
import {serializeNodes} from '../../../src/i18n/digest';
|
||||
import {MessageBundle} from '../../../src/i18n/message_bundle';
|
||||
import {Xliff} from '../../../src/i18n/serializers/xliff';
|
||||
import {HtmlParser} from '../../../src/ml_parser/html_parser';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG} from '../../../src/ml_parser/interpolation_config';
|
||||
import {serializeNodes} from '../../ml_parser/ast_serializer_spec';
|
||||
|
||||
const HTML = `
|
||||
<p i18n-title title="translatable attribute">not translatable</p>
|
||||
@ -77,8 +78,7 @@ const LOAD_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
`;
|
||||
|
||||
export function main(): void {
|
||||
let serializer: Xliff;
|
||||
let htmlParser: HtmlParser;
|
||||
const serializer = new Xliff();
|
||||
|
||||
function toXliff(html: string): string {
|
||||
const catalog = new MessageBundle(new HtmlParser, [], {});
|
||||
@ -86,39 +86,130 @@ export function main(): void {
|
||||
return catalog.write(serializer);
|
||||
}
|
||||
|
||||
function loadAsText(template: string, xliff: string): {[id: string]: string} {
|
||||
const messageBundle = new MessageBundle(htmlParser, [], {});
|
||||
messageBundle.updateFromTemplate(template, 'url', DEFAULT_INTERPOLATION_CONFIG);
|
||||
function loadAsMap(xliff: string): {[id: string]: string} {
|
||||
const i18nNodesByMsgId = serializer.load(xliff, 'url');
|
||||
const msgMap: {[id: string]: string} = {};
|
||||
Object.keys(i18nNodesByMsgId)
|
||||
.forEach(id => msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join(''));
|
||||
|
||||
const asAst = serializer.load(xliff, 'url', messageBundle);
|
||||
const asText: {[id: string]: string} = {};
|
||||
Object.keys(asAst).forEach(id => { asText[id] = serializeNodes(asAst[id]).join(''); });
|
||||
|
||||
return asText;
|
||||
return msgMap;
|
||||
}
|
||||
|
||||
describe('XLIFF serializer', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
htmlParser = new HtmlParser();
|
||||
serializer = new Xliff(htmlParser, DEFAULT_INTERPOLATION_CONFIG);
|
||||
});
|
||||
|
||||
|
||||
describe('write', () => {
|
||||
it('should write a valid xliff file', () => { expect(toXliff(HTML)).toEqual(WRITE_XLIFF); });
|
||||
});
|
||||
|
||||
describe('load', () => {
|
||||
it('should load XLIFF files', () => {
|
||||
expect(loadAsText(HTML, LOAD_XLIFF)).toEqual({
|
||||
expect(loadAsMap(LOAD_XLIFF)).toEqual({
|
||||
'983775b9a51ce14b036be72d4cfd65d68d64e231': 'etubirtta elbatalsnart',
|
||||
'ec1d033f2436133c14ab038286c4f5df4697484a':
|
||||
'{{ interpolation}} footnemele elbatalsnart <b>sredlohecalp htiw</b>',
|
||||
'<ph name="INTERPOLATION"/> footnemele elbatalsnart <ph name="START_BOLD_TEXT"/>sredlohecalp htiw<ph name="CLOSE_BOLD_TEXT"/>',
|
||||
'db3e0a6a5a96481f60aec61d98c3eecddef5ac23': 'oof',
|
||||
'd7fa2d59aaedcaa5309f13028c59af8c85b8c49d': '<div></div><img/><br/>',
|
||||
'd7fa2d59aaedcaa5309f13028c59af8c85b8c49d':
|
||||
'<ph name="START_TAG_DIV"/><ph name="CLOSE_TAG_DIV"/><ph name="TAG_IMG"/><ph name="LINE_BREAK"/>',
|
||||
});
|
||||
});
|
||||
|
||||
describe('structure errors', () => {
|
||||
it('should throw when a trans-unit has no translation', () => {
|
||||
const 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="missingtarget">
|
||||
<source/>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(/Message missingtarget misses a translation/);
|
||||
});
|
||||
|
||||
|
||||
it('should throw when a trans-unit has no id attribute', () => {
|
||||
const 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 datatype="html">
|
||||
<source/>
|
||||
<target/>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(/<trans-unit> misses the "id" attribute/);
|
||||
});
|
||||
|
||||
it('should throw on duplicate trans-unit id', () => {
|
||||
const 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="deadbeef">
|
||||
<source/>
|
||||
<target/>
|
||||
</trans-unit>
|
||||
<trans-unit id="deadbeef">
|
||||
<source/>
|
||||
<target/>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(/Duplicated translations for msg deadbeef/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('message errors', () => {
|
||||
it('should throw on unknown message tags', () => {
|
||||
const 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="deadbeef" datatype="html">
|
||||
<source/>
|
||||
<target><b>msg should contain only ph tags</b></target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(() => { loadAsMap(XLIFF); })
|
||||
.toThrowError(
|
||||
new RegExp(escapeRegExp(`[ERROR ->]<b>msg should contain only ph tags</b>`)));
|
||||
});
|
||||
|
||||
it('should throw when a placeholder misses an id attribute', () => {
|
||||
const 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="deadbeef" datatype="html">
|
||||
<source/>
|
||||
<target><x/></target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>`;
|
||||
|
||||
expect(() => {
|
||||
loadAsMap(XLIFF);
|
||||
}).toThrowError(new RegExp(escapeRegExp(`<x> misses the "id" attribute`)));
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -43,10 +43,10 @@ export function main(): void {
|
||||
<!ELEMENT ex (#PCDATA)>
|
||||
]>
|
||||
<messagebundle>
|
||||
<msg id="ec1d033f2436133c14ab038286c4f5df4697484a">translatable element <ph name="START_BOLD_TEXT"><ex><b></ex></ph>with placeholders<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph> <ph name="INTERPOLATION"/></msg>
|
||||
<msg id="e2ccf3d131b15f54aa1fcf1314b1ca77c14bfcc2">{ count, plural, =0 {<ph name="START_PARAGRAPH"><ex><p></ex></ph>test<ph name="CLOSE_PARAGRAPH"><ex></p></ex></ph>} }</msg>
|
||||
<msg id="db3e0a6a5a96481f60aec61d98c3eecddef5ac23" desc="d" meaning="m">foo</msg>
|
||||
<msg id="0e16a673a5a7a135c9f7b957ec2c5c6f6ee6e2c4">{ count, plural, =0 {{ sex, select, other {<ph name="START_PARAGRAPH"><ex><p></ex></ph>deeply nested<ph name="CLOSE_PARAGRAPH"><ex></p></ex></ph>} } } }</msg>
|
||||
<msg id="7056919470098446707">translatable element <ph name="START_BOLD_TEXT"><ex><b></ex></ph>with placeholders<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph> <ph name="INTERPOLATION"/></msg>
|
||||
<msg id="2981514368455622387">{VAR_PLURAL, plural, =0 {<ph name="START_PARAGRAPH"><ex><p></ex></ph>test<ph name="CLOSE_PARAGRAPH"><ex></p></ex></ph>} }</msg>
|
||||
<msg id="7999024498831672133" desc="d" meaning="m">foo</msg>
|
||||
<msg id="2015957479576096115">{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<ph name="START_PARAGRAPH"><ex><p></ex></ph>deeply nested<ph name="CLOSE_PARAGRAPH"><ex></p></ex></ph>} } } }</msg>
|
||||
</messagebundle>
|
||||
`;
|
||||
|
||||
@ -55,7 +55,7 @@ export function main(): void {
|
||||
it('should throw when trying to load an xmb file', () => {
|
||||
expect(() => {
|
||||
const serializer = new Xmb();
|
||||
serializer.load(XMB, 'url', null);
|
||||
serializer.load(XMB, 'url');
|
||||
}).toThrowError(/Unsupported/);
|
||||
});
|
||||
});
|
||||
|
@ -6,8 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
|
||||
import * as xml from '../../../src/i18n/serializers/xml_helper';
|
||||
|
||||
export function main(): void {
|
||||
|
@ -8,37 +8,24 @@
|
||||
|
||||
import {escapeRegExp} from '@angular/core/src/facade/lang';
|
||||
|
||||
import {MessageBundle} from '../../../src/i18n/message_bundle';
|
||||
import {serializeNodes} from '../../../src/i18n/digest';
|
||||
import {Xtb} from '../../../src/i18n/serializers/xtb';
|
||||
import {HtmlParser} from '../../../src/ml_parser/html_parser';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG} from '../../../src/ml_parser/interpolation_config';
|
||||
import {serializeNodes} from '../../ml_parser/ast_serializer_spec';
|
||||
|
||||
export function main(): void {
|
||||
describe('XTB serializer', () => {
|
||||
let serializer: Xtb;
|
||||
let htmlParser: HtmlParser;
|
||||
const serializer = new Xtb();
|
||||
|
||||
function loadAsText(template: string, xtb: string): {[id: string]: string} {
|
||||
const messageBundle = new MessageBundle(htmlParser, [], {});
|
||||
messageBundle.updateFromTemplate(template, 'url', DEFAULT_INTERPOLATION_CONFIG);
|
||||
|
||||
const asAst = serializer.load(xtb, 'url', messageBundle);
|
||||
const asText: {[id: string]: string} = {};
|
||||
Object.keys(asAst).forEach(id => { asText[id] = serializeNodes(asAst[id]).join(''); });
|
||||
|
||||
return asText;
|
||||
function loadAsMap(xtb: string): {[id: string]: string} {
|
||||
const i18nNodesByMsgId = serializer.load(xtb, 'url');
|
||||
const msgMap: {[id: string]: string} = {};
|
||||
Object.keys(i18nNodesByMsgId).forEach(id => {
|
||||
msgMap[id] = serializeNodes(i18nNodesByMsgId[id]).join('');
|
||||
});
|
||||
return msgMap;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
htmlParser = new HtmlParser();
|
||||
serializer = new Xtb(htmlParser, DEFAULT_INTERPOLATION_CONFIG);
|
||||
});
|
||||
|
||||
describe('load', () => {
|
||||
it('should load XTB files with a doctype', () => {
|
||||
const HTML = `<div i18n>bar</div>`;
|
||||
|
||||
const XTB = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE translationbundle [<!ELEMENT translationbundle (translation)*>
|
||||
<!ATTLIST translationbundle lang CDATA #REQUIRED>
|
||||
@ -50,75 +37,66 @@ export function main(): void {
|
||||
<!ATTLIST ph name CDATA #REQUIRED>
|
||||
]>
|
||||
<translationbundle>
|
||||
<translation id="28a86c8a00ae573b2bac698d6609316dc7b4a226">rab</translation>
|
||||
<translation id="8841459487341224498">rab</translation>
|
||||
</translationbundle>`;
|
||||
|
||||
expect(loadAsText(HTML, XTB)).toEqual({'28a86c8a00ae573b2bac698d6609316dc7b4a226': 'rab'});
|
||||
expect(loadAsMap(XTB)).toEqual({'8841459487341224498': 'rab'});
|
||||
});
|
||||
|
||||
it('should load XTB files without placeholders', () => {
|
||||
const HTML = `<div i18n>bar</div>`;
|
||||
|
||||
const XTB = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<translationbundle>
|
||||
<translation id="28a86c8a00ae573b2bac698d6609316dc7b4a226">rab</translation>
|
||||
<translation id="8841459487341224498">rab</translation>
|
||||
</translationbundle>`;
|
||||
|
||||
expect(loadAsText(HTML, XTB)).toEqual({'28a86c8a00ae573b2bac698d6609316dc7b4a226': 'rab'});
|
||||
expect(loadAsMap(XTB)).toEqual({'8841459487341224498': 'rab'});
|
||||
});
|
||||
|
||||
it('should load XTB files with placeholders', () => {
|
||||
const HTML = `<div i18n><p>bar</p></div>`;
|
||||
|
||||
it('should load XTB files with placeholders', () => {
|
||||
const XTB = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<translationbundle>
|
||||
<translation id="7de4d8ff1e42b7b31da6204074818236a9a5317f"><ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/></translation>
|
||||
<translation id="8877975308926375834"><ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/></translation>
|
||||
</translationbundle>`;
|
||||
|
||||
expect(loadAsText(HTML, XTB)).toEqual({
|
||||
'7de4d8ff1e42b7b31da6204074818236a9a5317f': '<p>rab</p>'
|
||||
expect(loadAsMap(XTB)).toEqual({
|
||||
'8877975308926375834': '<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>'
|
||||
});
|
||||
});
|
||||
|
||||
it('should replace ICU placeholders with their translations', () => {
|
||||
const HTML = `<div i18n>-{ count, plural, =0 {<p>bar</p>}}-</div>`;
|
||||
|
||||
const XTB = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<translationbundle>
|
||||
<translation id="eb404e202fed4846e25e7d9ac1fcb719fe4da257">*<ph name="ICU"/>*</translation>
|
||||
<translation id="fc92b9b781194a02ab773129c8c5a7fc0735efd7">{ count, plural, =1 {<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>}}</translation>
|
||||
<translation id="7717087045075616176">*<ph name="ICU"/>*</translation>
|
||||
<translation id="5115002811911870583">{VAR_PLURAL, plural, =1 {<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>}}</translation>
|
||||
</translationbundle>`;
|
||||
|
||||
expect(loadAsText(HTML, XTB)).toEqual({
|
||||
'eb404e202fed4846e25e7d9ac1fcb719fe4da257': `*{ count, plural, =1 {<p>rab</p>}}*`,
|
||||
'fc92b9b781194a02ab773129c8c5a7fc0735efd7': `{ count, plural, =1 {<p>rab</p>}}`,
|
||||
expect(loadAsMap(XTB)).toEqual({
|
||||
'7717087045075616176': `*<ph name="ICU"/>*`,
|
||||
'5115002811911870583':
|
||||
`{VAR_PLURAL, plural, =1 {[<ph name="START_PARAGRAPH"/>, rab, <ph name="CLOSE_PARAGRAPH"/>]}}`,
|
||||
});
|
||||
});
|
||||
|
||||
it('should load complex XTB files', () => {
|
||||
const HTML = `
|
||||
<div i18n>foo <b>bar</b> {{ a + b }}</div>
|
||||
<div i18n>{ count, plural, =0 {<p>bar</p>}}</div>
|
||||
<div i18n="m|d">foo</div>
|
||||
<div i18n>{ count, plural, =0 {{ sex, select, other {<p>bar</p>}} }}</div>`;
|
||||
|
||||
const XTB = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<translationbundle>
|
||||
<translation id="7103b4b13b616270a0044efade97d8b4f96f2ca6"><ph name="INTERPOLATION"/><ph name="START_BOLD_TEXT"/>rab<ph name="CLOSE_BOLD_TEXT"/> oof</translation>
|
||||
<translation id="fc92b9b781194a02ab773129c8c5a7fc0735efd7">{ count, plural, =1 {<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>}}</translation>
|
||||
<translation id="db3e0a6a5a96481f60aec61d98c3eecddef5ac23">oof</translation>
|
||||
<translation id="8fb569d3dd83e92eff2551b24f5290d3035ce61b">{ count, plural, =1 {{ sex, select, other {<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>}} }}</translation>
|
||||
<translation id="8281795707202401639"><ph name="INTERPOLATION"/><ph name="START_BOLD_TEXT"/>rab<ph name="CLOSE_BOLD_TEXT"/> oof</translation>
|
||||
<translation id="5115002811911870583">{VAR_PLURAL, plural, =1 {<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>}}</translation>
|
||||
<translation id="130772889486467622">oof</translation>
|
||||
<translation id="4739316421648347533">{VAR_PLURAL, plural, =1 {{VAR_GENDER, gender, male {<ph name="START_PARAGRAPH"/>rab<ph name="CLOSE_PARAGRAPH"/>}} }}</translation>
|
||||
</translationbundle>`;
|
||||
|
||||
expect(loadAsText(HTML, XTB)).toEqual({
|
||||
'7103b4b13b616270a0044efade97d8b4f96f2ca6': `{{ a + b }}<b>rab</b> oof`,
|
||||
'fc92b9b781194a02ab773129c8c5a7fc0735efd7': `{ count, plural, =1 {<p>rab</p>}}`,
|
||||
'db3e0a6a5a96481f60aec61d98c3eecddef5ac23': `oof`,
|
||||
'8fb569d3dd83e92eff2551b24f5290d3035ce61b':
|
||||
`{ count, plural, =1 {{ sex, select, other {<p>rab</p>}} }}`,
|
||||
expect(loadAsMap(XTB)).toEqual({
|
||||
'8281795707202401639':
|
||||
`<ph name="INTERPOLATION"/><ph name="START_BOLD_TEXT"/>rab<ph name="CLOSE_BOLD_TEXT"/> oof`,
|
||||
'5115002811911870583':
|
||||
`{VAR_PLURAL, plural, =1 {[<ph name="START_PARAGRAPH"/>, rab, <ph name="CLOSE_PARAGRAPH"/>]}}`,
|
||||
'130772889486467622': `oof`,
|
||||
'4739316421648347533':
|
||||
`{VAR_PLURAL, plural, =1 {[{VAR_GENDER, gender, male {[<ph name="START_PARAGRAPH"/>, rab, <ph name="CLOSE_PARAGRAPH"/>]}}, ]}}`,
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
@ -127,7 +105,7 @@ export function main(): void {
|
||||
'<translationbundle><translationbundle></translationbundle></translationbundle>';
|
||||
|
||||
expect(() => {
|
||||
loadAsText('', XTB);
|
||||
loadAsMap(XTB);
|
||||
}).toThrowError(/<translationbundle> elements can not be nested/);
|
||||
});
|
||||
|
||||
@ -136,58 +114,49 @@ export function main(): void {
|
||||
<translation></translation>
|
||||
</translationbundle>`;
|
||||
|
||||
expect(() => {
|
||||
loadAsText('', XTB);
|
||||
}).toThrowError(/<translation> misses the "id" attribute/);
|
||||
expect(() => { loadAsMap(XTB); }).toThrowError(/<translation> misses the "id" attribute/);
|
||||
});
|
||||
|
||||
it('should throw when a placeholder has no name attribute', () => {
|
||||
const HTML = '<div i18n>give me a message</div>';
|
||||
|
||||
const XTB = `<translationbundle>
|
||||
<translation id="8de97c6a35252d9409dcaca0b8171c952740b28c"><ph /></translation>
|
||||
<translation id="1186013544048295927"><ph /></translation>
|
||||
</translationbundle>`;
|
||||
|
||||
expect(() => { loadAsText(HTML, XTB); }).toThrowError(/<ph> misses the "name" attribute/);
|
||||
expect(() => { loadAsMap(XTB); }).toThrowError(/<ph> misses the "name" attribute/);
|
||||
});
|
||||
|
||||
it('should throw when a placeholder is not present in the source message', () => {
|
||||
const HTML = `<div i18n>bar</div>`;
|
||||
it('should throw on unknown xtb tags', () => {
|
||||
const XTB = `<what></what>`;
|
||||
|
||||
const XTB = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<translationbundle>
|
||||
<translation id="28a86c8a00ae573b2bac698d6609316dc7b4a226"><ph name="UNKNOWN"/></translation>
|
||||
expect(() => {
|
||||
loadAsMap(XTB);
|
||||
}).toThrowError(new RegExp(escapeRegExp(`Unexpected tag ("[ERROR ->]<what></what>")`)));
|
||||
});
|
||||
|
||||
it('should throw on unknown message tags', () => {
|
||||
const XTB = `<translationbundle>
|
||||
<translation id="1186013544048295927"><b>msg should contain only ph tags</b></translation>
|
||||
</translationbundle>`;
|
||||
|
||||
expect(() => { loadAsMap(XTB); })
|
||||
.toThrowError(
|
||||
new RegExp(escapeRegExp(`[ERROR ->]<b>msg should contain only ph tags</b>`)));
|
||||
});
|
||||
|
||||
it('should throw on duplicate message id', () => {
|
||||
const XTB = `<translationbundle>
|
||||
<translation id="1186013544048295927">msg1</translation>
|
||||
<translation id="1186013544048295927">msg2</translation>
|
||||
</translationbundle>`;
|
||||
|
||||
expect(() => {
|
||||
loadAsText(HTML, XTB);
|
||||
}).toThrowError(/The placeholder "UNKNOWN" does not exists in the source message/);
|
||||
loadAsMap(XTB);
|
||||
}).toThrowError(/Duplicated translations for msg 1186013544048295927/);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw when the translation results in invalid html', () => {
|
||||
const HTML = `<div i18n><p>bar</p></div>`;
|
||||
|
||||
const XTB = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<translationbundle>
|
||||
<translation id="7de4d8ff1e42b7b31da6204074818236a9a5317f">rab<ph name="CLOSE_PARAGRAPH"/></translation>
|
||||
</translationbundle>`;
|
||||
|
||||
expect(() => {
|
||||
loadAsText(HTML, XTB);
|
||||
}).toThrowError(/xtb parse errors:\nUnexpected closing tag "p"/);
|
||||
it('should throw when trying to save an xtb file',
|
||||
() => { expect(() => { serializer.write([]); }).toThrowError(/Unsupported/); });
|
||||
|
||||
});
|
||||
|
||||
it('should throw on unknown tags', () => {
|
||||
const XTB = `<what></what>`;
|
||||
|
||||
expect(() => {
|
||||
loadAsText('', XTB);
|
||||
}).toThrowError(new RegExp(escapeRegExp(`Unexpected tag ("[ERROR ->]<what></what>")`)));
|
||||
});
|
||||
|
||||
it('should throw when trying to save an xtb file',
|
||||
() => { expect(() => { serializer.write({}); }).toThrowError(/Unsupported/); });
|
||||
});
|
||||
}
|
110
modules/@angular/compiler/test/i18n/translation_bundle_spec.ts
Normal file
110
modules/@angular/compiler/test/i18n/translation_bundle_spec.ts
Normal file
@ -0,0 +1,110 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as i18n from '../../src/i18n/i18n_ast';
|
||||
import {TranslationBundle} from '../../src/i18n/translation_bundle';
|
||||
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '../../src/parse_util';
|
||||
import {serializeNodes} from '../ml_parser/ast_serializer_spec';
|
||||
|
||||
export function main(): void {
|
||||
describe('TranslationBundle', () => {
|
||||
const file = new ParseSourceFile('content', 'url');
|
||||
const location = new ParseLocation(file, 0, 0, 0);
|
||||
const span = new ParseSourceSpan(location, null);
|
||||
const srcNode = new i18n.Text('src', span);
|
||||
|
||||
it('should translate a plain message', () => {
|
||||
const msgMap = {foo: [new i18n.Text('bar', null)]};
|
||||
const tb = new TranslationBundle(msgMap, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
expect(serializeNodes(tb.get(msg))).toEqual(['bar']);
|
||||
});
|
||||
|
||||
it('should translate a message with placeholder', () => {
|
||||
const msgMap = {
|
||||
foo: [
|
||||
new i18n.Text('bar', null),
|
||||
new i18n.Placeholder('', 'ph1', null),
|
||||
]
|
||||
};
|
||||
const phMap = {
|
||||
ph1: '*phContent*',
|
||||
};
|
||||
const tb = new TranslationBundle(msgMap, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd');
|
||||
expect(serializeNodes(tb.get(msg))).toEqual(['bar*phContent*']);
|
||||
});
|
||||
|
||||
it('should translate a message with placeholder referencing messages', () => {
|
||||
const msgMap = {
|
||||
foo: [
|
||||
new i18n.Text('--', null),
|
||||
new i18n.Placeholder('', 'ph1', null),
|
||||
new i18n.Text('++', null),
|
||||
],
|
||||
ref: [
|
||||
new i18n.Text('*refMsg*', null),
|
||||
],
|
||||
};
|
||||
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd');
|
||||
let count = 0;
|
||||
const digest = (_: any) => count++ ? 'ref' : 'foo';
|
||||
const tb = new TranslationBundle(msgMap, digest);
|
||||
|
||||
expect(serializeNodes(tb.get(msg))).toEqual(['--*refMsg*++']);
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
it('should report unknown placeholders', () => {
|
||||
const msgMap = {
|
||||
foo: [
|
||||
new i18n.Text('bar', null),
|
||||
new i18n.Placeholder('', 'ph1', span),
|
||||
]
|
||||
};
|
||||
const tb = new TranslationBundle(msgMap, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
expect(() => tb.get(msg)).toThrowError(/Unknown placeholder/);
|
||||
});
|
||||
|
||||
it('should report missing translation', () => {
|
||||
const tb = new TranslationBundle({}, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
expect(() => tb.get(msg)).toThrowError(/Missing translation for message foo/);
|
||||
});
|
||||
|
||||
it('should report missing referenced message', () => {
|
||||
const msgMap = {
|
||||
foo: [new i18n.Placeholder('', 'ph1', span)],
|
||||
};
|
||||
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd');
|
||||
let count = 0;
|
||||
const digest = (_: any) => count++ ? 'ref' : 'foo';
|
||||
const tb = new TranslationBundle(msgMap, digest);
|
||||
expect(() => tb.get(msg)).toThrowError(/Missing translation for message ref/);
|
||||
});
|
||||
|
||||
it('should report invalid translated html', () => {
|
||||
const msgMap = {
|
||||
foo: [
|
||||
new i18n.Text('text', null),
|
||||
new i18n.Placeholder('', 'ph1', null),
|
||||
]
|
||||
};
|
||||
const phMap = {
|
||||
ph1: '</b>',
|
||||
};
|
||||
const tb = new TranslationBundle(msgMap, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd');
|
||||
expect(() => tb.get(msg)).toThrowError(/Unexpected closing tag "b"/);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -97,4 +97,4 @@ const serializerVisitor = new _SerializerVisitor();
|
||||
|
||||
export function serializeNodes(nodes: html.Node[]): string[] {
|
||||
return nodes.map(node => node.visit(serializerVisitor, null));
|
||||
}
|
||||
}
|
@ -13,8 +13,8 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_in
|
||||
|
||||
import {SimpleJsImportGenerator} from './output_emitter_util';
|
||||
|
||||
const someModuleUrl = 'asset:somePackage/lib/somePath';
|
||||
const anotherModuleUrl = 'asset:somePackage/lib/someOtherPath';
|
||||
const someModuleUrl = 'somePackage/somePath';
|
||||
const anotherModuleUrl = 'somePackage/someOtherPath';
|
||||
|
||||
const sameModuleIdentifier =
|
||||
new CompileIdentifierMetadata({name: 'someLocalId', moduleUrl: someModuleUrl});
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata';
|
||||
import {assetUrl} from '@angular/compiler/src/identifiers';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
import {ImportGenerator} from '@angular/compiler/src/output/path_util';
|
||||
import {ImportResolver} from '@angular/compiler/src/output/path_util';
|
||||
import {EventEmitter} from '@angular/core';
|
||||
import {BaseError} from '@angular/core/src/facade/errors';
|
||||
import {ViewType} from '@angular/core/src/linker/view_type';
|
||||
@ -22,7 +22,7 @@ export class ExternalClass {
|
||||
|
||||
const testDataIdentifier = new CompileIdentifierMetadata({
|
||||
name: 'ExternalClass',
|
||||
moduleUrl: `asset:@angular/lib/compiler/test/output/output_emitter_util`,
|
||||
moduleUrl: `@angular/compiler/test/output/output_emitter_util`,
|
||||
reference: ExternalClass
|
||||
});
|
||||
|
||||
@ -252,13 +252,8 @@ function createOperatorFn(op: o.BinaryOperator) {
|
||||
o.DYNAMIC_TYPE);
|
||||
}
|
||||
|
||||
export class SimpleJsImportGenerator implements ImportGenerator {
|
||||
getImportPath(moduleUrlStr: string, importedUrlStr: string): string {
|
||||
const importedAssetUrl = ImportGenerator.parseAssetUrl(importedUrlStr);
|
||||
if (importedAssetUrl) {
|
||||
return `${importedAssetUrl.packageName}/${importedAssetUrl.modulePath}`;
|
||||
} else {
|
||||
return importedUrlStr;
|
||||
}
|
||||
export class SimpleJsImportGenerator implements ImportResolver {
|
||||
fileNameToModuleName(importedUrlStr: string, moduleUrlStr: string): string {
|
||||
return importedUrlStr;
|
||||
}
|
||||
}
|
||||
|
@ -13,8 +13,8 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_in
|
||||
|
||||
import {SimpleJsImportGenerator} from './output_emitter_util';
|
||||
|
||||
const someModuleUrl = 'asset:somePackage/lib/somePath';
|
||||
const anotherModuleUrl = 'asset:somePackage/lib/someOtherPath';
|
||||
const someModuleUrl = 'somePackage/somePath';
|
||||
const anotherModuleUrl = 'somePackage/someOtherPath';
|
||||
|
||||
const sameModuleIdentifier =
|
||||
new CompileIdentifierMetadata({name: 'someLocalId', moduleUrl: someModuleUrl});
|
||||
|
@ -91,9 +91,6 @@ export function main() {
|
||||
it('should resolve package: urls',
|
||||
() => { expect(isStyleUrlResolvable('package:someUrl.css')).toBe(true); });
|
||||
|
||||
it('should resolve asset: urls',
|
||||
() => { expect(isStyleUrlResolvable('asset:someUrl.css')).toBe(true); });
|
||||
|
||||
it('should not resolve empty urls', () => {
|
||||
expect(isStyleUrlResolvable(null)).toBe(false);
|
||||
expect(isStyleUrlResolvable('')).toBe(false);
|
||||
|
@ -106,16 +106,6 @@ export function main() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('asset urls', () => {
|
||||
let resolver: UrlResolver;
|
||||
beforeEach(() => { resolver = createOfflineCompileUrlResolver(); });
|
||||
|
||||
it('should resolve package: urls into asset: urls', () => {
|
||||
expect(resolver.resolve(null, 'package:somePkg/somePath'))
|
||||
.toEqual('asset:somePkg/lib/somePath');
|
||||
});
|
||||
});
|
||||
|
||||
describe('corner and error cases', () => {
|
||||
it('should encode URLs before resolving',
|
||||
() => {
|
||||
|
@ -29,7 +29,7 @@ export * from './pipe_resolver_mock';
|
||||
import {createPlatformFactory, ModuleWithComponentFactories, Injectable, CompilerOptions, COMPILER_OPTIONS, CompilerFactory, NgModuleFactory, Injector, NgModule, Component, Directive, Pipe, Type, PlatformRef} from '@angular/core';
|
||||
import {MetadataOverride} from '@angular/core/testing';
|
||||
import {TestingCompilerFactory, TestingCompiler} from './private_import_core';
|
||||
import {platformCoreDynamic, RuntimeCompiler, DirectiveResolver, NgModuleResolver, PipeResolver} from '@angular/compiler';
|
||||
import {platformCoreDynamic, JitCompiler, DirectiveResolver, NgModuleResolver, PipeResolver} from '@angular/compiler';
|
||||
import {MockDirectiveResolver} from './directive_resolver_mock';
|
||||
import {MockNgModuleResolver} from './ng_module_resolver_mock';
|
||||
import {MockPipeResolver} from './pipe_resolver_mock';
|
||||
@ -40,7 +40,7 @@ export class TestingCompilerFactoryImpl implements TestingCompilerFactory {
|
||||
constructor(private _compilerFactory: CompilerFactory) {}
|
||||
|
||||
createTestingCompiler(options: CompilerOptions[]): TestingCompiler {
|
||||
const compiler = <RuntimeCompiler>this._compilerFactory.createCompiler(options);
|
||||
const compiler = <JitCompiler>this._compilerFactory.createCompiler(options);
|
||||
return new TestingCompilerImpl(
|
||||
compiler, compiler.injector.get(MockDirectiveResolver),
|
||||
compiler.injector.get(MockPipeResolver), compiler.injector.get(MockNgModuleResolver));
|
||||
@ -50,7 +50,7 @@ export class TestingCompilerFactoryImpl implements TestingCompilerFactory {
|
||||
export class TestingCompilerImpl implements TestingCompiler {
|
||||
private _overrider = new MetadataOverrider();
|
||||
constructor(
|
||||
private _compiler: RuntimeCompiler, private _directiveResolver: MockDirectiveResolver,
|
||||
private _compiler: JitCompiler, private _directiveResolver: MockDirectiveResolver,
|
||||
private _pipeResolver: MockPipeResolver, private _moduleResolver: MockNgModuleResolver) {}
|
||||
get injector(): Injector { return this._compiler.injector; }
|
||||
|
||||
|
@ -22,5 +22,8 @@
|
||||
"files": [
|
||||
"index.ts",
|
||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||
]
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"annotateForClosureCompiler": true
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ export class AnimationGroupPlayer implements AnimationPlayer {
|
||||
this._started = false;
|
||||
}
|
||||
|
||||
setPosition(p: any /** TODO #9100 */): void {
|
||||
setPosition(p: number): void {
|
||||
this._players.forEach(player => { player.setPosition(p); });
|
||||
}
|
||||
|
||||
@ -100,4 +100,6 @@ export class AnimationGroupPlayer implements AnimationPlayer {
|
||||
});
|
||||
return min;
|
||||
}
|
||||
|
||||
get players(): AnimationPlayer[] { return this._players; }
|
||||
}
|
||||
|
@ -56,6 +56,6 @@ export class NoOpAnimationPlayer implements AnimationPlayer {
|
||||
finish(): void { this._onFinish(); }
|
||||
destroy(): void {}
|
||||
reset(): void {}
|
||||
setPosition(p: any /** TODO #9100 */): void {}
|
||||
setPosition(p: number): void {}
|
||||
getPosition(): number { return 0; }
|
||||
}
|
||||
|
@ -104,7 +104,9 @@ export class AnimationSequencePlayer implements AnimationPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
setPosition(p: any /** TODO #9100 */): void { this._players[0].setPosition(p); }
|
||||
setPosition(p: number): void { this._players[0].setPosition(p); }
|
||||
|
||||
getPosition(): number { return this._players[0].getPosition(); }
|
||||
|
||||
get players(): AnimationPlayer[] { return this._players; }
|
||||
}
|
||||
|
@ -84,6 +84,8 @@ export function balanceAnimationKeyframes(
|
||||
firstKeyframe.styles.styles.push(extraFirstKeyframeStyles);
|
||||
}
|
||||
|
||||
collectAndResolveStyles(collectedStyles, [finalStateStyles]);
|
||||
|
||||
return keyframes;
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,8 @@ import {CompilerFactory, CompilerOptions} from './linker/compiler';
|
||||
import {ComponentFactory, ComponentRef} from './linker/component_factory';
|
||||
import {ComponentFactoryResolver} from './linker/component_factory_resolver';
|
||||
import {NgModuleFactory, NgModuleInjector, NgModuleRef} from './linker/ng_module_factory';
|
||||
import {AppView} from './linker/view';
|
||||
import {ViewRef, ViewRef_} from './linker/view_ref';
|
||||
import {WtfScopeFn, wtfCreateScope, wtfLeave} from './profile/profile';
|
||||
import {Testability, TestabilityRegistry} from './testability/testability';
|
||||
import {Type} from './type';
|
||||
@ -60,6 +62,15 @@ export function isDevMode(): boolean {
|
||||
return _devMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* A token for third-party components that can register themselves with NgProbe.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class NgProbeToken {
|
||||
constructor(public name: string, public token: any) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a platform.
|
||||
* Platforms have to be eagerly created via this function.
|
||||
@ -378,6 +389,23 @@ export abstract class ApplicationRef {
|
||||
* Get a list of components registered to this application.
|
||||
*/
|
||||
get components(): ComponentRef<any>[] { return <ComponentRef<any>[]>unimplemented(); };
|
||||
|
||||
/**
|
||||
* Attaches a view so that it will be dirty checked.
|
||||
* The view will be automatically detached when it is destroyed.
|
||||
* This will throw if the view is already attached to a ViewContainer.
|
||||
*/
|
||||
attachView(view: ViewRef): void { unimplemented(); }
|
||||
|
||||
/**
|
||||
* Detaches a view from dirty checking again.
|
||||
*/
|
||||
detachView(view: ViewRef): void { unimplemented(); }
|
||||
|
||||
/**
|
||||
* Returns the number of attached views.
|
||||
*/
|
||||
get viewCount() { return unimplemented(); }
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@ -388,7 +416,7 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||
private _bootstrapListeners: Function[] = [];
|
||||
private _rootComponents: ComponentRef<any>[] = [];
|
||||
private _rootComponentTypes: Type<any>[] = [];
|
||||
private _changeDetectorRefs: ChangeDetectorRef[] = [];
|
||||
private _views: AppView<any>[] = [];
|
||||
private _runningTick: boolean = false;
|
||||
private _enforceNoNewChanges: boolean = false;
|
||||
|
||||
@ -406,12 +434,16 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||
{next: () => { this._zone.run(() => { this.tick(); }); }});
|
||||
}
|
||||
|
||||
registerChangeDetector(changeDetector: ChangeDetectorRef): void {
|
||||
this._changeDetectorRefs.push(changeDetector);
|
||||
attachView(viewRef: ViewRef): void {
|
||||
const view = (viewRef as ViewRef_<any>).internalView;
|
||||
this._views.push(view);
|
||||
view.attachToAppRef(this);
|
||||
}
|
||||
|
||||
unregisterChangeDetector(changeDetector: ChangeDetectorRef): void {
|
||||
ListWrapper.remove(this._changeDetectorRefs, changeDetector);
|
||||
detachView(viewRef: ViewRef): void {
|
||||
const view = (viewRef as ViewRef_<any>).internalView;
|
||||
ListWrapper.remove(this._views, view);
|
||||
view.detach();
|
||||
}
|
||||
|
||||
bootstrap<C>(componentOrFactory: ComponentFactory<C>|Type<C>): ComponentRef<C> {
|
||||
@ -442,9 +474,8 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||
return compRef;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_loadComponent(componentRef: ComponentRef<any>): void {
|
||||
this._changeDetectorRefs.push(componentRef.changeDetectorRef);
|
||||
private _loadComponent(componentRef: ComponentRef<any>): void {
|
||||
this.attachView(componentRef.hostView);
|
||||
this.tick();
|
||||
this._rootComponents.push(componentRef);
|
||||
// Get the listeners lazily to prevent DI cycles.
|
||||
@ -454,12 +485,8 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||
listeners.forEach((listener) => listener(componentRef));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_unloadComponent(componentRef: ComponentRef<any>): void {
|
||||
if (this._rootComponents.indexOf(componentRef) == -1) {
|
||||
return;
|
||||
}
|
||||
this.unregisterChangeDetector(componentRef.changeDetectorRef);
|
||||
private _unloadComponent(componentRef: ComponentRef<any>): void {
|
||||
this.detachView(componentRef.hostView);
|
||||
ListWrapper.remove(this._rootComponents, componentRef);
|
||||
}
|
||||
|
||||
@ -471,9 +498,9 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||
const scope = ApplicationRef_._tickScope();
|
||||
try {
|
||||
this._runningTick = true;
|
||||
this._changeDetectorRefs.forEach((detector) => detector.detectChanges());
|
||||
this._views.forEach((view) => view.ref.detectChanges());
|
||||
if (this._enforceNoNewChanges) {
|
||||
this._changeDetectorRefs.forEach((detector) => detector.checkNoChanges());
|
||||
this._views.forEach((view) => view.ref.checkNoChanges());
|
||||
}
|
||||
} finally {
|
||||
this._runningTick = false;
|
||||
@ -483,9 +510,11 @@ export class ApplicationRef_ extends ApplicationRef {
|
||||
|
||||
ngOnDestroy() {
|
||||
// TODO(alxhub): Dispose of the NgZone.
|
||||
this._rootComponents.slice().forEach((component) => component.destroy());
|
||||
this._views.slice().forEach((view) => view.destroy());
|
||||
}
|
||||
|
||||
get viewCount() { return this._views.length; }
|
||||
|
||||
get componentTypes(): Type<any>[] { return this._rootComponentTypes; }
|
||||
|
||||
get components(): ComponentRef<any>[] { return this._rootComponents; }
|
||||
|
@ -14,7 +14,7 @@
|
||||
export * from './metadata';
|
||||
export * from './util';
|
||||
export * from './di';
|
||||
export {createPlatform, assertPlatform, destroyPlatform, getPlatform, PlatformRef, ApplicationRef, enableProdMode, isDevMode, createPlatformFactory} from './application_ref';
|
||||
export {createPlatform, assertPlatform, destroyPlatform, getPlatform, PlatformRef, ApplicationRef, enableProdMode, isDevMode, createPlatformFactory, NgProbeToken} from './application_ref';
|
||||
export {APP_ID, PACKAGE_ROOT_URL, PLATFORM_INITIALIZER, APP_BOOTSTRAP_LISTENER} from './application_tokens';
|
||||
export {APP_INITIALIZER, ApplicationInitStatus} from './application_init';
|
||||
export * from './zone';
|
||||
|
@ -22,7 +22,7 @@ export class DebugDomRootRenderer implements RootRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
export class DebugDomRenderer implements Renderer {
|
||||
export class DebugDomRenderer {
|
||||
constructor(private _delegate: Renderer) {}
|
||||
|
||||
selectRootElement(selectorOrNode: string|any, debugInfo?: RenderDebugInfo): any {
|
||||
@ -150,7 +150,9 @@ export class DebugDomRenderer implements Renderer {
|
||||
|
||||
animate(
|
||||
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||
duration: number, delay: number, easing: string): AnimationPlayer {
|
||||
return this._delegate.animate(element, startingStyles, keyframes, duration, delay, easing);
|
||||
duration: number, delay: number, easing: string,
|
||||
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
|
||||
return this._delegate.animate(
|
||||
element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,9 @@
|
||||
import {AnimationGroupPlayer} from '../animation/animation_group_player';
|
||||
import {AnimationPlayer} from '../animation/animation_player';
|
||||
import {queueAnimation as queueAnimationGlobally} from '../animation/animation_queue';
|
||||
import {AnimationTransitionEvent} from '../animation/animation_transition_event';
|
||||
import {AnimationSequencePlayer} from '../animation/animation_sequence_player';
|
||||
import {ViewAnimationMap} from '../animation/view_animation_map';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
|
||||
export class AnimationViewContext {
|
||||
private _players = new ViewAnimationMap();
|
||||
@ -30,15 +31,26 @@ export class AnimationViewContext {
|
||||
this._players.set(element, animationName, player);
|
||||
}
|
||||
|
||||
cancelActiveAnimation(element: any, animationName: string, removeAllAnimations: boolean = false):
|
||||
void {
|
||||
getAnimationPlayers(element: any, animationName: string, removeAllAnimations: boolean = false):
|
||||
AnimationPlayer[] {
|
||||
const players: AnimationPlayer[] = [];
|
||||
if (removeAllAnimations) {
|
||||
this._players.findAllPlayersByElement(element).forEach(player => player.destroy());
|
||||
this._players.findAllPlayersByElement(element).forEach(
|
||||
player => { _recursePlayers(player, players); });
|
||||
} else {
|
||||
const player = this._players.find(element, animationName);
|
||||
if (player) {
|
||||
player.destroy();
|
||||
const currentPlayer = this._players.find(element, animationName);
|
||||
if (currentPlayer) {
|
||||
_recursePlayers(currentPlayer, players);
|
||||
}
|
||||
}
|
||||
return players;
|
||||
}
|
||||
}
|
||||
|
||||
function _recursePlayers(player: AnimationPlayer, collectedPlayers: AnimationPlayer[]) {
|
||||
if ((player instanceof AnimationGroupPlayer) || (player instanceof AnimationSequencePlayer)) {
|
||||
player.players.forEach(player => _recursePlayers(player, collectedPlayers));
|
||||
} else {
|
||||
collectedPlayers.push(player);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ApplicationRef} from '../application_ref';
|
||||
import {ChangeDetectorRef, ChangeDetectorStatus} from '../change_detection/change_detection';
|
||||
import {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
@ -41,7 +42,10 @@ export abstract class AppView<T> {
|
||||
lastRootNode: any;
|
||||
allNodes: any[];
|
||||
disposables: Function[];
|
||||
viewContainer: ViewContainer = null;
|
||||
viewContainer: ViewContainer;
|
||||
// This will be set if a view is directly attached to an ApplicationRef
|
||||
// and not to a view container.
|
||||
appRef: ApplicationRef;
|
||||
|
||||
numberOfChecks: number = 0;
|
||||
|
||||
@ -138,10 +142,12 @@ export abstract class AppView<T> {
|
||||
injector(nodeIndex: number): Injector { return new ElementInjector(this, nodeIndex); }
|
||||
|
||||
detachAndDestroy() {
|
||||
if (this._hasExternalHostElement) {
|
||||
this.detach();
|
||||
} else if (isPresent(this.viewContainer)) {
|
||||
if (this.viewContainer) {
|
||||
this.viewContainer.detachView(this.viewContainer.nestedViews.indexOf(this));
|
||||
} else if (this.appRef) {
|
||||
this.appRef.detachView(this.ref);
|
||||
} else if (this._hasExternalHostElement) {
|
||||
this.detach();
|
||||
}
|
||||
this.destroy();
|
||||
}
|
||||
@ -196,6 +202,7 @@ export abstract class AppView<T> {
|
||||
projectedViews.splice(index, 1);
|
||||
}
|
||||
}
|
||||
this.appRef = null;
|
||||
this.viewContainer = null;
|
||||
this.dirtyParentQueriesInternal();
|
||||
}
|
||||
@ -208,7 +215,18 @@ export abstract class AppView<T> {
|
||||
}
|
||||
}
|
||||
|
||||
attachToAppRef(appRef: ApplicationRef) {
|
||||
if (this.viewContainer) {
|
||||
throw new Error('This view is already attached to a ViewContainer!');
|
||||
}
|
||||
this.appRef = appRef;
|
||||
this.dirtyParentQueriesInternal();
|
||||
}
|
||||
|
||||
attachAfter(viewContainer: ViewContainer, prevView: AppView<any>) {
|
||||
if (this.appRef) {
|
||||
throw new Error('This view is already attached directly to the ApplicationRef!');
|
||||
}
|
||||
this._renderAttach(viewContainer, prevView);
|
||||
this.viewContainer = viewContainer;
|
||||
if (this.declaredViewContainer && this.declaredViewContainer !== viewContainer) {
|
||||
@ -232,8 +250,10 @@ export abstract class AppView<T> {
|
||||
if (nextSibling) {
|
||||
this.visitRootNodesInternal(this._directRenderer.insertBefore, nextSibling);
|
||||
} else {
|
||||
this.visitRootNodesInternal(
|
||||
this._directRenderer.appendChild, this._directRenderer.parentElement(prevNode));
|
||||
const parentElement = this._directRenderer.parentElement(prevNode);
|
||||
if (parentElement) {
|
||||
this.visitRootNodesInternal(this._directRenderer.appendChild, parentElement);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.renderer.attachViewAfter(prevNode, this.flatRootNodes);
|
||||
|
@ -17,7 +17,7 @@ import {AppView} from './view';
|
||||
/**
|
||||
* @stable
|
||||
*/
|
||||
export abstract class ViewRef {
|
||||
export abstract class ViewRef extends ChangeDetectorRef {
|
||||
get destroyed(): boolean { return <boolean>unimplemented(); }
|
||||
|
||||
abstract onDestroy(callback: Function): any /** TODO #9100 */;
|
||||
|
@ -56,8 +56,12 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
}
|
||||
|
||||
// API of tsickle for lowering decorators to properties on the class.
|
||||
if ((<any>type).ctorParameters) {
|
||||
const ctorParameters = (<any>type).ctorParameters;
|
||||
const tsickleCtorParams = (<any>type).ctorParameters;
|
||||
if (tsickleCtorParams) {
|
||||
// Newer tsickle uses a function closure
|
||||
// Retain the non-function case for compatibility with older tsickle
|
||||
const ctorParameters =
|
||||
typeof tsickleCtorParams === 'function' ? tsickleCtorParams() : tsickleCtorParams;
|
||||
const paramTypes = ctorParameters.map((ctorParam: any) => ctorParam && ctorParam.type);
|
||||
const paramAnnotations = ctorParameters.map(
|
||||
(ctorParam: any) =>
|
||||
|
@ -88,7 +88,8 @@ export abstract class Renderer {
|
||||
|
||||
abstract animate(
|
||||
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
|
||||
duration: number, delay: number, easing: string): AnimationPlayer;
|
||||
duration: number, delay: number, easing: string,
|
||||
previousPlayers?: AnimationPlayer[]): AnimationPlayer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1854,6 +1854,8 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
let animation = driver.log.pop();
|
||||
let kf = animation['keyframeLookup'];
|
||||
expect(kf[1]).toEqual([1, {'background': 'green'}]);
|
||||
let player = animation['player'];
|
||||
player.finish();
|
||||
|
||||
cmp.exp = 'blue';
|
||||
fixture.detectChanges();
|
||||
@ -1863,6 +1865,8 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
kf = animation['keyframeLookup'];
|
||||
expect(kf[0]).toEqual([0, {'background': 'green'}]);
|
||||
expect(kf[1]).toEqual([1, {'background': 'grey'}]);
|
||||
player = animation['player'];
|
||||
player.finish();
|
||||
|
||||
cmp.exp = 'red';
|
||||
fixture.detectChanges();
|
||||
@ -1872,6 +1876,8 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
kf = animation['keyframeLookup'];
|
||||
expect(kf[0]).toEqual([0, {'background': 'grey'}]);
|
||||
expect(kf[1]).toEqual([1, {'background': 'red'}]);
|
||||
player = animation['player'];
|
||||
player.finish();
|
||||
|
||||
cmp.exp = 'orange';
|
||||
fixture.detectChanges();
|
||||
@ -1881,6 +1887,8 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
kf = animation['keyframeLookup'];
|
||||
expect(kf[0]).toEqual([0, {'background': 'red'}]);
|
||||
expect(kf[1]).toEqual([1, {'background': 'grey'}]);
|
||||
player = animation['player'];
|
||||
player.finish();
|
||||
}));
|
||||
|
||||
it('should seed in the origin animation state styles into the first animation step',
|
||||
@ -1911,6 +1919,44 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
expect(animation['startingStyles']).toEqual({'height': '100px'});
|
||||
}));
|
||||
|
||||
it('should seed in the previous animation styles into the transition if the previous transition was interupted midway',
|
||||
fakeAsync(() => {
|
||||
TestBed.overrideComponent(DummyIfCmp, {
|
||||
set: {
|
||||
template: `
|
||||
<div class="target" [@status]="exp"></div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
'status',
|
||||
[
|
||||
state('*', style({ opacity: 0 })),
|
||||
state('a', style({height: '100px', width: '200px'})),
|
||||
state('b', style({height: '1000px' })),
|
||||
transition('* => *', [
|
||||
animate(1000, style({ fontSize: '20px' })),
|
||||
animate(1000)
|
||||
])
|
||||
])]
|
||||
}
|
||||
});
|
||||
|
||||
const driver = TestBed.get(AnimationDriver) as MockAnimationDriver;
|
||||
const fixture = TestBed.createComponent(DummyIfCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
cmp.exp = 'a';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
driver.log = [];
|
||||
|
||||
cmp.exp = 'b';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
const animation = driver.log[0];
|
||||
expect(animation['previousStyles']).toEqual({opacity: '0', fontSize: '*'});
|
||||
}));
|
||||
|
||||
it('should perform a state change even if there is no transition that is found',
|
||||
fakeAsync(() => {
|
||||
TestBed.overrideComponent(DummyIfCmp, {
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, CompilerFactory, Component, NgModule, PlatformRef, Type} from '@angular/core';
|
||||
import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, CompilerFactory, Component, NgModule, PlatformRef, Type, ViewChild, ViewContainerRef} from '@angular/core';
|
||||
import {ApplicationRef, ApplicationRef_} from '@angular/core/src/application_ref';
|
||||
import {ErrorHandler} from '@angular/core/src/error_handler';
|
||||
import {ComponentRef} from '@angular/core/src/linker/component_factory';
|
||||
@ -16,9 +16,7 @@ import {DOCUMENT} from '@angular/platform-browser/src/dom/dom_tokens';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
import {ServerModule} from '@angular/platform-server';
|
||||
|
||||
import {TestBed, async, inject, withModule} from '../testing';
|
||||
|
||||
import {SpyChangeDetectorRef} from './spies';
|
||||
import {ComponentFixtureNoNgZone, TestBed, async, inject, withModule} from '../testing';
|
||||
|
||||
@Component({selector: 'comp', template: 'hello'})
|
||||
class SomeComponent {
|
||||
@ -74,13 +72,16 @@ export function main() {
|
||||
beforeEach(() => { TestBed.configureTestingModule({imports: [createModule()]}); });
|
||||
|
||||
it('should throw when reentering tick', inject([ApplicationRef], (ref: ApplicationRef_) => {
|
||||
const cdRef = <any>new SpyChangeDetectorRef();
|
||||
const view = jasmine.createSpyObj('view', ['detach', 'attachToAppRef']);
|
||||
const viewRef = jasmine.createSpyObj('viewRef', ['detectChanges']);
|
||||
viewRef.internalView = view;
|
||||
view.ref = viewRef;
|
||||
try {
|
||||
ref.registerChangeDetector(cdRef);
|
||||
cdRef.spy('detectChanges').and.callFake(() => ref.tick());
|
||||
ref.attachView(viewRef);
|
||||
viewRef.detectChanges.and.callFake(() => ref.tick());
|
||||
expect(() => ref.tick()).toThrowError('ApplicationRef.tick is called recursively');
|
||||
} finally {
|
||||
ref.unregisterChangeDetector(cdRef);
|
||||
ref.detachView(viewRef);
|
||||
}
|
||||
}));
|
||||
|
||||
@ -261,6 +262,84 @@ export function main() {
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('attachView / detachView', () => {
|
||||
@Component({template: '{{name}}'})
|
||||
class MyComp {
|
||||
name = 'Initial';
|
||||
}
|
||||
|
||||
@Component({template: '<ng-container #vc></ng-container>'})
|
||||
class ContainerComp {
|
||||
@ViewChild('vc', {read: ViewContainerRef})
|
||||
vc: ViewContainerRef;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [MyComp, ContainerComp],
|
||||
providers: [{provide: ComponentFixtureNoNgZone, useValue: true}]
|
||||
});
|
||||
});
|
||||
|
||||
it('should dirty check attached views', () => {
|
||||
const comp = TestBed.createComponent(MyComp);
|
||||
const appRef: ApplicationRef = TestBed.get(ApplicationRef);
|
||||
expect(appRef.viewCount).toBe(0);
|
||||
|
||||
appRef.tick();
|
||||
expect(comp.nativeElement).toHaveText('');
|
||||
|
||||
appRef.attachView(comp.componentRef.hostView);
|
||||
appRef.tick();
|
||||
expect(appRef.viewCount).toBe(1);
|
||||
expect(comp.nativeElement).toHaveText('Initial');
|
||||
});
|
||||
|
||||
it('should not dirty check detached views', () => {
|
||||
const comp = TestBed.createComponent(MyComp);
|
||||
const appRef: ApplicationRef = TestBed.get(ApplicationRef);
|
||||
|
||||
appRef.attachView(comp.componentRef.hostView);
|
||||
appRef.tick();
|
||||
expect(comp.nativeElement).toHaveText('Initial');
|
||||
|
||||
appRef.detachView(comp.componentRef.hostView);
|
||||
comp.componentInstance.name = 'New';
|
||||
appRef.tick();
|
||||
expect(appRef.viewCount).toBe(0);
|
||||
expect(comp.nativeElement).toHaveText('Initial');
|
||||
});
|
||||
|
||||
it('should detach attached views if they are destroyed', () => {
|
||||
const comp = TestBed.createComponent(MyComp);
|
||||
const appRef: ApplicationRef = TestBed.get(ApplicationRef);
|
||||
|
||||
appRef.attachView(comp.componentRef.hostView);
|
||||
comp.destroy();
|
||||
|
||||
expect(appRef.viewCount).toBe(0);
|
||||
});
|
||||
|
||||
it('should not allow to attach a view to both, a view container and the ApplicationRef',
|
||||
() => {
|
||||
const comp = TestBed.createComponent(MyComp);
|
||||
const hostView = comp.componentRef.hostView;
|
||||
const containerComp = TestBed.createComponent(ContainerComp);
|
||||
containerComp.detectChanges();
|
||||
const vc = containerComp.componentInstance.vc;
|
||||
const appRef: ApplicationRef = TestBed.get(ApplicationRef);
|
||||
|
||||
vc.insert(hostView);
|
||||
expect(() => appRef.attachView(hostView))
|
||||
.toThrowError('This view is already attached to a ViewContainer!');
|
||||
vc.detach(0);
|
||||
|
||||
appRef.attachView(hostView);
|
||||
expect(() => vc.insert(hostView))
|
||||
.toThrowError('This view is already attached directly to the ApplicationRef!');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Injectable, RenderComponentType, Renderer, RootRenderer} from '@angular/core';
|
||||
import {Component, ContentChild, Injectable, Input, RenderComponentType, Renderer, RootRenderer, TemplateRef} from '@angular/core';
|
||||
import {DebugDomRenderer} from '@angular/core/src/debug/debug_renderer';
|
||||
import {DirectRenderer} from '@angular/core/src/render/api';
|
||||
import {TestBed, inject} from '@angular/core/testing';
|
||||
@ -125,6 +125,46 @@ export function main() {
|
||||
const projectedNode = childHostEl.childNodes[1];
|
||||
expect(directRenderer.appendChild).toHaveBeenCalledWith(projectedNode, childHostEl);
|
||||
});
|
||||
|
||||
it('should support using structural directives with ngTemplateOutlet', () => {
|
||||
@Component({
|
||||
template:
|
||||
'<child [templateCtx]="templateCtx"><template let-shown="shown" #tpl><span *ngIf="shown">hello</span></template></child>'
|
||||
})
|
||||
class Parent {
|
||||
templateCtx = {shown: false};
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'child',
|
||||
template:
|
||||
'(<template [ngTemplateOutlet]="templateRef" [ngOutletContext]="templateCtx"></template>)'
|
||||
})
|
||||
class Child {
|
||||
@Input()
|
||||
templateCtx: any;
|
||||
|
||||
@ContentChild('tpl')
|
||||
templateRef: TemplateRef<any>;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Parent, Child]});
|
||||
|
||||
let fixture = TestBed.createComponent(Parent);
|
||||
fixture.componentInstance.templateCtx.shown = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('()');
|
||||
fixture.destroy();
|
||||
|
||||
fixture = TestBed.createComponent(Parent);
|
||||
fixture.componentInstance.templateCtx.shown = true;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('(hello)');
|
||||
|
||||
fixture.componentInstance.templateCtx.shown = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('()');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -89,6 +89,25 @@ export function main() {
|
||||
const p = reflector.parameters(ClassWithoutDecorators);
|
||||
expect(p.length).toEqual(2);
|
||||
});
|
||||
|
||||
// See https://github.com/angular/tsickle/issues/261
|
||||
it('should read forwardRef down-leveled type', () => {
|
||||
class Dep {}
|
||||
class ForwardLegacy {
|
||||
constructor(d: Dep) {}
|
||||
// Older tsickle had a bug: wrote a forward reference
|
||||
static ctorParameters = [{type: Dep}];
|
||||
}
|
||||
expect(reflector.parameters(ForwardLegacy)).toEqual([[Dep]]);
|
||||
class Forward {
|
||||
constructor(d: Dep) {}
|
||||
// Newer tsickle generates a functionClosure
|
||||
static ctorParameters = () => [{type: ForwardDep}];
|
||||
}
|
||||
class ForwardDep {}
|
||||
expect(reflector.parameters(Forward)).toEqual([[ForwardDep]]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('propMetadata', () => {
|
||||
|
@ -5,8 +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 {AnimationPlayer} from '@angular/core';
|
||||
import {AUTO_STYLE, AnimationPlayer} from '@angular/core';
|
||||
|
||||
export class MockAnimationPlayer implements AnimationPlayer {
|
||||
private _onDoneFns: Function[] = [];
|
||||
@ -16,8 +15,21 @@ export class MockAnimationPlayer implements AnimationPlayer {
|
||||
private _started = false;
|
||||
|
||||
public parentPlayer: AnimationPlayer = null;
|
||||
public previousStyles: {[styleName: string]: string | number} = {};
|
||||
|
||||
public log: any[] /** TODO #9100 */ = [];
|
||||
public log: any[] = [];
|
||||
|
||||
constructor(
|
||||
public startingStyles: {[key: string]: string | number} = {},
|
||||
public keyframes: Array<[number, {[style: string]: string | number}]> = [],
|
||||
previousPlayers: AnimationPlayer[] = []) {
|
||||
previousPlayers.forEach(player => {
|
||||
if (player instanceof MockAnimationPlayer) {
|
||||
const styles = player._captureStyles();
|
||||
Object.keys(styles).forEach(prop => this.previousStyles[prop] = styles[prop]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _onFinish(): void {
|
||||
if (!this._finished) {
|
||||
@ -67,6 +79,32 @@ export class MockAnimationPlayer implements AnimationPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
setPosition(p: any /** TODO #9100 */): void {}
|
||||
setPosition(p: number): void {}
|
||||
getPosition(): number { return 0; }
|
||||
|
||||
private _captureStyles(): {[styleName: string]: string | number} {
|
||||
const captures: {[prop: string]: string | number} = {};
|
||||
|
||||
if (this.hasStarted()) {
|
||||
// when assembling the captured styles, it's important that
|
||||
// we build the keyframe styles in the following order:
|
||||
// {startingStyles, ... other styles within keyframes, ... previousStyles }
|
||||
Object.keys(this.startingStyles).forEach(prop => {
|
||||
captures[prop] = this.startingStyles[prop];
|
||||
});
|
||||
|
||||
this.keyframes.forEach(kf => {
|
||||
const [offset, styles] = kf;
|
||||
const newStyles: {[prop: string]: string | number} = {};
|
||||
Object.keys(styles).forEach(
|
||||
prop => { captures[prop] = this._finished ? styles[prop] : AUTO_STYLE; });
|
||||
});
|
||||
}
|
||||
|
||||
Object.keys(this.previousStyles).forEach(prop => {
|
||||
captures[prop] = this.previousStyles[prop];
|
||||
});
|
||||
|
||||
return captures;
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
"../../system.d.ts"
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"annotateForClosureCompiler": true,
|
||||
"strictMetadataEmit": true
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,9 @@
|
||||
writeScriptTag('/vendor/system.js');
|
||||
writeScriptTag('/vendor/Reflect.js');
|
||||
writeScriptTag('/_common/system-config.js');
|
||||
if (location.pathname.indexOf('/upgrade/') != -1) {
|
||||
writeScriptTag('/vendor/angular.js');
|
||||
}
|
||||
|
||||
function writeScriptTag(scriptUrl: string, onload: string = '') {
|
||||
document.write('<script src="' + scriptUrl + '" onload="' + onload + '"></script>');
|
||||
|
@ -6,6 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import {AppModule} from './module';
|
||||
import * as module from './module';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||
if (module.AppModule) {
|
||||
platformBrowserDynamic().bootstrapModule(module.AppModule);
|
||||
}
|
@ -19,6 +19,7 @@ System.config({
|
||||
'/vendor/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
|
||||
'@angular/router': '/vendor/@angular/router/bundles/router.umd.js',
|
||||
'@angular/upgrade': '/vendor/@angular/upgrade/bundles/upgrade.umd.js',
|
||||
'@angular/upgrade/static': '/vendor/@angular/upgrade/bundles/upgrade-static.umd.js',
|
||||
'rxjs': '/vendor/rxjs',
|
||||
},
|
||||
packages: {
|
||||
|
@ -20,6 +20,7 @@ mkdir $DIST/vendor/
|
||||
ln -s ../../../dist/packages-dist/ $DIST/vendor/@angular
|
||||
|
||||
for FILE in \
|
||||
../../../node_modules/angular/angular.js \
|
||||
../../../node_modules/zone.js/dist/zone.js \
|
||||
../../../node_modules/systemjs/dist/system.js \
|
||||
../../../node_modules/reflect-metadata/Reflect.js \
|
||||
@ -35,4 +36,6 @@ for MODULE in `find . -name module.ts`; do
|
||||
cp _common/*.html $FINAL_DIR_PATH
|
||||
cp $DIST/_common/*.js $FINAL_DIR_PATH
|
||||
cp $DIST/_common/*.js.map $FINAL_DIR_PATH
|
||||
|
||||
find `dirname $MODULE` -name \*.css -exec cp {} $FINAL_DIR_PATH \;
|
||||
done
|
||||
|
@ -18,7 +18,7 @@
|
||||
"target": "es5",
|
||||
"lib": ["es2015", "dom"],
|
||||
"skipLibCheck": true,
|
||||
"types": ["jasmine", "node"]
|
||||
"types": ["jasmine", "node", "angularjs"]
|
||||
},
|
||||
"include": [
|
||||
"./_common/*.ts",
|
||||
|
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {browser, by, element} from 'protractor';
|
||||
import {verifyNoBrowserErrors} from '../../../../_common/e2e_util';
|
||||
|
||||
function loadPage(url: string) {
|
||||
browser.ng12Hybrid = true;
|
||||
browser.rootEl = 'example-app';
|
||||
browser.get(url);
|
||||
}
|
||||
|
||||
describe('upgrade(static)', () => {
|
||||
beforeEach(() => { loadPage('/upgrade/static/ts/'); });
|
||||
afterEach(verifyNoBrowserErrors);
|
||||
|
||||
it('should render the `ng2-heroes` component', () => {
|
||||
expect(element(by.css('h1')).getText()).toEqual('Heroes');
|
||||
expect(element.all(by.css('p')).get(0).getText()).toEqual('There are 3 heroes.');
|
||||
});
|
||||
|
||||
it('should render 3 ng1-hero components', () => {
|
||||
const heroComponents = element.all(by.css('ng1-hero'));
|
||||
expect(heroComponents.count()).toEqual(3);
|
||||
});
|
||||
|
||||
it('should add a new hero when the "Add Hero" button is pressed', () => {
|
||||
const addHeroButton = element.all(by.css('button')).last();
|
||||
expect(addHeroButton.getText()).toEqual('Add Hero');
|
||||
addHeroButton.click();
|
||||
const heroComponents = element.all(by.css('ng1-hero'));
|
||||
expect(heroComponents.last().element(by.css('h2')).getText()).toEqual('Kamala Khan');
|
||||
});
|
||||
|
||||
it('should remove a hero when the "Remove" button is pressed', () => {
|
||||
let firstHero = element.all(by.css('ng1-hero')).get(0);
|
||||
expect(firstHero.element(by.css('h2')).getText()).toEqual('Superman');
|
||||
|
||||
const removeHeroButton = firstHero.element(by.css('button'));
|
||||
expect(removeHeroButton.getText()).toEqual('Remove');
|
||||
removeHeroButton.click();
|
||||
|
||||
const heroComponents = element.all(by.css('ng1-hero'));
|
||||
expect(heroComponents.count()).toEqual(2);
|
||||
|
||||
firstHero = element.all(by.css('ng1-hero')).get(0);
|
||||
expect(firstHero.element(by.css('h2')).getText()).toEqual('Wonder Woman');
|
||||
});
|
||||
});
|
184
modules/@angular/examples/upgrade/static/ts/module.ts
Normal file
184
modules/@angular/examples/upgrade/static/ts/module.ts
Normal file
@ -0,0 +1,184 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {Component, Directive, DoCheck, ElementRef, EventEmitter, Inject, Injectable, Injector, Input, NgModule, OnChanges, OnDestroy, OnInit, Output, SimpleChanges} from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import {UpgradeComponent, UpgradeModule, downgradeComponent, downgradeInjectable} from '@angular/upgrade/static';
|
||||
|
||||
interface Hero {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
// #docregion Angular 2 Stuff
|
||||
// #docregion ng2-heroes
|
||||
// This Angular 2 component will be "downgraded" to be used in Angular 1
|
||||
@Component({
|
||||
selector: 'ng2-heroes',
|
||||
// This template uses the upgraded `ng1-hero` component
|
||||
// Note that because its element is compiled by Angular 2+ we must use camelCased attribute names
|
||||
template: `<h1>Heroes</h1>
|
||||
<p><ng-content></ng-content></p>
|
||||
<div *ngFor="let hero of heroes">
|
||||
<ng1-hero [hero]="hero" (onRemove)="removeHero.emit(hero)"><strong>Super Hero</strong></ng1-hero>
|
||||
</div>
|
||||
<button (click)="addHero.emit()">Add Hero</button>`,
|
||||
})
|
||||
class Ng2HeroesComponent {
|
||||
@Input() heroes: Hero[];
|
||||
@Output() addHero = new EventEmitter();
|
||||
@Output() removeHero = new EventEmitter();
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
// #docregion ng2-heroes-service
|
||||
// This Angular 2 service will be "downgraded" to be used in Angular 1
|
||||
@Injectable()
|
||||
class HeroesService {
|
||||
heroes: Hero[] = [
|
||||
{name: 'superman', description: 'The man of steel'},
|
||||
{name: 'wonder woman', description: 'Princess of the Amazons'},
|
||||
{name: 'thor', description: 'The hammer-wielding god'}
|
||||
];
|
||||
|
||||
// #docregion use-ng1-upgraded-service
|
||||
constructor(@Inject('titleCase') titleCase: (v: string) => string) {
|
||||
// Change all the hero names to title case, using the "upgraded" Angular 1 service
|
||||
this.heroes.forEach((hero: Hero) => hero.name = titleCase(hero.name));
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
addHero() {
|
||||
this.heroes =
|
||||
this.heroes.concat([{name: 'Kamala Khan', description: 'Epic shape-shifting healer'}]);
|
||||
}
|
||||
|
||||
removeHero(hero: Hero) { this.heroes = this.heroes.filter((item: Hero) => item !== hero); }
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
// #docregion ng1-hero-wrapper
|
||||
// This Angular 2 directive will act as an interface to the "upgraded" Angular 1 component
|
||||
@Directive({selector: 'ng1-hero'})
|
||||
class Ng1HeroComponentWrapper extends UpgradeComponent implements OnInit, OnChanges, DoCheck,
|
||||
OnDestroy {
|
||||
// The names of the input and output properties here must match the names of the
|
||||
// `<` and `&` bindings in the Angular 1 component that is being wrapped
|
||||
@Input() hero: Hero;
|
||||
@Output() onRemove: EventEmitter<void>;
|
||||
constructor(@Inject(ElementRef) elementRef: ElementRef, @Inject(Injector) injector: Injector) {
|
||||
// We must pass the name of the directive as used by Angular 1 to the super
|
||||
super('ng1Hero', elementRef, injector);
|
||||
}
|
||||
|
||||
// For this class to work when compiled with AoT, we must implement these lifecycle hooks
|
||||
// because the AoT compiler will not realise that the super class implements them
|
||||
ngOnInit() { super.ngOnInit(); }
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) { super.ngOnChanges(changes); }
|
||||
|
||||
ngDoCheck() { super.ngDoCheck(); }
|
||||
|
||||
ngOnDestroy() { super.ngOnDestroy(); }
|
||||
}
|
||||
// #enddocregion
|
||||
|
||||
// #docregion ng2-module
|
||||
// This NgModule represents the Angular 2 pieces of the application
|
||||
@NgModule({
|
||||
declarations: [Ng2HeroesComponent, Ng1HeroComponentWrapper],
|
||||
providers: [
|
||||
HeroesService,
|
||||
// #docregion upgrade-ng1-service
|
||||
// Register an Angular 2+ provider whose value is the "upgraded" Angular 1 service
|
||||
{provide: 'titleCase', useFactory: (i: any) => i.get('titleCase'), deps: ['$injector']}
|
||||
// #enddocregion
|
||||
],
|
||||
// All components that are to be "downgraded" must be declared as `entryComponents`
|
||||
entryComponents: [Ng2HeroesComponent],
|
||||
// We must import `UpgradeModule` to get access to the Angular 1 core services
|
||||
imports: [BrowserModule, UpgradeModule]
|
||||
})
|
||||
class Ng2AppModule {
|
||||
ngDoBootstrap() { /* this is a placeholder to stop the boostrapper from complaining */
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
||||
// #enddocregion
|
||||
|
||||
|
||||
// #docregion Angular 1 Stuff
|
||||
// #docregion ng1-module
|
||||
// This Angular 1 module represents the Angular 1 pieces of the application
|
||||
const ng1AppModule = angular.module('ng1AppModule', []);
|
||||
// #enddocregion
|
||||
|
||||
// #docregion ng1-hero
|
||||
// This Angular 1 component will be "upgraded" to be used in Angular 2+
|
||||
ng1AppModule.component('ng1Hero', {
|
||||
bindings: {hero: '<', onRemove: '&'},
|
||||
transclude: true,
|
||||
template: `<div class="title" ng-transclude></div>
|
||||
<h2>{{ $ctrl.hero.name }}</h2>
|
||||
<p>{{ $ctrl.hero.description }}</p>
|
||||
<button ng-click="$ctrl.onRemove()">Remove</button>`
|
||||
});
|
||||
// #enddocregion
|
||||
|
||||
// #docregion ng1-title-case-service
|
||||
// This Angular 1 service will be "upgraded" to be used in Angular 2+
|
||||
ng1AppModule.factory(
|
||||
'titleCase',
|
||||
() => (value: string) => value.replace(/((^|\s)[a-z])/g, (_, c) => c.toUpperCase()));
|
||||
// #enddocregion
|
||||
|
||||
// #docregion downgrade-ng2-heroes-service
|
||||
// Register an Angular 1 service, whose value is the "downgraded" Angular 2+ injectable.
|
||||
ng1AppModule.factory('heroesService', downgradeInjectable(HeroesService));
|
||||
// #enddocregion
|
||||
|
||||
// #docregion ng2-heroes-wrapper
|
||||
// This is directive will act as the interface to the "downgraded" Angular 2+ component
|
||||
ng1AppModule.directive(
|
||||
'ng2Heroes',
|
||||
downgradeComponent(
|
||||
// The inputs and outputs here must match the relevant names of the properties on the
|
||||
// "downgraded" component
|
||||
{component: Ng2HeroesComponent, inputs: ['heroes'], outputs: ['addHero', 'removeHero']}));
|
||||
// #enddocregion
|
||||
|
||||
// #docregion example-app
|
||||
// This is our top level application component
|
||||
ng1AppModule.component('exampleApp', {
|
||||
// We inject the "downgraded" HeroesService into this Angular 1 component
|
||||
// (We don't need the `HeroesService` type for Angular 1 DI - it just helps with TypeScript
|
||||
// compilation)
|
||||
controller: [
|
||||
'heroesService', function(heroesService: HeroesService) { this.heroesService = heroesService; }
|
||||
],
|
||||
// This template make use of the downgraded `ng2-heroes` component
|
||||
// Note that because its element is compiled by Angular 1 we must use kebab-case attributes for
|
||||
// inputs and outputs
|
||||
template: `<link rel="stylesheet" href="./styles.css">
|
||||
<ng2-heroes [heroes]="$ctrl.heroesService.heroes" (add-hero)="$ctrl.heroesService.addHero()" (remove-hero)="$ctrl.heroesService.removeHero($event)">
|
||||
There are {{ $ctrl.heroesService.heroes.length }} heroes.
|
||||
</ng2-heroes>`
|
||||
});
|
||||
// #enddocregion
|
||||
// #enddocregion
|
||||
|
||||
|
||||
// #docregion bootstrap
|
||||
// First we bootstrap the Angular 2 HybridModule
|
||||
// (We are using the dynamic browser platform as this example has not been compiled AoT)
|
||||
platformBrowserDynamic().bootstrapModule(Ng2AppModule).then(ref => {
|
||||
// Once Angular 2 bootstrap is complete then we bootstrap the Angular 1 module
|
||||
const upgrade = ref.injector.get(UpgradeModule) as UpgradeModule;
|
||||
upgrade.bootstrap(document.body, [ng1AppModule.name]);
|
||||
});
|
||||
// #enddocregion
|
17
modules/@angular/examples/upgrade/static/ts/styles.css
Normal file
17
modules/@angular/examples/upgrade/static/ts/styles.css
Normal file
@ -0,0 +1,17 @@
|
||||
ng2-heroes {
|
||||
border: solid black 2px;
|
||||
display: block;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
ng1-hero {
|
||||
border: solid green 2px;
|
||||
margin-top: 5px;
|
||||
padding: 5px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.title {
|
||||
background-color: blue;
|
||||
color: white;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user