build: remove references to `tsc-wrapped` (#19298)

With this commit `ngc` is used instead of `tsc-wrapped` for
collecting metadata and tsickle rewriting and `tsc-wrapped`
is removed from the repository.

`@angular/tsc-wrapped@5` is now deprecated and is no longer
used, updated, or maintained as part as of Angular 5.x.x.

`@angular/tsc-wrapped@4` is still maintained and required by
Angular 4.x.x and will be maintained as long as 4.x.x is in
LTS.

PR Close #19298
This commit is contained in:
Chuck Jazdzewski 2017-09-20 09:54:47 -07:00 committed by Igor Minar
parent 4c73b52d5c
commit f96142cd7c
104 changed files with 336 additions and 6594 deletions

View File

@ -135,7 +135,6 @@ groups:
compiler-cli:
conditions:
files:
- "packages/tsc-wrapped/*"
- "packages/compiler-cli/*"
- "packages/bazel/*"
users:

View File

@ -221,7 +221,6 @@ The following is the list of supported scopes:
* **platform-webworker-dynamic**
* **router**
* **upgrade**
* **tsc-wrapped**
There are currently a few exceptions to the "use package name" rule:

View File

@ -66,7 +66,7 @@ You can think of `.metadata.json` as a diagram of the overall structure of a dec
<div class="l-sub-section">
Angular's [schema.ts](https://github.com/angular/angular/blob/master/packages/tsc-wrapped/src/schema.ts)
Angular's [schema.ts](https://github.com/angular/angular/blob/master/packages/compiler-cli/src/metadata/schema.ts)
describes the JSON format as a collection of TypeScript interfaces.
</div>

View File

@ -14,7 +14,6 @@
"@angular/platform-browser-dynamic",
"@angular/platform-server",
"@angular/router",
"@angular/tsc-wrapped",
"@angular/upgrade",
"angular-in-memory-web-api",
"core-js",

View File

@ -41,7 +41,6 @@ const ANGULAR_PACKAGES = [
'platform-browser-dynamic',
'platform-server',
'router',
'tsc-wrapped',
'upgrade',
];

View File

@ -26,13 +26,11 @@ describe('example-boilerplate tool', () => {
it('should override the Angular node_modules with the locally built Angular packages if `useLocal` is true', () => {
const numberOfAngularPackages = 12;
const numberOfAngularToolsPackages = 1;
exampleBoilerPlate.add(true);
expect(exampleBoilerPlate.overridePackage).toHaveBeenCalledTimes(numberOfAngularPackages + numberOfAngularToolsPackages);
expect(exampleBoilerPlate.overridePackage).toHaveBeenCalledTimes(numberOfAngularPackages);
// for example
expect(exampleBoilerPlate.overridePackage).toHaveBeenCalledWith(path.resolve(__dirname, '../../../dist/packages-dist'), 'common');
expect(exampleBoilerPlate.overridePackage).toHaveBeenCalledWith(path.resolve(__dirname, '../../../dist/packages-dist'), 'core');
expect(exampleBoilerPlate.overridePackage).toHaveBeenCalledWith(path.resolve(__dirname, '../../../dist/packages-dist'), 'tsc-wrapped');
});
it('should process all the example folders', () => {

View File

@ -23,7 +23,6 @@
"@angular/platform-browser-dynamic": "~4.3.1",
"@angular/platform-server": "~4.3.1",
"@angular/router": "~4.3.1",
"@angular/tsc-wrapped": "~4.3.1",
"@angular/upgrade": "~4.3.1",
"angular-in-memory-web-api": "~0.4.0",
"core-js": "^2.4.1",

View File

@ -25,9 +25,12 @@ PACKAGES=(core
language-service
benchpress)
TSC_PACKAGES=(compiler-cli
language-service
benchpress)
NODE_PACKAGES=(compiler-cli
benchpress
tsc-wrapped)
benchpress)
BUILD_ALL=true
BUNDLE=true
@ -38,6 +41,7 @@ BUILD_EXAMPLES=true
COMPILE_SOURCE=true
TYPECHECK_ALL=true
BUILD_TOOLS=true
export NODE_PATH=${NODE_PATH:-}:${currentDir}/dist/tools
for ARG in "$@"; do
case "$ARG" in
@ -156,7 +160,7 @@ runRollup() {
if [[ -f "${1}/rollup.config.js" ]]; then
cd ${1}
echo "====== $ROLLUP -c ${1}/rollup.config.js"
echo "====== $ROLLUP -c ${1}/rollup.config.js --sourcemap"
$ROLLUP -c rollup.config.js --sourcemap >/dev/null 2>&1
# Recurse for sub directories
@ -216,11 +220,12 @@ minify() {
# None
#######################################
compilePackage() {
echo "====== [${3}]: COMPILING: ${NGC} -p ${1}/tsconfig-build.json"
# For NODE_PACKAGES items (not getting rolled up)
if containsElement "${3}" "${NODE_PACKAGES[@]}"; then
# For TSC_PACKAGES items
if containsElement "${3}" "${TSC_PACKAGES[@]}"; then
echo "====== [${3}]: COMPILING: ${TSC} -p ${1}/tsconfig-build.json"
$TSC -p ${1}/tsconfig-build.json
else
echo "====== [${3}]: COMPILING: ${NGC} -p ${1}/tsconfig-build.json"
local package_name=$(basename "${2}")
$NGC -p ${1}/tsconfig-build.json
echo "====== Create ${1}/../${package_name}.d.ts re-export file for tsickle"
@ -247,9 +252,15 @@ compilePackage() {
# None
#######################################
compilePackageES5() {
echo "====== [${3}]: COMPILING: ${NGC} -p ${1}/tsconfig-build.json --target es5 -d false --outDir ${2} --importHelpers true --sourceMap"
local package_name=$(basename "${2}")
$NGC -p ${1}/tsconfig-build.json --target es5 -d false --outDir ${2} --importHelpers true --sourceMap
if containsElement "${3}" "${TSC_PACKAGES[@]}"; then
echo "====== [${3}]: COMPILING: ${TSC} -p ${1}/tsconfig-build.json --target es5 -d false --outDir ${2} --importHelpers true --sourceMap"
local package_name=$(basename "${2}")
$TSC -p ${1}/tsconfig-build.json --target es5 -d false --outDir ${2} --importHelpers true --sourceMap
else
echo "====== [${3}]: COMPILING: ${NGC} -p ${1}/tsconfig-build.json --target es5 -d false --outDir ${2} --importHelpers true --sourceMap"
local package_name=$(basename "${2}")
$NGC -p ${1}/tsconfig-build.json --target es5 -d false --outDir ${2} --importHelpers true --sourceMap
fi
for DIR in ${1}/* ; do
[ -d "${DIR}" ] || continue
@ -315,7 +326,7 @@ echo "====== BUILDING: Version ${VERSION}"
N="
"
TSC=`pwd`/node_modules/.bin/tsc
NGC="node --max-old-space-size=3000 `pwd`/dist/packages-dist/tsc-wrapped/src/main"
NGC="node --max-old-space-size=3000 `pwd`/dist/tools/@angular/compiler-cli/src/main"
UGLIFYJS=`pwd`/node_modules/.bin/uglifyjs
TSCONFIG=./tools/tsconfig.json
ROLLUP=`pwd`/node_modules/.bin/rollup
@ -394,14 +405,12 @@ if [[ ${BUILD_ALL} == true ]]; then
fi
if [[ ${BUILD_TOOLS} == true || ${BUILD_ALL} == true ]]; then
echo "====== (tsc-wrapped)COMPILING: \$(npm bin)/tsc -p packages/tsc-wrapped/tsconfig.json ====="
$(npm bin)/tsc -p packages/tsc-wrapped/tsconfig.json
echo "====== (tsc-wrapped)COMPILING: \$(npm bin)/tsc -p packages/tsc-wrapped/tsconfig-build.json ====="
$(npm bin)/tsc -p packages/tsc-wrapped/tsconfig-build.json
cp ./packages/tsc-wrapped/package.json ./dist/packages-dist/tsc-wrapped
cp ./packages/tsc-wrapped/README.md ./dist/packages-dist/tsc-wrapped
updateVersionReferences dist/packages-dist/tsc-wrapped
echo "====== (compiler)COMPILING: \$(npm bin)/tsc -p packages/compiler/tsconfig-tools.json"
$(npm bin)/tsc -p packages/compiler/tsconfig-tools.json
echo "====== (compiler)COMPILING: \$(npm bin)/tsc -p packages/compiler-cli/tsconfig-tools.json"
$(npm bin)/tsc -p packages/compiler-cli/tsconfig-tools.json
mkdir -p ./dist/packages-dist
rsync -a packages/bazel/ ./dist/packages-dist/bazel
# Remove BEGIN-INTERNAL...END-INTERAL blocks
# https://stackoverflow.com/questions/24175271/how-can-i-match-multi-line-patterns-in-the-command-line-with-perl-style-regex

View File

@ -10,7 +10,6 @@
"@angular/core": "file:../../dist/packages-dist/core",
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
"@angular/tsc-wrapped": "file:../../dist/packages-dist/tsc-wrapped",
"google-closure-compiler": "git+https://github.com/alexeagle/closure-compiler.git#packagejson.dist",
"rxjs": "file:../../node_modules/rxjs",
"typescript": "file:../../node_modules/typescript",

View File

@ -10,7 +10,6 @@
"@angular/core": "file:../../dist/packages-dist/core",
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
"@angular/tsc-wrapped": "file:../../dist/packages-dist/tsc-wrapped",
"google-closure-compiler": "git+https://github.com/alexeagle/closure-compiler.git#packagejson.dist",
"rxjs": "file:../../node_modules/rxjs",
"typescript": "file:../../node_modules/typescript",

View File

@ -12,7 +12,6 @@
"@angular/language-service": "file:../../dist/packages-dist/language-service",
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
"@angular/tsc-wrapped": "file:../../dist/packages-dist/tsc-wrapped",
"@types/minimist": "^1.2.0",
"@types/node": "^7.0.5",
"minimist": "^1.2.0",

View File

@ -15,7 +15,6 @@
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
"@angular/router": "file:../../dist/packages-dist/router",
"@angular/tsc-wrapped": "file:../../dist/packages-dist/tsc-wrapped",
"@angular/upgrade": "file:../../dist/packages-dist/upgrade",
"@types/jasmine": "2.5.41",
"rxjs": "file:../../node_modules/rxjs",

View File

@ -61,7 +61,6 @@ module.exports = function(config) {
'dist/all/@angular/examples/**/e2e_test/*',
'dist/all/@angular/language-service/**',
'dist/all/@angular/router/test/**',
'dist/all/@angular/tsc-wrapped/**',
'dist/all/@angular/platform-browser/testing/e2e_util.js',
'dist/all/angular1_router.js',
'dist/examples/**/e2e_test/**',

View File

@ -12,9 +12,7 @@
"paths": {
"selenium-webdriver": ["../node_modules/@types/selenium-webdriver/index.d.ts"],
"rxjs/*": ["../node_modules/rxjs/*"],
"@angular/*": ["../dist/all/@angular/*"],
"@angular/tsc-wrapped": ["../dist/packages-dist/tsc-wrapped"],
"@angular/tsc-wrapped/*": ["../dist/packages-dist/tsc-wrapped/*"]
"@angular/*": ["../dist/all/@angular/*"]
},
"rootDir": ".",
"inlineSourceMap": true,
@ -32,5 +30,8 @@
"benchmarks_external",
"payload_tests",
"rollup-test"
]
],
"angularCompilerOptions": {
"skipTemplateCodegen": true
}
}

View File

@ -5,7 +5,8 @@
"baseUrl": ".",
"rootDir": "../../",
"paths": {
"@angular/animations": ["../../../../dist/packages/animations"]
"@angular/animations": ["../../../../dist/packages/animations"],
"@angular/core": ["../../../../dist/packages/core"]
},
"outDir": "../../../../dist/packages/animations"
},
@ -19,7 +20,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "testing.js",
"flatModuleId": "@angular/animations/browser/testing"
}

View File

@ -5,7 +5,8 @@
"baseUrl": ".",
"rootDir": "../",
"paths": {
"@angular/animations": ["../../../dist/packages/animations"]
"@angular/animations": ["../../../dist/packages/animations"],
"@angular/core": ["../../../dist/packages/core"]
},
"outDir": "../../../dist/packages/animations"
},
@ -18,7 +19,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "browser.js",
"flatModuleId": "@angular/animations/browser"
}

View File

@ -4,7 +4,9 @@
"compilerOptions": {
"baseUrl": ".",
"rootDir": ".",
"paths": {},
"paths": {
"@angular/core": ["../../dist/packages/core"]
},
"outDir": "../../dist/packages/animations"
},
@ -16,7 +18,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "animations.js",
"flatModuleId": "@angular/animations"
}

View File

@ -19,7 +19,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "testing.js",
"flatModuleId": "@angular/common/http/testing"
}

View File

@ -17,7 +17,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "http.js",
"flatModuleId": "@angular/common/http"
}

View File

@ -19,5 +19,8 @@
},
"exclude": [
"./closure-locale.ts"
]
],
"angularCompilerOptions": {
"skipTemplateCodegen": true
}
}

View File

@ -18,7 +18,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "testing.js",
"flatModuleId": "@angular/common/testing"
}

View File

@ -17,7 +17,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "common.js",
"flatModuleId": "@angular/common"
}

View File

@ -9,8 +9,7 @@ ts_library(
]),
module_name = "@angular/compiler-cli",
deps = [
"//packages/compiler",
"//packages/tsc-wrapped",
"//packages/compiler"
],
tsconfig = ":tsconfig-build.json",
)

View File

@ -112,7 +112,7 @@ with the one already used in the plugin for TypeScript typechecking and emit.
## Design
At a high level, this program
- collects static metadata about the sources using the `tsc-wrapped` package
- collects static metadata about the sources
- uses the `OfflineCompiler` from `@angular/compiler` to codegen additional `.ts` files
- these `.ts` files are written to the `genDir` path, then compiled together with the application.
@ -130,7 +130,7 @@ $ ./scripts/ci/offline_compiler_test.sh
# Recompile @angular/core module (needs to use tsc-ext to keep the metadata)
$ export NODE_PATH=${NODE_PATH}:$(pwd)/dist/all:$(pwd)/dist/tools
$ node dist/tools/@angular/tsc-wrapped/src/main -p packages/core/tsconfig-build.json
$ node dist/tools/@angular/compiler-cli/src/main -p packages/core/tsconfig-build.json
# Iterate on the test
$ cd /tmp/wherever/e2e_test.1464388257/

View File

@ -17,10 +17,7 @@ var rxjsLocation = normalize('../../node_modules/rxjs');
var tslibLocation = normalize('../../node_modules/tslib');
var esm = 'esm/';
var locations = {
'tsc-wrapped': normalize('../../dist/tools/@angular') + '/',
'compiler-cli': normalize('../../dist/packages') + '/'
};
var locations = {'compiler-cli': normalize('../../dist/packages') + '/'};
var esm_suffixes = {};
@ -30,11 +27,6 @@ function normalize(fileName) {
function resolve(id, from) {
// console.log('Resolve id:', id, 'from', from)
if (id == '@angular/tsc-wrapped') {
// Hack to restrict the import to not include the index of @angular/tsc-wrapped so we don't
// rollup tsickle.
return locations['tsc-wrapped'] + 'tsc-wrapped/src/collector.js';
}
var match = m.exec(id);
if (match) {
var packageName = match[1];

View File

@ -14,8 +14,7 @@ import 'reflect-metadata';
import * as path from 'path';
import * as ts from 'typescript';
import * as assert from 'assert';
import {tsc} from '@angular/tsc-wrapped/src/tsc';
import {__NGTOOLS_PRIVATE_API_2} from '@angular/compiler-cli';
import {__NGTOOLS_PRIVATE_API_2, readConfiguration} from '@angular/compiler-cli';
const glob = require('glob');
@ -50,14 +49,14 @@ function codeGenTest(forceError = false) {
const readResources: string[] = [];
const wroteFiles: string[] = [];
const config = tsc.readConfiguration(project, basePath);
const delegateHost = ts.createCompilerHost(config.parsed.options, true);
const config = readConfiguration(project);
const delegateHost = ts.createCompilerHost(config.options, true);
const host: ts.CompilerHost = Object.assign(
{}, delegateHost,
{writeFile: (fileName: string, ...rest: any[]) => { wroteFiles.push(fileName); }});
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
const program = ts.createProgram(config.rootNames, config.options, host);
config.ngOptions.basePath = basePath;
config.options.basePath = basePath;
console.log(`>>> running codegen for ${project}`);
if (forceError) {
@ -66,9 +65,9 @@ function codeGenTest(forceError = false) {
return __NGTOOLS_PRIVATE_API_2
.codeGen({
basePath,
compilerOptions: config.parsed.options, program, host,
compilerOptions: config.options, program, host,
angularCompilerOptions: config.ngOptions,
angularCompilerOptions: config.options,
// i18n options.
i18nFormat: 'xlf',
@ -129,21 +128,21 @@ function i18nTest() {
const readResources: string[] = [];
const wroteFiles: string[] = [];
const config = tsc.readConfiguration(project, basePath);
const delegateHost = ts.createCompilerHost(config.parsed.options, true);
const config = readConfiguration(project);
const delegateHost = ts.createCompilerHost(config.options, true);
const host: ts.CompilerHost = Object.assign(
{}, delegateHost,
{writeFile: (fileName: string, ...rest: any[]) => { wroteFiles.push(fileName); }});
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
const program = ts.createProgram(config.rootNames, config.options, host);
config.ngOptions.basePath = basePath;
config.options.basePath = basePath;
console.log(`>>> running i18n extraction for ${project}`);
return __NGTOOLS_PRIVATE_API_2
.extractI18n({
basePath,
compilerOptions: config.parsed.options, program, host,
angularCompilerOptions: config.ngOptions,
compilerOptions: config.options, program, host,
angularCompilerOptions: config.options,
i18nFormat: 'xlf',
locale: undefined,
outFile: undefined,
@ -193,18 +192,14 @@ function lazyRoutesTest() {
const basePath = path.join(__dirname, '../ngtools_src');
const project = path.join(basePath, 'tsconfig-build.json');
const config = tsc.readConfiguration(project, basePath);
const host = ts.createCompilerHost(config.parsed.options, true);
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
const config = readConfiguration(project);
const host = ts.createCompilerHost(config.options, true);
const program = ts.createProgram(config.rootNames, config.options, host);
config.ngOptions.basePath = basePath;
config.options.basePath = basePath;
const lazyRoutes = __NGTOOLS_PRIVATE_API_2.listLazyRoutes({
program,
host,
angularCompilerOptions: config.ngOptions,
entryModule: 'app.module#AppModule'
});
const lazyRoutes = __NGTOOLS_PRIVATE_API_2.listLazyRoutes(
{program, host, angularCompilerOptions: config.options, entryModule: 'app.module#AppModule'});
const expectations: {[route: string]: string} = {
'./lazy.module#LazyModule': 'lazy.module.ts',

View File

@ -14,8 +14,7 @@ import 'reflect-metadata';
import * as path from 'path';
import * as ts from 'typescript';
import * as assert from 'assert';
import {tsc} from '@angular/tsc-wrapped/src/tsc';
import {CompilerOptions, CodeGenerator, CompilerHostContext, NodeCompilerHostContext} from '@angular/compiler-cli';
import {CompilerOptions, CodeGenerator, CompilerHostContext, NodeCompilerHostContext, readConfiguration} from '@angular/compiler-cli';
/**
* Main method.
@ -46,10 +45,10 @@ function main() {
}
}
const config = tsc.readConfiguration(project, basePath);
config.ngOptions.basePath = basePath;
const config = readConfiguration(project);
config.options.basePath = basePath;
// This flag tells ngc do not recompile libraries.
config.ngOptions.generateCodeForLibraries = false;
config.options.generateCodeForLibraries = false;
console.log(`>>> running codegen for ${project}`);
codegen(
@ -86,21 +85,21 @@ function main() {
}
/**
* Simple adaption of tsc-wrapped main to just run codegen with a CompilerHostContext
* Simple adaption of main to just run codegen with a CompilerHostContext
*/
function codegen(
config: {parsed: ts.ParsedCommandLine, ngOptions: CompilerOptions},
config: {options: CompilerOptions, rootNames: string[]},
hostContextFactory: (host: ts.CompilerHost) => CompilerHostContext) {
const host = ts.createCompilerHost(config.parsed.options, true);
const host = ts.createCompilerHost(config.options, true);
// HACK: patch the realpath to solve symlink issue here:
// https://github.com/Microsoft/TypeScript/issues/9552
// todo(misko): remove once facade symlinks are removed
host.realpath = (path) => path;
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
const program = ts.createProgram(config.rootNames, config.options, host);
return CodeGenerator.create(config.ngOptions, {
return CodeGenerator.create(config.options, {
} as any, program, host, hostContextFactory(host)).codegen();
}

View File

@ -9,7 +9,6 @@
"ng-xi18n": "./src/extract_i18n.js"
},
"dependencies": {
"@angular/tsc-wrapped": "5.0.0-beta.7",
"reflect-metadata": "^0.1.2",
"minimist": "^1.2.0",
"tsickle": "^0.24.0",

View File

@ -431,6 +431,10 @@ function addReferencesToSourceFile(sf: ts.SourceFile, genFileNames: string[]) {
sf.referencedFiles = newReferencedFiles;
}
export function getOriginalReferences(sourceFile: ts.SourceFile): ts.FileReference[]|undefined {
return sourceFile && (sourceFile as any).originalReferencedFiles;
}
function dotRelative(from: string, to: string): string {
const rPath: string = path.relative(from, to).replace(/\\/g, '/');
return rPath.startsWith('.') ? rPath : './' + rPath;

View File

@ -15,7 +15,7 @@ import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diag
import {ModuleMetadata, createBundleIndexHost} from '../metadata/index';
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
import {TsCompilerAotCompilerTypeCheckHostAdapter} from './compiler_host';
import {TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host';
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
import {GENERATED_FILES, StructureIsReused, tsStructureIsReused} from './util';
@ -173,15 +173,34 @@ class AngularCompilerProgram implements Program {
};
}
const emitResult = emitCallback({
program: this.tsProgram,
host: this.host,
options: this.options,
writeFile: createWriteFileCallback(genFiles, this.host, outSrcMapping),
emitOnlyDtsFiles: (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS,
customTransformers: this.calculateTransforms(genFiles, customTransformers)
});
// Restore the original references before we emit so TypeScript doesn't emit
// a reference to the .d.ts file.
const augmentedReferences = new Map<ts.SourceFile, ts.FileReference[]>();
for (const sourceFile of this.tsProgram.getSourceFiles()) {
const originalReferences = getOriginalReferences(sourceFile);
if (originalReferences) {
augmentedReferences.set(sourceFile, sourceFile.referencedFiles);
sourceFile.referencedFiles = originalReferences;
}
}
let emitResult: ts.EmitResult;
try {
emitResult = emitCallback({
program: this.tsProgram,
host: this.host,
options: this.options,
writeFile: createWriteFileCallback(genFiles, this.host, outSrcMapping),
emitOnlyDtsFiles: (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS,
customTransformers: this.calculateTransforms(genFiles, customTransformers)
});
} finally {
// Restore the references back to the augmented value to ensure that the
// checks that TypeScript makes for project structure reuse will succeed.
for (const [sourceFile, references] of Array.from(augmentedReferences)) {
sourceFile.referencedFiles = references;
}
}
if (!outSrcMapping.length) {
// if no files were emitted by TypeScript, also don't emit .json files
@ -464,6 +483,7 @@ function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions {
enableSummariesForJit: true,
preserveWhitespaces: options.preserveWhitespaces,
fullTemplateTypeCheck: options.fullTemplateTypeCheck,
rootDir: options.rootDir,
};
}

View File

@ -8,6 +8,7 @@
import * as ng from '@angular/compiler-cli';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as ts from 'typescript';
@ -247,3 +248,12 @@ const LOWERING_QUICKSTART = {
export class AppModule { }
`
};
const tmpdir = process.env.TEST_TMPDIR || os.tmpdir();
function makeTempDir(): string {
const id = (Math.random() * 1000000).toFixed(0);
const dir = path.join(tmpdir, `tmp.${id}`);
fs.mkdirSync(dir);
return dir;
}

View File

@ -8,8 +8,7 @@
"baseUrl": ".",
"rootDir": ".",
"paths": {
"@angular/compiler": ["../../dist/packages/compiler"],
"@angular/tsc-wrapped": ["../../dist/packages-dist/tsc-wrapped"]
"@angular/compiler": ["../../dist/packages/compiler"]
},
"outDir": "../../dist/packages/compiler-cli"
},

View File

@ -0,0 +1,10 @@
{
"extends": "./tsconfig-build.json",
"compilerOptions": {
"outDir": "../../dist/tools/@angular/compiler-cli",
"paths": {
"@angular/compiler": ["../../dist/tools/@angular/compiler"]
}
}
}

View File

@ -27,6 +27,7 @@ import {TypeCheckCompiler} from '../view_compiler/type_check_compiler';
import {ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
import {AotCompilerHost} from './compiler_host';
import {AotCompilerOptions} from './compiler_options';
import {GeneratedFile} from './generated_file';
import {StaticReflector} from './static_reflector';
import {StaticSymbol} from './static_symbol';
@ -45,16 +46,14 @@ export class AotCompiler {
new Map<StaticSymbol, {template: TemplateAst[], pipes: CompilePipeSummary[]}>();
constructor(
private _config: CompilerConfig, private _host: AotCompilerHost,
private _reflector: StaticReflector, private _metadataResolver: CompileMetadataResolver,
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
private _viewCompiler: ViewCompiler, private _typeCheckCompiler: TypeCheckCompiler,
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
private _summaryResolver: SummaryResolver<StaticSymbol>, private _localeId: string|null,
private _translationFormat: string|null,
/** TODO(tbosch): remove this flag as it is always on in the new ngc */
private _enableSummariesForJit: boolean|null, private _symbolResolver: StaticSymbolResolver) {
}
private _config: CompilerConfig, private options: AotCompilerOptions,
private _host: AotCompilerHost, private _reflector: StaticReflector,
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _typeCheckCompiler: TypeCheckCompiler, private _ngModuleCompiler: NgModuleCompiler,
private _outputEmitter: OutputEmitter,
private _summaryResolver: SummaryResolver<StaticSymbol>,
private _symbolResolver: StaticSymbolResolver) {}
clearCache() { this._metadataResolver.clearCache(); }
@ -121,7 +120,8 @@ export class AotCompiler {
private _createNgFactoryStub(file: NgAnalyzedFile, emitFlags: StubEmitFlags): GeneratedFile[] {
const generatedFiles: GeneratedFile[] = [];
const outputCtx = this._createOutputContext(ngfactoryFilePath(file.fileName, true));
const outputCtx = this._createOutputContext(
calculateGenFileName(ngfactoryFilePath(file.fileName, true), this.options.rootDir));
file.ngModules.forEach((ngModuleMeta, ngModuleIndex) => {
// Note: the code below needs to executed for StubEmitFlags.Basic and StubEmitFlags.TypeCheck,
@ -209,8 +209,10 @@ export class AotCompiler {
}
const encapsulation =
compMeta.template !.encapsulation || this._config.defaultEncapsulation;
const outputCtx = this._createOutputContext(_stylesModuleUrl(
normalizedUrl, encapsulation === ViewEncapsulation.Emulated, fileSuffix));
const outputCtx = this._createOutputContext(calculateGenFileName(
_stylesModuleUrl(
normalizedUrl, encapsulation === ViewEncapsulation.Emulated, fileSuffix),
this.options.rootDir));
_createEmptyStub(outputCtx);
generatedFiles.push(this._codegenSourceModule(normalizedUrl, outputCtx));
});
@ -221,12 +223,13 @@ export class AotCompiler {
private _createNgSummaryStub(file: NgAnalyzedFile, emitFlags: StubEmitFlags): GeneratedFile[] {
const generatedFiles: GeneratedFile[] = [];
// note: .ngsummary.js stubs don't change when we produce type check stubs
if (!this._enableSummariesForJit || !(emitFlags & StubEmitFlags.Basic)) {
if (!this.options.enableSummariesForJit || !(emitFlags & StubEmitFlags.Basic)) {
return generatedFiles;
}
if (file.directives.length || file.injectables.length || file.ngModules.length ||
file.pipes.length || file.exportsNonSourceFiles) {
const outputCtx = this._createOutputContext(summaryForJitFileName(file.fileName, true));
const outputCtx = this._createOutputContext(
calculateGenFileName(summaryForJitFileName(file.fileName, true), this.options.rootDir));
file.ngModules.forEach(ngModule => {
// create exports that user code can reference
createForJitStub(outputCtx, ngModule.type.reference);
@ -295,7 +298,8 @@ export class AotCompiler {
const fileSuffix = splitTypescriptSuffix(srcFileUrl, true)[1];
const generatedFiles: GeneratedFile[] = [];
const outputCtx = this._createOutputContext(ngfactoryFilePath(srcFileUrl, true));
const outputCtx = this._createOutputContext(
calculateGenFileName(ngfactoryFilePath(srcFileUrl, true), this.options.rootDir));
generatedFiles.push(
...this._createSummary(srcFileUrl, directives, pipes, ngModules, injectables, outputCtx));
@ -366,7 +370,8 @@ export class AotCompiler {
metadata: this._metadataResolver.getInjectableSummary(ref) !.type
}))
];
const forJitOutputCtx = this._createOutputContext(summaryForJitFileName(srcFileName, true));
const forJitOutputCtx = this._createOutputContext(
calculateGenFileName(summaryForJitFileName(srcFileName, true), this.options.rootDir));
const {json, exportAs} = serializeSummaries(
srcFileName, forJitOutputCtx, this._summaryResolver, this._symbolResolver, symbolSummaries,
typeData);
@ -377,7 +382,7 @@ export class AotCompiler {
]));
});
const summaryJson = new GeneratedFile(srcFileName, summaryFileName(srcFileName), json);
if (this._enableSummariesForJit) {
if (this.options.enableSummariesForJit) {
return [summaryJson, this._codegenSourceModule(srcFileName, forJitOutputCtx)];
};
@ -387,18 +392,18 @@ export class AotCompiler {
private _compileModule(outputCtx: OutputContext, ngModule: CompileNgModuleMetadata): void {
const providers: CompileProviderMetadata[] = [];
if (this._localeId) {
const normalizedLocale = this._localeId.replace(/_/g, '-');
if (this.options.locale) {
const normalizedLocale = this.options.locale.replace(/_/g, '-');
providers.push({
token: createTokenForExternalReference(this._reflector, Identifiers.LOCALE_ID),
useValue: normalizedLocale,
});
}
if (this._translationFormat) {
if (this.options.i18nFormat) {
providers.push({
token: createTokenForExternalReference(this._reflector, Identifiers.TRANSLATIONS_FORMAT),
useValue: this._translationFormat
useValue: this.options.i18nFormat
});
}
@ -516,8 +521,11 @@ export class AotCompiler {
private _codegenStyles(
srcFileUrl: string, compMeta: CompileDirectiveMetadata,
stylesheetMetadata: CompileStylesheetMetadata, fileSuffix: string): GeneratedFile {
const outputCtx = this._createOutputContext(_stylesModuleUrl(
stylesheetMetadata.moduleUrl !, this._styleCompiler.needsStyleShim(compMeta), fileSuffix));
const outputCtx = this._createOutputContext(calculateGenFileName(
_stylesModuleUrl(
stylesheetMetadata.moduleUrl !, this._styleCompiler.needsStyleShim(compMeta),
fileSuffix),
this.options.rootDir));
const compiledStylesheet =
this._styleCompiler.compileStyles(outputCtx, compMeta, stylesheetMetadata);
_resolveStyleStatements(
@ -723,3 +731,17 @@ export function mergeAnalyzedFiles(analyzedFiles: NgAnalyzedFile[]): NgAnalyzedM
function mergeAndValidateNgFiles(files: NgAnalyzedFile[]): NgAnalyzedModules {
return validateAnalyzedModules(mergeAnalyzedFiles(files));
}
function calculateGenFileName(fileName: string, rootDir: string | undefined): string {
if (!rootDir) return fileName;
const fileNameParts = fileName.split(/\\|\//);
const rootDirParts = rootDir.split(/\\|\//);
if (!rootDirParts[rootDirParts.length - 1]) rootDirParts.pop();
let i = 0;
while (i < Math.min(fileNameParts.length, rootDirParts.length) &&
fileNameParts[i] === rootDirParts[i])
i++;
const result = [...rootDirParts, ...fileNameParts.slice(i)].join('/');
return result;
}

View File

@ -84,9 +84,9 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
const viewCompiler = new ViewCompiler(staticReflector);
const typeCheckCompiler = new TypeCheckCompiler(options, staticReflector);
const compiler = new AotCompiler(
config, compilerHost, staticReflector, resolver, tmplParser, new StyleCompiler(urlResolver),
viewCompiler, typeCheckCompiler, new NgModuleCompiler(staticReflector),
new TypeScriptEmitter(), summaryResolver, options.locale || null, options.i18nFormat || null,
options.enableSummariesForJit || null, symbolResolver);
config, options, compilerHost, staticReflector, resolver, tmplParser,
new StyleCompiler(urlResolver), viewCompiler, typeCheckCompiler,
new NgModuleCompiler(staticReflector), new TypeScriptEmitter(), summaryResolver,
symbolResolver);
return {compiler, reflector: staticReflector};
}

View File

@ -17,4 +17,5 @@ export interface AotCompilerOptions {
enableSummariesForJit?: boolean;
preserveWhitespaces?: boolean;
fullTemplateTypeCheck?: boolean;
rootDir?: string;
}

View File

@ -11,4 +11,4 @@
// replaces this file with production index.ts when it rewrites private symbol
// names.
export * from './public_api';
export * from './testing';

View File

@ -0,0 +1,14 @@
/**
* @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
*/
// This file is not used to build this module. It is only used during editing
// by the TypeScript language service and during build for verification. `ngc`
// replaces this file with production index.ts when it rewrites private symbol
// names.
export * from './public_api';

View File

@ -5,19 +5,20 @@
"baseUrl": ".",
"rootDir": "../",
"paths": {
"@angular/compiler": ["../../../dist/packages/compiler"]
"@angular/compiler": ["../../../dist/packages/compiler"],
"@angular/core": ["../../../dist/packages/core"]
},
"outDir": "../../../dist/packages/compiler"
},
"files": [
"public_api.ts",
"testing.ts",
"../../../node_modules/zone.js/dist/zone.js.d.ts"
],
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"flatModuleOutFile": "testing.js",
"flatModuleId": "@angular/compiler/testing"
"skipMetadataEmit": true,
"skipTemplateCodegen": true
}
}

View File

@ -11,6 +11,9 @@
"module": "es2015",
"moduleResolution": "node",
"outDir": "../../dist/packages/compiler",
"paths": {
"@angular/core": ["../../dist/packages/core"]
},
"rootDir": ".",
"sourceMap": true,
"inlineSources": true,
@ -23,5 +26,11 @@
"files": [
"index.ts",
"../../node_modules/zone.js/dist/zone.js.d.ts"
]
],
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"skipMetadataEmit": true,
"skipTemplateCodegen": true
}
}

View File

@ -0,0 +1,9 @@
{
"extends": "./tsconfig-build.json",
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"outDir": "../../dist/tools/@angular/compiler"
}
}

View File

@ -18,7 +18,8 @@
],
"angularCompilerOptions": {
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "testing.js",
"flatModuleId": "@angular/core/testing"
}

View File

@ -5,7 +5,8 @@
"baseUrl": ".",
"rootDir": ".",
"paths": {
"rxjs/*": ["../../node_modules/rxjs/*"]
"rxjs/*": ["../../node_modules/rxjs/*"],
"@angular/core": ["."]
},
"outDir": "../../dist/packages/core"
},
@ -18,7 +19,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "core.js",
"flatModuleId": "@angular/core"
}

View File

@ -23,7 +23,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "forms.js",
"flatModuleId": "@angular/forms"
}

View File

@ -18,7 +18,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "testing.js",
"flatModuleId": "@angular/http/testing"
}

View File

@ -19,7 +19,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "http.js",
"flatModuleId": "@angular/http"
}

View File

@ -16,10 +16,7 @@ var rxjsLocation = normalize('../../node_modules/rxjs');
var tslibLocation = normalize('../../node_modules/tslib');
var esm = 'esm/';
var locations = {
'tsc-wrapped': normalize('../../dist/packages-dist') + '/',
'compiler-cli': normalize('../../dist/packages') + '/'
};
var locations = {'compiler-cli': normalize('../../dist/packages') + '/'};
var esm_suffixes = {};
@ -29,11 +26,6 @@ function normalize(fileName) {
function resolve(id, from) {
// console.log('Resolve id:', id, 'from', from)
if (id == '@angular/tsc-wrapped') {
// Hack to restrict the import to not include the index of @angular/tsc-wrapped so we don't
// rollup tsickle.
return locations['tsc-wrapped'] + 'tsc-wrapped/src/collector.js';
}
var match = m.exec(id);
if (match) {
var packageName = match[1];

View File

@ -16,9 +16,7 @@
"@angular/compiler-cli/*": ["../../dist/packages/compiler-cli/*"],
"@angular/http": ["../../dist/packages/http"],
"@angular/platform-server": ["../../dist/packages/platform-server"],
"@angular/platform-browser": ["../../dist/packages/platform-browser"],
"@angular/tsc-wrapped": ["../../dist/packages-dist/tsc-wrapped"],
"@angular/tsc-wrapped/*": ["../../dist/packages-dist/tsc-wrapped/*"]
"@angular/platform-browser": ["../../dist/packages/platform-browser"]
},
"outDir": "../../dist/packages/language-service"
},

View File

@ -26,7 +26,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "testing.js",
"flatModuleId": "@angular/platform-browser-dynamic/testing"
}

View File

@ -25,7 +25,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "platform-browser-dynamic.js",
"flatModuleId": "@angular/platform-browser-dynamic"
}

View File

@ -6,6 +6,7 @@
"rootDir": "../",
"paths": {
"rxjs/*": ["../../../node_modules/rxjs/*"],
"@angular/common": ["../../../dist/packages/common"],
"@angular/core": ["../../../dist/packages/core"],
"@angular/core/testing": ["../../../dist/packages/core/testing"],
"@angular/animations": ["../../../dist/packages/animations"],
@ -23,7 +24,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "animations.js",
"flatModuleId": "@angular/platform-browser/animations"
}

View File

@ -23,7 +23,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "testing.js",
"flatModuleId": "@angular/platform-browser/testing"
}

View File

@ -20,7 +20,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "platform-browser.js",
"flatModuleId": "@angular/platform-browser"
}

View File

@ -12,7 +12,6 @@
"@angular/common": "file:../../../dist/packages-dist/common",
"@angular/compiler": "file:../../../dist/packages-dist/compiler",
"@angular/compiler-cli": "file:../../../dist/packages-dist/compiler-cli",
"@angular/tsc-wrapped": "file:../../../dist/packages-dist/tsc-wrapped",
"@angular/core": "file:../../../dist/packages-dist/core",
"@angular/http": "file:../../../dist/packages-dist/http",
"@angular/platform-browser": "file:../../../dist/packages-dist/platform-browser",

View File

@ -5,13 +5,16 @@
"baseUrl": ".",
"rootDir": "../",
"paths": {
"@angular/animations": ["../../../dist/packages/animations"],
"@angular/animations/browser": ["../../../dist/packages/animations/browser"],
"@angular/core": ["../../../dist/packages/core"],
"@angular/core/testing": ["../../../dist/packages/core/testing"],
"@angular/common": ["../../../dist/packages/common"],
"@angular/common/http": ["../../../dist/packages/common/http"],
"@angular/common/testing": ["../../../dist/packages/common/testing"],
"@angular/compiler": ["../../../dist/packages/compiler"],
"@angular/compiler/testing": ["../../../dist/packages/compiler/testing"],
"@angular/http": ["../../../dist/packages/http"],
"@angular/platform-browser": ["../../../dist/packages/platform-browser"],
"@angular/platform-browser/animations": ["../../../dist/packages/platform-browser/animations"],
"@angular/platform-browser/testing": ["../../../dist/packages/platform-browser/testing"],
@ -31,7 +34,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "testing.js",
"flatModuleId": "@angular/platform-server/testing"
}

View File

@ -5,6 +5,7 @@
"baseUrl": ".",
"rootDir": ".",
"paths": {
"@angular/animations": ["../../dist/packages/animations"],
"@angular/animations/browser": ["../../dist/packages/animations/browser"],
"@angular/core": ["../../dist/packages/core"],
"@angular/common": ["../../dist/packages/common"],
@ -26,7 +27,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "platform-server.js",
"flatModuleId": "@angular/platform-server"
}

View File

@ -22,7 +22,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "platform-webworker-dynamic.js",
"flatModuleId": "@angular/platform-webworker-dynamic"
}

View File

@ -19,7 +19,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "platform-webworker.js",
"flatModuleId": "@angular/platform-webworker"
}

View File

@ -20,7 +20,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "testing.js",
"flatModuleId": "@angular/router/testing"
}

View File

@ -19,7 +19,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "router.js",
"flatModuleId": "@angular/router"
}

View File

@ -4,6 +4,7 @@
"baseUrl": ".",
"rootDir": "../",
"paths": {
"@angular/common": ["../../../dist/packages/common"],
"@angular/core": ["../../../dist/packages/core"],
"@angular/platform-browser": ["../../../dist/packages/platform-browser"],
"@angular/router": ["../../../dist/packages/router"],
@ -18,7 +19,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "upgrade.js",
"flatModuleId": "@angular/router/upgrade"
}

View File

@ -1,11 +0,0 @@
package(default_visibility=["//visibility:public"])
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")
ts_library(
name = "tsc-wrapped",
srcs = glob(["**/*.ts"], exclude=[
"test/**",
]),
module_name = "@angular/tsc-wrapped",
tsconfig = ":tsconfig-build.json",
)

View File

@ -1,30 +0,0 @@
# tsc-wrapped
This package is an internal dependency used by @angular/compiler-cli. Please use that instead.
This is a wrapper around TypeScript's `tsc` program that allows us to hook in extra extensions.
TypeScript will eventually have an extensibility model for arbitrary extensions. We don't want
to constrain their design with baggage from a legacy implementation, so this wrapper only
supports specific extensions developed by the Angular team:
- tsickle down-levels Decorators into Annotations so they can be tree-shaken
- tsickle can also optionally produce Closure Compiler-friendly code
- ./collector.ts emits an extra `.metadata.json` file for every `.d.ts` file written,
which retains metadata about decorators that is lost in the TS emit
- @angular/compiler-cli extends this library to additionally generate template code
## TypeScript Decorator metadata collector
The `.d.ts` format does not preserve information about the Decorators applied to symbols.
Some tools, such as Angular template compiler, need access to statically analyzable
information about Decorators, so this library allows programs to produce a `foo.metadata.json`
to accompany a `foo.d.ts` file, and preserves the information that was lost in the declaration
emit.
## Releasing
```
$ $(npm bin)/tsc -p tools
$ cp tools/tsc-wrapped/package.json dist/tools/@angular/tsc-wrapped/
$ npm login [angular]
$ npm publish dist/tools/@angular/tsc-wrapped
```

View File

@ -1,10 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export {main} from './src/main';
export * from './index_no_tsickle';

View File

@ -1,19 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
// This index allows tsc-wrapped to be used with no dependency on tsickle.
// Short-term workaround until tsc-wrapped is removed entirely.
export {MetadataWriterHost} from './src/compiler_host';
export {CodegenExtension, UserError, createBundleIndexHost} from './src/main_no_tsickle';
export {default as AngularCompilerOptions} from './src/options';
export * from './src/bundler';
export * from './src/cli_options';
export * from './src/collector';
export * from './src/index_writer';
export * from './src/schema';

View File

@ -1,19 +0,0 @@
{
"name": "@angular/tsc-wrapped",
"version": "0.0.0-PLACEHOLDER",
"description": "Wraps the tsc CLI, allowing extensions.",
"homepage": "https://github.com/angular/angular/blob/master/packages/tsc-wrapped",
"bugs": "https://github.com/angular/angular/issues",
"contributors": [
"Alex Eagle <alexeagle@google.com>",
"Chuck Jazdzewski <chuckj@google.com>"
],
"license": "MIT",
"repository": {"type":"git","url":"https://github.com/angular/angular.git"},
"dependencies": {
"tsickle": "^0.24.0"
},
"peerDependencies": {
"typescript": "^2.4.2"
}
}

View File

@ -1,629 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as path from 'path';
import * as ts from 'typescript';
import {MetadataCollector} from './collector';
import {ClassMetadata, ConstructorMetadata, FunctionMetadata, MemberMetadata, MetadataEntry, MetadataError, MetadataImportedSymbolReferenceExpression, MetadataMap, MetadataObject, MetadataSymbolicExpression, MetadataSymbolicReferenceExpression, MetadataValue, MethodMetadata, ModuleExportMetadata, ModuleMetadata, VERSION, isClassMetadata, isConstructorMetadata, isFunctionMetadata, isInterfaceMetadata, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataImportedSymbolReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicExpression, isMethodMetadata} from './schema';
// The character set used to produce private names.
const PRIVATE_NAME_CHARS = 'abcdefghijklmnopqrstuvwxyz';
interface Symbol {
module: string;
name: string;
// Produced by indirectly by exportAll() for symbols re-export another symbol.
exports?: Symbol;
// Produced by indirectly by exportAll() for symbols are re-exported by another symbol.
reexportedAs?: Symbol;
// Produced by canonicalizeSymbols() for all symbols. A symbol is private if it is not
// exported by the index.
isPrivate?: boolean;
// Produced by canonicalizeSymbols() for all symbols. This is the one symbol that
// respresents all other symbols and is the only symbol that, among all the re-exported
// aliases, whose fields can be trusted to contain the correct information.
// For private symbols this is the declaration symbol. For public symbols this is the
// symbol that is exported.
canonicalSymbol?: Symbol;
// Produced by canonicalizeSymbols() for all symbols. This the symbol that originally
// declared the value and should be used to fetch the value.
declaration?: Symbol;
// A symbol is referenced if it is exported from index or referenced by the value of
// a referenced symbol's value.
referenced?: boolean;
// A symbol is marked as a re-export the symbol was rexported from a module that is
// not part of the flat module bundle.
reexport?: boolean;
// Only valid for referenced canonical symbols. Produces by convertSymbols().
value?: MetadataEntry;
// Only valid for referenced private symbols. It is the name to use to import the symbol from
// the bundle index. Produce by assignPrivateNames();
privateName?: string;
}
export interface BundleEntries { [name: string]: MetadataEntry; }
export interface BundlePrivateEntry {
privateName: string;
name: string;
module: string;
}
export interface BundledModule {
metadata: ModuleMetadata;
privates: BundlePrivateEntry[];
}
export interface MetadataBundlerHost {
getMetadataFor(moduleName: string): ModuleMetadata|undefined;
}
type StaticsMetadata = {
[name: string]: MetadataValue | FunctionMetadata;
};
export class MetadataBundler {
private symbolMap = new Map<string, Symbol>();
private metadataCache = new Map<string, ModuleMetadata|undefined>();
private exports = new Map<string, Symbol[]>();
private rootModule: string;
private exported: Set<Symbol>;
constructor(
private root: string, private importAs: string|undefined, private host: MetadataBundlerHost) {
this.rootModule = `./${path.basename(root)}`;
}
getMetadataBundle(): BundledModule {
// Export the root module. This also collects the transitive closure of all values referenced by
// the exports.
const exportedSymbols = this.exportAll(this.rootModule);
this.canonicalizeSymbols(exportedSymbols);
// TODO: exports? e.g. a module re-exports a symbol from another bundle
const metadata = this.getEntries(exportedSymbols);
const privates = Array.from(this.symbolMap.values())
.filter(s => s.referenced && s.isPrivate)
.map(s => ({
privateName: s.privateName !,
name: s.declaration !.name,
module: s.declaration !.module
}));
const origins = Array.from(this.symbolMap.values())
.filter(s => s.referenced && !s.reexport)
.reduce<{[name: string]: string}>((p, s) => {
p[s.isPrivate ? s.privateName ! : s.name] = s.declaration !.module;
return p;
}, {});
const exports = this.getReExports(exportedSymbols);
return {
metadata: {
__symbolic: 'module',
version: VERSION,
exports: exports.length ? exports : undefined, metadata, origins,
importAs: this.importAs !
},
privates
};
}
static resolveModule(importName: string, from: string): string {
return resolveModule(importName, from);
}
private getMetadata(moduleName: string): ModuleMetadata|undefined {
let result = this.metadataCache.get(moduleName);
if (!result) {
if (moduleName.startsWith('.')) {
const fullModuleName = resolveModule(moduleName, this.root);
result = this.host.getMetadataFor(fullModuleName);
}
this.metadataCache.set(moduleName, result);
}
return result;
}
private exportAll(moduleName: string): Symbol[] {
const module = this.getMetadata(moduleName);
let result = this.exports.get(moduleName);
if (result) {
return result;
}
result = [];
const exportSymbol = (exportedSymbol: Symbol, exportAs: string) => {
const symbol = this.symbolOf(moduleName, exportAs);
result !.push(symbol);
exportedSymbol.reexportedAs = symbol;
symbol.exports = exportedSymbol;
};
// Export all the symbols defined in this module.
if (module && module.metadata) {
for (let key in module.metadata) {
const data = module.metadata[key];
if (isMetadataImportedSymbolReferenceExpression(data)) {
// This is a re-export of an imported symbol. Record this as a re-export.
const exportFrom = resolveModule(data.module, moduleName);
this.exportAll(exportFrom);
const symbol = this.symbolOf(exportFrom, data.name);
exportSymbol(symbol, key);
} else {
// Record that this symbol is exported by this module.
result.push(this.symbolOf(moduleName, key));
}
}
}
// Export all the re-exports from this module
if (module && module.exports) {
for (const exportDeclaration of module.exports) {
const exportFrom = resolveModule(exportDeclaration.from, moduleName);
// Record all the exports from the module even if we don't use it directly.
const exportedSymbols = this.exportAll(exportFrom);
if (exportDeclaration.export) {
// Re-export all the named exports from a module.
for (const exportItem of exportDeclaration.export) {
const name = typeof exportItem == 'string' ? exportItem : exportItem.name;
const exportAs = typeof exportItem == 'string' ? exportItem : exportItem.as;
const symbol = this.symbolOf(exportFrom, name);
if (exportedSymbols && exportedSymbols.length == 1 && exportedSymbols[0].reexport &&
exportedSymbols[0].name == '*') {
// This is a named export from a module we have no metadata about. Record the named
// export as a re-export.
symbol.reexport = true;
}
exportSymbol(this.symbolOf(exportFrom, name), exportAs);
}
} else {
// Re-export all the symbols from the module
const exportedSymbols = this.exportAll(exportFrom);
for (const exportedSymbol of exportedSymbols) {
const name = exportedSymbol.name;
exportSymbol(exportedSymbol, name);
}
}
}
}
if (!module) {
// If no metadata is found for this import then it is considered external to the
// library and should be recorded as a re-export in the final metadata if it is
// eventually re-exported.
const symbol = this.symbolOf(moduleName, '*');
symbol.reexport = true;
result.push(symbol);
}
this.exports.set(moduleName, result);
return result;
}
/**
* Fill in the canonicalSymbol which is the symbol that should be imported by factories.
* The canonical symbol is the one exported by the index file for the bundle or definition
* symbol for private symbols that are not exported by bundle index.
*/
private canonicalizeSymbols(exportedSymbols: Symbol[]) {
const symbols = Array.from(this.symbolMap.values());
this.exported = new Set(exportedSymbols);
symbols.forEach(this.canonicalizeSymbol, this);
}
private canonicalizeSymbol(symbol: Symbol) {
const rootExport = getRootExport(symbol);
const declaration = getSymbolDeclaration(symbol);
const isPrivate = !this.exported.has(rootExport);
const canonicalSymbol = isPrivate ? declaration : rootExport;
symbol.isPrivate = isPrivate;
symbol.declaration = declaration;
symbol.canonicalSymbol = canonicalSymbol;
symbol.reexport = declaration.reexport;
}
private getEntries(exportedSymbols: Symbol[]): BundleEntries {
const result: BundleEntries = {};
const exportedNames = new Set(exportedSymbols.map(s => s.name));
let privateName = 0;
function newPrivateName(): string {
while (true) {
let digits: string[] = [];
let index = privateName++;
let base = PRIVATE_NAME_CHARS;
while (!digits.length || index > 0) {
digits.unshift(base[index % base.length]);
index = Math.floor(index / base.length);
}
digits.unshift('\u0275');
const result = digits.join('');
if (!exportedNames.has(result)) return result;
}
}
exportedSymbols.forEach(symbol => this.convertSymbol(symbol));
const symbolsMap = new Map<string, string[]>();
Array.from(this.symbolMap.values()).forEach(symbol => {
if (symbol.referenced && !symbol.reexport) {
let name = symbol.name;
const identifier = `${symbol.declaration!.module}:${symbol.declaration !.name}`;
if (symbol.isPrivate && !symbol.privateName) {
name = newPrivateName();
symbol.privateName = name;
}
if (symbolsMap.has(identifier)) {
const names = symbolsMap.get(identifier);
names !.push(name);
} else {
symbolsMap.set(identifier, [name]);
}
result[name] = symbol.value !;
}
});
// check for duplicated entries
symbolsMap.forEach((names: string[], identifier: string) => {
if (names.length > 1) {
const [module, declaredName] = identifier.split(':');
// prefer the export that uses the declared name (if any)
let reference = names.indexOf(declaredName);
if (reference === -1) {
reference = 0;
}
// keep one entry and replace the others by references
names.forEach((name: string, i: number) => {
if (i !== reference) {
result[name] = {__symbolic: 'reference', name: names[reference]};
}
});
}
});
return result;
}
private getReExports(exportedSymbols: Symbol[]): ModuleExportMetadata[] {
type ExportClause = {name: string, as: string}[];
const modules = new Map<string, ExportClause>();
const exportAlls = new Set<string>();
for (const symbol of exportedSymbols) {
if (symbol.reexport) {
// symbol.declaration is guarenteed to be defined during the phase this method is called.
const declaration = symbol.declaration !;
const module = declaration.module;
if (declaration !.name == '*') {
// Reexport all the symbols.
exportAlls.add(declaration.module);
} else {
// Re-export the symbol as the exported name.
let entry = modules.get(module);
if (!entry) {
entry = [];
modules.set(module, entry);
}
const as = symbol.name;
const name = declaration.name;
entry.push({name, as});
}
}
}
return [
...Array.from(exportAlls.values()).map(from => ({from})),
...Array.from(modules.entries()).map(([from, exports]) => ({export: exports, from}))
];
}
private convertSymbol(symbol: Symbol) {
// canonicalSymbol is ensured to be defined before this is called.
const canonicalSymbol = symbol.canonicalSymbol !;
if (!canonicalSymbol.referenced) {
canonicalSymbol.referenced = true;
// declaration is ensured to be definded before this method is called.
const declaration = canonicalSymbol.declaration !;
const module = this.getMetadata(declaration.module);
if (module) {
const value = module.metadata[declaration.name];
if (value && !declaration.name.startsWith('___')) {
canonicalSymbol.value = this.convertEntry(declaration.module, value);
}
}
}
}
private convertEntry(moduleName: string, value: MetadataEntry): MetadataEntry {
if (isClassMetadata(value)) {
return this.convertClass(moduleName, value);
}
if (isFunctionMetadata(value)) {
return this.convertFunction(moduleName, value);
}
if (isInterfaceMetadata(value)) {
return value;
}
return this.convertValue(moduleName, value);
}
private convertClass(moduleName: string, value: ClassMetadata): ClassMetadata {
return {
__symbolic: 'class',
arity: value.arity,
extends: this.convertExpression(moduleName, value.extends) !,
decorators:
value.decorators && value.decorators.map(d => this.convertExpression(moduleName, d) !),
members: this.convertMembers(moduleName, value.members !),
statics: value.statics && this.convertStatics(moduleName, value.statics)
};
}
private convertMembers(moduleName: string, members: MetadataMap): MetadataMap {
const result: MetadataMap = {};
for (const name in members) {
const value = members[name];
result[name] = value.map(v => this.convertMember(moduleName, v));
}
return result;
}
private convertMember(moduleName: string, member: MemberMetadata) {
const result: MemberMetadata = {__symbolic: member.__symbolic};
result.decorators =
member.decorators && member.decorators.map(d => this.convertExpression(moduleName, d) !);
if (isMethodMetadata(member)) {
(result as MethodMetadata).parameterDecorators = member.parameterDecorators &&
member.parameterDecorators.map(
d => d && d.map(p => this.convertExpression(moduleName, p) !));
if (isConstructorMetadata(member)) {
if (member.parameters) {
(result as ConstructorMetadata).parameters =
member.parameters.map(p => this.convertExpression(moduleName, p));
}
}
}
return result;
}
private convertStatics(moduleName: string, statics: StaticsMetadata): StaticsMetadata {
let result: StaticsMetadata = {};
for (const key in statics) {
const value = statics[key];
result[key] = isFunctionMetadata(value) ? this.convertFunction(moduleName, value) : value;
}
return result;
}
private convertFunction(moduleName: string, value: FunctionMetadata): FunctionMetadata {
return {
__symbolic: 'function',
parameters: value.parameters,
defaults: value.defaults && value.defaults.map(v => this.convertValue(moduleName, v)),
value: this.convertValue(moduleName, value.value)
};
}
private convertValue(moduleName: string, value: MetadataValue): MetadataValue {
if (isPrimitive(value)) {
return value;
}
if (isMetadataError(value)) {
return this.convertError(moduleName, value);
}
if (isMetadataSymbolicExpression(value)) {
return this.convertExpression(moduleName, value) !;
}
if (Array.isArray(value)) {
return value.map(v => this.convertValue(moduleName, v));
}
// Otherwise it is a metadata object.
const object = value as MetadataObject;
const result: MetadataObject = {};
for (const key in object) {
result[key] = this.convertValue(moduleName, object[key]);
}
return result;
}
private convertExpression(
moduleName: string, value: MetadataSymbolicExpression|MetadataError|null|
undefined): MetadataSymbolicExpression|MetadataError|undefined|null {
if (value) {
switch (value.__symbolic) {
case 'error':
return this.convertError(moduleName, value as MetadataError);
case 'reference':
return this.convertReference(moduleName, value as MetadataSymbolicReferenceExpression);
default:
return this.convertExpressionNode(moduleName, value);
}
}
return value;
}
private convertError(module: string, value: MetadataError): MetadataError {
return {
__symbolic: 'error',
message: value.message,
line: value.line,
character: value.character,
context: value.context, module
};
}
private convertReference(moduleName: string, value: MetadataSymbolicReferenceExpression):
MetadataSymbolicReferenceExpression|MetadataError|undefined {
const createReference = (symbol: Symbol): MetadataSymbolicReferenceExpression => {
const declaration = symbol.declaration !;
if (declaration.module.startsWith('.')) {
// Reference to a symbol defined in the module. Ensure it is converted then return a
// references to the final symbol.
this.convertSymbol(symbol);
return {
__symbolic: 'reference',
get name() {
// Resolved lazily because private names are assigned late.
const canonicalSymbol = symbol.canonicalSymbol !;
if (canonicalSymbol.isPrivate == null) {
throw Error('Invalid state: isPrivate was not initialized');
}
return canonicalSymbol.isPrivate ? canonicalSymbol.privateName ! : canonicalSymbol.name;
}
};
} else {
// The symbol was a re-exported symbol from another module. Return a reference to the
// original imported symbol.
return {__symbolic: 'reference', name: declaration.name, module: declaration.module};
}
};
if (isMetadataGlobalReferenceExpression(value)) {
const metadata = this.getMetadata(moduleName);
if (metadata && metadata.metadata && metadata.metadata[value.name]) {
// Reference to a symbol defined in the module
return createReference(this.canonicalSymbolOf(moduleName, value.name));
}
// If a reference has arguments, the arguments need to be converted.
if (value.arguments) {
return {
__symbolic: 'reference',
name: value.name,
arguments: value.arguments.map(a => this.convertValue(moduleName, a))
};
}
// Global references without arguments (such as to Math or JSON) are unmodified.
return value;
}
if (isMetadataImportedSymbolReferenceExpression(value)) {
// References to imported symbols are separated into two, references to bundled modules and
// references to modules external to the bundle. If the module reference is relative it is
// assumed to be in the bundle. If it is Global it is assumed to be outside the bundle.
// References to symbols outside the bundle are left unmodified. References to symbol inside
// the bundle need to be converted to a bundle import reference reachable from the bundle
// index.
if (value.module.startsWith('.')) {
// Reference is to a symbol defined inside the module. Convert the reference to a reference
// to the canonical symbol.
const referencedModule = resolveModule(value.module, moduleName);
const referencedName = value.name;
return createReference(this.canonicalSymbolOf(referencedModule, referencedName));
}
// Value is a reference to a symbol defined outside the module.
if (value.arguments) {
// If a reference has arguments the arguments need to be converted.
return {
__symbolic: 'reference',
name: value.name,
module: value.module,
arguments: value.arguments.map(a => this.convertValue(moduleName, a))
};
}
return value;
}
if (isMetadataModuleReferenceExpression(value)) {
// Cannot support references to bundled modules as the internal modules of a bundle are erased
// by the bundler.
if (value.module.startsWith('.')) {
return {
__symbolic: 'error',
message: 'Unsupported bundled module reference',
context: {module: value.module}
};
}
// References to unbundled modules are unmodified.
return value;
}
}
private convertExpressionNode(moduleName: string, value: MetadataSymbolicExpression):
MetadataSymbolicExpression {
const result: MetadataSymbolicExpression = {__symbolic: value.__symbolic};
for (const key in value) {
(result as any)[key] = this.convertValue(moduleName, (value as any)[key]);
}
return result;
}
private symbolOf(module: string, name: string): Symbol {
const symbolKey = `${module}:${name}`;
let symbol = this.symbolMap.get(symbolKey);
if (!symbol) {
symbol = {module, name};
this.symbolMap.set(symbolKey, symbol);
}
return symbol;
}
private canonicalSymbolOf(module: string, name: string): Symbol {
// Ensure the module has been seen.
this.exportAll(module);
const symbol = this.symbolOf(module, name);
if (!symbol.canonicalSymbol) {
this.canonicalizeSymbol(symbol);
}
return symbol;
}
}
export class CompilerHostAdapter implements MetadataBundlerHost {
private collector = new MetadataCollector();
constructor(private host: ts.CompilerHost) {}
getMetadataFor(fileName: string): ModuleMetadata|undefined {
const sourceFile = this.host.getSourceFile(fileName + '.ts', ts.ScriptTarget.Latest);
return this.collector.getMetadata(sourceFile);
}
}
function resolveModule(importName: string, from: string): string {
if (importName.startsWith('.') && from) {
let normalPath = path.normalize(path.join(path.dirname(from), importName));
if (!normalPath.startsWith('.') && from.startsWith('.')) {
// path.normalize() preserves leading '../' but not './'. This adds it back.
normalPath = `.${path.sep}${normalPath}`;
}
// Replace windows path delimiters with forward-slashes. Otherwise the paths are not
// TypeScript compatible when building the bundle.
return normalPath.replace(/\\/g, '/');
}
return importName;
}
function isPrimitive(o: any): o is boolean|string|number {
return o === null || (typeof o !== 'function' && typeof o !== 'object');
}
function getRootExport(symbol: Symbol): Symbol {
return symbol.reexportedAs ? getRootExport(symbol.reexportedAs) : symbol;
}
function getSymbolDeclaration(symbol: Symbol): Symbol {
return symbol.exports ? getSymbolDeclaration(symbol.exports) : symbol;
}

View File

@ -1,50 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export class CliOptions {
public basePath: string|null;
constructor({basePath = null}: {basePath?: string | null}) { this.basePath = basePath; }
}
export class I18nExtractionCliOptions extends CliOptions {
i18nFormat: string|null;
locale: string|null;
outFile: string|null;
constructor({i18nFormat = null, locale = null, outFile = null}: {
i18nFormat?: string,
locale?: string,
outFile?: string,
}) {
super({});
this.i18nFormat = i18nFormat;
this.locale = locale;
this.outFile = outFile;
}
}
export class NgcCliOptions extends CliOptions {
public i18nFormat: string|null;
public i18nFile: string|null;
public locale: string|null;
public missingTranslation: string|null;
constructor({i18nFormat = null, i18nFile = null, locale = null, missingTranslation = null,
basePath = null}: {
i18nFormat?: string | null,
i18nFile?: string|null,
locale?: string|null,
missingTranslation?: string|null,
basePath?: string|null
}) {
super({basePath: basePath});
this.i18nFormat = i18nFormat;
this.i18nFile = i18nFile;
this.locale = locale;
this.missingTranslation = missingTranslation;
}
}

View File

@ -1,774 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {Evaluator, errorSymbol} from './evaluator';
import {ClassMetadata, ConstructorMetadata, FunctionMetadata, InterfaceMetadata, MemberMetadata, MetadataEntry, MetadataError, MetadataMap, MetadataSymbolicBinaryExpression, MetadataSymbolicCallExpression, MetadataSymbolicExpression, MetadataSymbolicIfExpression, MetadataSymbolicIndexExpression, MetadataSymbolicPrefixExpression, MetadataSymbolicReferenceExpression, MetadataSymbolicSelectExpression, MetadataSymbolicSpreadExpression, MetadataValue, MethodMetadata, ModuleExportMetadata, ModuleMetadata, VERSION, isClassMetadata, isConstructorMetadata, isFunctionMetadata, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataSymbolicExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSelectExpression, isMethodMetadata} from './schema';
import {Symbols} from './symbols';
// In TypeScript 2.1 these flags moved
// These helpers work for both 2.0 and 2.1.
const isExport = (ts as any).ModifierFlags ?
((node: ts.Node) =>
!!((ts as any).getCombinedModifierFlags(node) & (ts as any).ModifierFlags.Export)) :
((node: ts.Node) => !!((node.flags & (ts as any).NodeFlags.Export)));
const isStatic = (ts as any).ModifierFlags ?
((node: ts.Node) =>
!!((ts as any).getCombinedModifierFlags(node) & (ts as any).ModifierFlags.Static)) :
((node: ts.Node) => !!((node.flags & (ts as any).NodeFlags.Static)));
/**
* A set of collector options to use when collecting metadata.
*/
export interface CollectorOptions {
/**
* Version of the metadata to collect.
*/
version?: number;
/**
* Collect a hidden field "$quoted$" in objects literals that record when the key was quoted in
* the source.
*/
quotedNames?: boolean;
/**
* Do not simplify invalid expressions.
*/
verboseInvalidExpression?: boolean;
/**
* An expression substitution callback.
*/
substituteExpression?: (value: MetadataValue, node: ts.Node) => MetadataValue;
}
/**
* Collect decorator metadata from a TypeScript module.
*/
export class MetadataCollector {
constructor(private options: CollectorOptions = {}) {}
/**
* Returns a JSON.stringify friendly form describing the decorators of the exported classes from
* the source file that is expected to correspond to a module.
*/
public getMetadata(
sourceFile: ts.SourceFile, strict: boolean = false,
substituteExpression?: (value: MetadataValue, node: ts.Node) => MetadataValue): ModuleMetadata
|undefined {
const locals = new Symbols(sourceFile);
const nodeMap =
new Map<MetadataValue|ClassMetadata|InterfaceMetadata|FunctionMetadata, ts.Node>();
const composedSubstituter = substituteExpression && this.options.substituteExpression ?
(value: MetadataValue, node: ts.Node) =>
this.options.substituteExpression !(substituteExpression(value, node), node) :
substituteExpression;
const evaluatorOptions = substituteExpression ?
{...this.options, substituteExpression: composedSubstituter} :
this.options;
let metadata: {[name: string]: MetadataValue | ClassMetadata | FunctionMetadata}|undefined;
const evaluator = new Evaluator(locals, nodeMap, evaluatorOptions, (name, value) => {
if (!metadata) metadata = {};
metadata[name] = value;
});
let exports: ModuleExportMetadata[]|undefined = undefined;
function objFromDecorator(decoratorNode: ts.Decorator): MetadataSymbolicExpression {
return <MetadataSymbolicExpression>evaluator.evaluateNode(decoratorNode.expression);
}
function recordEntry<T extends MetadataEntry>(entry: T, node: ts.Node): T {
nodeMap.set(entry, node);
return entry;
}
function errorSym(
message: string, node?: ts.Node, context?: {[name: string]: string}): MetadataError {
return errorSymbol(message, node, context, sourceFile);
}
function maybeGetSimpleFunction(
functionDeclaration: ts.FunctionDeclaration |
ts.MethodDeclaration): {func: FunctionMetadata, name: string}|undefined {
if (functionDeclaration.name && functionDeclaration.name.kind == ts.SyntaxKind.Identifier) {
const nameNode = <ts.Identifier>functionDeclaration.name;
const functionName = nameNode.text;
const functionBody = functionDeclaration.body;
if (functionBody && functionBody.statements.length == 1) {
const statement = functionBody.statements[0];
if (statement.kind === ts.SyntaxKind.ReturnStatement) {
const returnStatement = <ts.ReturnStatement>statement;
if (returnStatement.expression) {
const func: FunctionMetadata = {
__symbolic: 'function',
parameters: namesOf(functionDeclaration.parameters),
value: evaluator.evaluateNode(returnStatement.expression)
};
if (functionDeclaration.parameters.some(p => p.initializer != null)) {
func.defaults = functionDeclaration.parameters.map(
p => p.initializer && evaluator.evaluateNode(p.initializer));
}
return recordEntry({func, name: functionName}, functionDeclaration);
}
}
}
}
}
function classMetadataOf(classDeclaration: ts.ClassDeclaration): ClassMetadata {
const result: ClassMetadata = {__symbolic: 'class'};
function getDecorators(decorators: ts.Decorator[] | undefined): MetadataSymbolicExpression[]|
undefined {
if (decorators && decorators.length)
return decorators.map(decorator => objFromDecorator(decorator));
return undefined;
}
function referenceFrom(node: ts.Node): MetadataSymbolicReferenceExpression|MetadataError|
MetadataSymbolicSelectExpression {
const result = evaluator.evaluateNode(node);
if (isMetadataError(result) || isMetadataSymbolicReferenceExpression(result) ||
isMetadataSymbolicSelectExpression(result)) {
return result;
} else {
return errorSym('Symbol reference expected', node);
}
}
// Add class parents
if (classDeclaration.heritageClauses) {
classDeclaration.heritageClauses.forEach((hc) => {
if (hc.token === ts.SyntaxKind.ExtendsKeyword && hc.types) {
hc.types.forEach(type => result.extends = referenceFrom(type.expression));
}
});
}
// Add arity if the type is generic
const typeParameters = classDeclaration.typeParameters;
if (typeParameters && typeParameters.length) {
result.arity = typeParameters.length;
}
// Add class decorators
if (classDeclaration.decorators) {
result.decorators = getDecorators(classDeclaration.decorators);
}
// member decorators
let members: MetadataMap|null = null;
function recordMember(name: string, metadata: MemberMetadata) {
if (!members) members = {};
const data = members.hasOwnProperty(name) ? members[name] : [];
data.push(metadata);
members[name] = data;
}
// static member
let statics: {[name: string]: MetadataValue | FunctionMetadata}|null = null;
function recordStaticMember(name: string, value: MetadataValue | FunctionMetadata) {
if (!statics) statics = {};
statics[name] = value;
}
for (const member of classDeclaration.members) {
let isConstructor = false;
switch (member.kind) {
case ts.SyntaxKind.Constructor:
case ts.SyntaxKind.MethodDeclaration:
isConstructor = member.kind === ts.SyntaxKind.Constructor;
const method = <ts.MethodDeclaration|ts.ConstructorDeclaration>member;
if (isStatic(method)) {
const maybeFunc = maybeGetSimpleFunction(<ts.MethodDeclaration>method);
if (maybeFunc) {
recordStaticMember(maybeFunc.name, maybeFunc.func);
}
continue;
}
const methodDecorators = getDecorators(method.decorators);
const parameters = method.parameters;
const parameterDecoratorData:
((MetadataSymbolicExpression | MetadataError)[] | undefined)[] = [];
const parametersData:
(MetadataSymbolicReferenceExpression | MetadataError |
MetadataSymbolicSelectExpression | null)[] = [];
let hasDecoratorData: boolean = false;
let hasParameterData: boolean = false;
for (const parameter of parameters) {
const parameterData = getDecorators(parameter.decorators);
parameterDecoratorData.push(parameterData);
hasDecoratorData = hasDecoratorData || !!parameterData;
if (isConstructor) {
if (parameter.type) {
parametersData.push(referenceFrom(parameter.type));
} else {
parametersData.push(null);
}
hasParameterData = true;
}
}
const data: MethodMetadata = {__symbolic: isConstructor ? 'constructor' : 'method'};
const name = isConstructor ? '__ctor__' : evaluator.nameOf(member.name);
if (methodDecorators) {
data.decorators = methodDecorators;
}
if (hasDecoratorData) {
data.parameterDecorators = parameterDecoratorData;
}
if (hasParameterData) {
(<ConstructorMetadata>data).parameters = parametersData;
}
if (!isMetadataError(name)) {
recordMember(name, data);
}
break;
case ts.SyntaxKind.PropertyDeclaration:
case ts.SyntaxKind.GetAccessor:
case ts.SyntaxKind.SetAccessor:
const property = <ts.PropertyDeclaration>member;
if (isStatic(property)) {
const name = evaluator.nameOf(property.name);
if (!isMetadataError(name)) {
if (property.initializer) {
const value = evaluator.evaluateNode(property.initializer);
recordStaticMember(name, value);
} else {
recordStaticMember(name, errorSym('Variable not initialized', property.name));
}
}
}
const propertyDecorators = getDecorators(property.decorators);
if (propertyDecorators) {
const name = evaluator.nameOf(property.name);
if (!isMetadataError(name)) {
recordMember(name, {__symbolic: 'property', decorators: propertyDecorators});
}
}
break;
}
}
if (members) {
result.members = members;
}
if (statics) {
result.statics = statics;
}
return recordEntry(result, classDeclaration);
}
// Collect all exported symbols from an exports clause.
const exportMap = new Map<string, string>();
ts.forEachChild(sourceFile, node => {
switch (node.kind) {
case ts.SyntaxKind.ExportDeclaration:
const exportDeclaration = <ts.ExportDeclaration>node;
const {moduleSpecifier, exportClause} = exportDeclaration;
if (!moduleSpecifier) {
// If there is a module specifier there is also an exportClause
exportClause !.elements.forEach(spec => {
const exportedAs = spec.name.text;
const name = (spec.propertyName || spec.name).text;
exportMap.set(name, exportedAs);
});
}
}
});
const isExportedIdentifier = (identifier?: ts.Identifier) =>
identifier && exportMap.has(identifier.text);
const isExported =
(node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.TypeAliasDeclaration |
ts.InterfaceDeclaration | ts.EnumDeclaration) =>
isExport(node) || isExportedIdentifier(node.name);
const exportedIdentifierName = (identifier?: ts.Identifier) =>
identifier && (exportMap.get(identifier.text) || identifier.text);
const exportedName =
(node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration |
ts.TypeAliasDeclaration | ts.EnumDeclaration) => exportedIdentifierName(node.name);
// Predeclare classes and functions
ts.forEachChild(sourceFile, node => {
switch (node.kind) {
case ts.SyntaxKind.ClassDeclaration:
const classDeclaration = <ts.ClassDeclaration>node;
if (classDeclaration.name) {
const className = classDeclaration.name.text;
if (isExported(classDeclaration)) {
locals.define(
className, {__symbolic: 'reference', name: exportedName(classDeclaration)});
} else {
locals.define(
className, errorSym('Reference to non-exported class', node, {className}));
}
}
break;
case ts.SyntaxKind.InterfaceDeclaration:
const interfaceDeclaration = <ts.InterfaceDeclaration>node;
if (interfaceDeclaration.name) {
const interfaceName = interfaceDeclaration.name.text;
// All references to interfaces should be converted to references to `any`.
locals.define(interfaceName, {__symbolic: 'reference', name: 'any'});
}
break;
case ts.SyntaxKind.FunctionDeclaration:
const functionDeclaration = <ts.FunctionDeclaration>node;
if (!isExported(functionDeclaration)) {
// Report references to this function as an error.
const nameNode = functionDeclaration.name;
if (nameNode && nameNode.text) {
locals.define(
nameNode.text,
errorSym(
'Reference to a non-exported function', nameNode, {name: nameNode.text}));
}
}
break;
}
});
ts.forEachChild(sourceFile, node => {
switch (node.kind) {
case ts.SyntaxKind.ExportDeclaration:
// Record export declarations
const exportDeclaration = <ts.ExportDeclaration>node;
const {moduleSpecifier, exportClause} = exportDeclaration;
if (!moduleSpecifier) {
// no module specifier -> export {propName as name};
if (exportClause) {
exportClause.elements.forEach(spec => {
const name = spec.name.text;
// If the symbol was not already exported, export a reference since it is a
// reference to an import
if (!metadata || !metadata[name]) {
const propNode = spec.propertyName || spec.name;
const value: MetadataValue = evaluator.evaluateNode(propNode);
if (!metadata) metadata = {};
metadata[name] = recordEntry(value, node);
}
});
}
}
if (moduleSpecifier && moduleSpecifier.kind == ts.SyntaxKind.StringLiteral) {
// Ignore exports that don't have string literals as exports.
// This is allowed by the syntax but will be flagged as an error by the type checker.
const from = (<ts.StringLiteral>moduleSpecifier).text;
const moduleExport: ModuleExportMetadata = {from};
if (exportClause) {
moduleExport.export = exportClause.elements.map(
spec => spec.propertyName ? {name: spec.propertyName.text, as: spec.name.text} :
spec.name.text);
}
if (!exports) exports = [];
exports.push(moduleExport);
}
break;
case ts.SyntaxKind.ClassDeclaration:
const classDeclaration = <ts.ClassDeclaration>node;
if (classDeclaration.name) {
if (isExported(classDeclaration)) {
const name = exportedName(classDeclaration);
if (name) {
if (!metadata) metadata = {};
metadata[name] = classMetadataOf(classDeclaration);
}
}
}
// Otherwise don't record metadata for the class.
break;
case ts.SyntaxKind.TypeAliasDeclaration:
const typeDeclaration = <ts.TypeAliasDeclaration>node;
if (typeDeclaration.name && isExported(typeDeclaration)) {
const name = exportedName(typeDeclaration);
if (name) {
if (!metadata) metadata = {};
metadata[name] = {__symbolic: 'interface'};
}
}
break;
case ts.SyntaxKind.InterfaceDeclaration:
const interfaceDeclaration = <ts.InterfaceDeclaration>node;
if (interfaceDeclaration.name && isExported(interfaceDeclaration)) {
const name = exportedName(interfaceDeclaration);
if (name) {
if (!metadata) metadata = {};
metadata[name] = {__symbolic: 'interface'};
}
}
break;
case ts.SyntaxKind.FunctionDeclaration:
// Record functions that return a single value. Record the parameter
// names substitution will be performed by the StaticReflector.
const functionDeclaration = <ts.FunctionDeclaration>node;
if (isExported(functionDeclaration) && functionDeclaration.name) {
const name = exportedName(functionDeclaration);
const maybeFunc = maybeGetSimpleFunction(functionDeclaration);
if (name) {
if (!metadata) metadata = {};
metadata[name] =
maybeFunc ? recordEntry(maybeFunc.func, node) : {__symbolic: 'function'};
}
}
break;
case ts.SyntaxKind.EnumDeclaration:
const enumDeclaration = <ts.EnumDeclaration>node;
if (isExported(enumDeclaration)) {
const enumValueHolder: {[name: string]: MetadataValue} = {};
const enumName = exportedName(enumDeclaration);
let nextDefaultValue: MetadataValue = 0;
let writtenMembers = 0;
for (const member of enumDeclaration.members) {
let enumValue: MetadataValue;
if (!member.initializer) {
enumValue = nextDefaultValue;
} else {
enumValue = evaluator.evaluateNode(member.initializer);
}
let name: string|undefined = undefined;
if (member.name.kind == ts.SyntaxKind.Identifier) {
const identifier = <ts.Identifier>member.name;
name = identifier.text;
enumValueHolder[name] = enumValue;
writtenMembers++;
}
if (typeof enumValue === 'number') {
nextDefaultValue = enumValue + 1;
} else if (name) {
nextDefaultValue = {
__symbolic: 'binary',
operator: '+',
left: {
__symbolic: 'select',
expression: recordEntry({__symbolic: 'reference', name: enumName}, node), name
}
};
} else {
nextDefaultValue =
recordEntry(errorSym('Unsuppported enum member name', member.name), node);
}
}
if (writtenMembers) {
if (enumName) {
if (!metadata) metadata = {};
metadata[enumName] = recordEntry(enumValueHolder, node);
}
}
}
break;
case ts.SyntaxKind.VariableStatement:
const variableStatement = <ts.VariableStatement>node;
for (const variableDeclaration of variableStatement.declarationList.declarations) {
if (variableDeclaration.name.kind == ts.SyntaxKind.Identifier) {
const nameNode = <ts.Identifier>variableDeclaration.name;
let varValue: MetadataValue;
if (variableDeclaration.initializer) {
varValue = evaluator.evaluateNode(variableDeclaration.initializer);
} else {
varValue = recordEntry(errorSym('Variable not initialized', nameNode), nameNode);
}
let exported = false;
if (isExport(variableStatement) || isExport(variableDeclaration) ||
isExportedIdentifier(nameNode)) {
const name = exportedIdentifierName(nameNode);
if (name) {
if (!metadata) metadata = {};
metadata[name] = recordEntry(varValue, node);
}
exported = true;
}
if (typeof varValue == 'string' || typeof varValue == 'number' ||
typeof varValue == 'boolean') {
locals.define(nameNode.text, varValue);
if (exported) {
locals.defineReference(
nameNode.text, {__symbolic: 'reference', name: nameNode.text});
}
} else if (!exported) {
if (varValue && !isMetadataError(varValue)) {
locals.define(nameNode.text, recordEntry(varValue, node));
} else {
locals.define(
nameNode.text,
recordEntry(
errorSym('Reference to a local symbol', nameNode, {name: nameNode.text}),
node));
}
}
} else {
// Destructuring (or binding) declarations are not supported,
// var {<identifier>[, <identifier>]+} = <expression>;
// or
// var [<identifier>[, <identifier}+] = <expression>;
// are not supported.
const report: (nameNode: ts.Node) => void = (nameNode: ts.Node) => {
switch (nameNode.kind) {
case ts.SyntaxKind.Identifier:
const name = <ts.Identifier>nameNode;
const varValue = errorSym('Destructuring not supported', name);
locals.define(name.text, varValue);
if (isExport(node)) {
if (!metadata) metadata = {};
metadata[name.text] = varValue;
}
break;
case ts.SyntaxKind.BindingElement:
const bindingElement = <ts.BindingElement>nameNode;
report(bindingElement.name);
break;
case ts.SyntaxKind.ObjectBindingPattern:
case ts.SyntaxKind.ArrayBindingPattern:
const bindings = <ts.BindingPattern>nameNode;
(bindings as any).elements.forEach(report);
break;
}
};
report(variableDeclaration.name);
}
}
break;
}
});
if (metadata || exports) {
if (!metadata)
metadata = {};
else if (strict) {
validateMetadata(sourceFile, nodeMap, metadata);
}
const result: ModuleMetadata = {
__symbolic: 'module',
version: this.options.version || VERSION, metadata
};
if (exports) result.exports = exports;
return result;
}
}
}
// This will throw if the metadata entry given contains an error node.
function validateMetadata(
sourceFile: ts.SourceFile, nodeMap: Map<MetadataEntry, ts.Node>,
metadata: {[name: string]: MetadataEntry}) {
let locals: Set<string> = new Set(['Array', 'Object', 'Set', 'Map', 'string', 'number', 'any']);
function validateExpression(
expression: MetadataValue | MetadataSymbolicExpression | MetadataError) {
if (!expression) {
return;
} else if (Array.isArray(expression)) {
expression.forEach(validateExpression);
} else if (typeof expression === 'object' && !expression.hasOwnProperty('__symbolic')) {
Object.getOwnPropertyNames(expression).forEach(v => validateExpression((<any>expression)[v]));
} else if (isMetadataError(expression)) {
reportError(expression);
} else if (isMetadataGlobalReferenceExpression(expression)) {
if (!locals.has(expression.name)) {
const reference = <MetadataValue>metadata[expression.name];
if (reference) {
validateExpression(reference);
}
}
} else if (isFunctionMetadata(expression)) {
validateFunction(<any>expression);
} else if (isMetadataSymbolicExpression(expression)) {
switch (expression.__symbolic) {
case 'binary':
const binaryExpression = <MetadataSymbolicBinaryExpression>expression;
validateExpression(binaryExpression.left);
validateExpression(binaryExpression.right);
break;
case 'call':
case 'new':
const callExpression = <MetadataSymbolicCallExpression>expression;
validateExpression(callExpression.expression);
if (callExpression.arguments) callExpression.arguments.forEach(validateExpression);
break;
case 'index':
const indexExpression = <MetadataSymbolicIndexExpression>expression;
validateExpression(indexExpression.expression);
validateExpression(indexExpression.index);
break;
case 'pre':
const prefixExpression = <MetadataSymbolicPrefixExpression>expression;
validateExpression(prefixExpression.operand);
break;
case 'select':
const selectExpression = <MetadataSymbolicSelectExpression>expression;
validateExpression(selectExpression.expression);
break;
case 'spread':
const spreadExpression = <MetadataSymbolicSpreadExpression>expression;
validateExpression(spreadExpression.expression);
break;
case 'if':
const ifExpression = <MetadataSymbolicIfExpression>expression;
validateExpression(ifExpression.condition);
validateExpression(ifExpression.elseExpression);
validateExpression(ifExpression.thenExpression);
break;
}
}
}
function validateMember(classData: ClassMetadata, member: MemberMetadata) {
if (member.decorators) {
member.decorators.forEach(validateExpression);
}
if (isMethodMetadata(member) && member.parameterDecorators) {
member.parameterDecorators.forEach(validateExpression);
}
// Only validate parameters of classes for which we know that are used with our DI
if (classData.decorators && isConstructorMetadata(member) && member.parameters) {
member.parameters.forEach(validateExpression);
}
}
function validateClass(classData: ClassMetadata) {
if (classData.decorators) {
classData.decorators.forEach(validateExpression);
}
if (classData.members) {
Object.getOwnPropertyNames(classData.members)
.forEach(name => classData.members ![name].forEach((m) => validateMember(classData, m)));
}
if (classData.statics) {
Object.getOwnPropertyNames(classData.statics).forEach(name => {
const staticMember = classData.statics ![name];
if (isFunctionMetadata(staticMember)) {
validateExpression(staticMember.value);
} else {
validateExpression(staticMember);
}
});
}
}
function validateFunction(functionDeclaration: FunctionMetadata) {
if (functionDeclaration.value) {
const oldLocals = locals;
if (functionDeclaration.parameters) {
locals = new Set(oldLocals.values());
if (functionDeclaration.parameters)
functionDeclaration.parameters.forEach(n => locals.add(n));
}
validateExpression(functionDeclaration.value);
locals = oldLocals;
}
}
function shouldReportNode(node: ts.Node | undefined) {
if (node) {
const nodeStart = node.getStart();
return !(
node.pos != nodeStart &&
sourceFile.text.substring(node.pos, nodeStart).indexOf('@dynamic') >= 0);
}
return true;
}
function reportError(error: MetadataError) {
const node = nodeMap.get(error);
if (shouldReportNode(node)) {
const lineInfo = error.line != undefined ?
error.character != undefined ? `:${error.line + 1}:${error.character + 1}` :
`:${error.line + 1}` :
'';
throw new Error(
`${sourceFile.fileName}${lineInfo}: Metadata collected contains an error that will be reported at runtime: ${expandedMessage(error)}.\n ${JSON.stringify(error)}`);
}
}
Object.getOwnPropertyNames(metadata).forEach(name => {
const entry = metadata[name];
try {
if (isClassMetadata(entry)) {
validateClass(entry);
}
} catch (e) {
const node = nodeMap.get(entry);
if (shouldReportNode(node)) {
if (node) {
const {line, character} = sourceFile.getLineAndCharacterOfPosition(node.getStart());
throw new Error(
`${sourceFile.fileName}:${line + 1}:${character + 1}: Error encountered in metadata generated for exported symbol '${name}': \n ${e.message}`);
}
throw new Error(
`Error encountered in metadata generated for exported symbol ${name}: \n ${e.message}`);
}
}
});
}
// Collect parameter names from a function.
function namesOf(parameters: ts.NodeArray<ts.ParameterDeclaration>): string[] {
const result: string[] = [];
function addNamesOf(name: ts.Identifier | ts.BindingPattern) {
if (name.kind == ts.SyntaxKind.Identifier) {
const identifier = <ts.Identifier>name;
result.push(identifier.text);
} else {
const bindingPattern = <ts.BindingPattern>name;
for (const element of bindingPattern.elements) {
const name = (element as any).name;
if (name) {
addNamesOf(name);
}
}
}
}
for (const parameter of parameters) {
addNamesOf(parameter.name);
}
return result;
}
function expandedMessage(error: any): string {
switch (error.message) {
case 'Reference to non-exported class':
if (error.context && error.context.className) {
return `Reference to a non-exported class ${error.context.className}. Consider exporting the class`;
}
break;
case 'Variable not initialized':
return 'Only initialized variables and constants can be referenced because the value of this variable is needed by the template compiler';
case 'Destructuring not supported':
return 'Referencing an exported destructured variable or constant is not supported by the template compiler. Consider simplifying this to avoid destructuring';
case 'Could not resolve type':
if (error.context && error.context.typeName) {
return `Could not resolve type ${error.context.typeName}`;
}
break;
case 'Function call not supported':
let prefix =
error.context && error.context.name ? `Calling function '${error.context.name}', f` : 'F';
return prefix +
'unction calls are not supported. Consider replacing the function or lambda with a reference to an exported function';
case 'Reference to a local symbol':
if (error.context && error.context.name) {
return `Reference to a local (non-exported) symbol '${error.context.name}'. Consider exporting the symbol`;
}
}
return error.message;
}

View File

@ -1,159 +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 {writeFileSync} from 'fs';
import {normalize} from 'path';
import * as ts from 'typescript';
import NgOptions from './options';
import {MetadataCollector} from './collector';
import {ModuleMetadata} from './schema';
export function formatDiagnostics(d: ts.Diagnostic[]): string {
const host: ts.FormatDiagnosticsHost = {
getCurrentDirectory: () => ts.sys.getCurrentDirectory(),
getNewLine: () => ts.sys.newLine,
getCanonicalFileName: (f: string) => f
};
return ts.formatDiagnostics(d, host);
}
/**
* Implementation of CompilerHost that forwards all methods to another instance.
* Useful for partial implementations to override only methods they care about.
*/
export abstract class DelegatingHost implements ts.CompilerHost {
constructor(protected delegate: ts.CompilerHost) {}
getSourceFile =
(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) =>
this.delegate.getSourceFile(fileName, languageVersion, onError);
getCancellationToken = () => this.delegate.getCancellationToken !();
getDefaultLibFileName = (options: ts.CompilerOptions) =>
this.delegate.getDefaultLibFileName(options);
getDefaultLibLocation = () => this.delegate.getDefaultLibLocation !();
writeFile: ts.WriteFileCallback = this.delegate.writeFile;
getCurrentDirectory = () => this.delegate.getCurrentDirectory();
getDirectories = (path: string): string[] =>
(this.delegate as any).getDirectories?(this.delegate as any).getDirectories(path): [];
getCanonicalFileName = (fileName: string) => this.delegate.getCanonicalFileName(fileName);
useCaseSensitiveFileNames = () => this.delegate.useCaseSensitiveFileNames();
getNewLine = () => this.delegate.getNewLine();
fileExists = (fileName: string) => this.delegate.fileExists(fileName);
readFile = (fileName: string) => this.delegate.readFile(fileName);
trace = (s: string) => this.delegate.trace !(s);
directoryExists = (directoryName: string) => this.delegate.directoryExists !(directoryName);
}
const IGNORED_FILES = /\.ngfactory\.js$|\.ngstyle\.js$/;
const DTS = /\.d\.ts$/;
export class MetadataWriterHost extends DelegatingHost {
private metadataCollector = new MetadataCollector({quotedNames: true});
private metadataCollector1 = new MetadataCollector({version: 1});
constructor(
delegate: ts.CompilerHost, private ngOptions: NgOptions, private emitAllFiles: boolean) {
super(delegate);
}
private writeMetadata(emitFilePath: string, sourceFile: ts.SourceFile) {
// TODO: replace with DTS filePath when https://github.com/Microsoft/TypeScript/pull/8412 is
// released
if (/*DTS*/ /\.js$/.test(emitFilePath)) {
const path = emitFilePath.replace(/*DTS*/ /\.js$/, '.metadata.json');
// Beginning with 2.1, TypeScript transforms the source tree before emitting it.
// We need the original, unmodified, tree which might be several levels back
// depending on the number of transforms performed. All SourceFile's prior to 2.1
// will appear to be the original source since they didn't include an original field.
let collectableFile = sourceFile;
while ((collectableFile as any).original) {
collectableFile = (collectableFile as any).original;
}
const metadata =
this.metadataCollector.getMetadata(collectableFile, !!this.ngOptions.strictMetadataEmit);
const metadata1 = this.metadataCollector1.getMetadata(collectableFile, false);
const metadatas: ModuleMetadata[] =
[metadata, metadata1].filter(e => !!e) as ModuleMetadata[];
if (metadatas.length) {
const metadataText = JSON.stringify(metadatas);
writeFileSync(path, metadataText, {encoding: 'utf-8'});
}
}
}
writeFile: ts.WriteFileCallback =
(fileName: string, data: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
const isDts = /\.d\.ts$/.test(fileName);
if (this.emitAllFiles || isDts) {
// Let the original file be written first; this takes care of creating parent directories
this.delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
}
if (isDts) {
// TODO: remove this early return after https://github.com/Microsoft/TypeScript/pull/8412
// is released
return;
}
if (IGNORED_FILES.test(fileName)) {
return;
}
if (!sourceFiles) {
throw new Error(
'Metadata emit requires the sourceFiles are passed to WriteFileCallback. ' +
'Update to TypeScript ^1.9.0-dev');
}
if (sourceFiles.length > 1) {
throw new Error('Bundled emit with --out is not supported');
}
if (!this.ngOptions.skipMetadataEmit && !this.ngOptions.flatModuleOutFile) {
this.writeMetadata(fileName, sourceFiles[0]);
}
}
}
export function createSyntheticIndexHost<H extends ts.CompilerHost>(
delegate: H, syntheticIndex: {name: string, content: string, metadata: string}): H {
const normalSyntheticIndexName = normalize(syntheticIndex.name);
const indexContent = syntheticIndex.content;
const indexMetadata = syntheticIndex.metadata;
const newHost = Object.create(delegate);
newHost.fileExists = (fileName: string): boolean => {
return normalize(fileName) == normalSyntheticIndexName || delegate.fileExists(fileName);
};
newHost.readFile = (fileName: string) => {
return normalize(fileName) == normalSyntheticIndexName ? indexContent :
delegate.readFile(fileName);
};
newHost.getSourceFile =
(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) => {
if (normalize(fileName) == normalSyntheticIndexName) {
return ts.createSourceFile(fileName, indexContent, languageVersion, true);
}
return delegate.getSourceFile(fileName, languageVersion, onError);
};
newHost.writeFile =
(fileName: string, data: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
if (fileName.match(DTS) && sourceFiles && sourceFiles.length == 1 &&
normalize(sourceFiles[0].fileName) == normalSyntheticIndexName) {
// If we are writing the synthetic index, write the metadata along side.
const metadataName = fileName.replace(DTS, '.metadata.json');
writeFileSync(metadataName, indexMetadata, {encoding: 'utf8'});
}
};
return newHost;
}

View File

@ -1,680 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {CollectorOptions} from './collector';
import {MetadataEntry, MetadataError, MetadataImportedSymbolReferenceExpression, MetadataSymbolicCallExpression, MetadataValue, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSpreadExpression} from './schema';
import {Symbols} from './symbols';
// In TypeScript 2.1 the spread element kind was renamed.
const spreadElementSyntaxKind: ts.SyntaxKind =
(ts.SyntaxKind as any).SpreadElement || (ts.SyntaxKind as any).SpreadElementExpression;
function isMethodCallOf(callExpression: ts.CallExpression, memberName: string): boolean {
const expression = callExpression.expression;
if (expression.kind === ts.SyntaxKind.PropertyAccessExpression) {
const propertyAccessExpression = <ts.PropertyAccessExpression>expression;
const name = propertyAccessExpression.name;
if (name.kind == ts.SyntaxKind.Identifier) {
return name.text === memberName;
}
}
return false;
}
function isCallOf(callExpression: ts.CallExpression, ident: string): boolean {
const expression = callExpression.expression;
if (expression.kind === ts.SyntaxKind.Identifier) {
const identifier = <ts.Identifier>expression;
return identifier.text === ident;
}
return false;
}
/**
* ts.forEachChild stops iterating children when the callback return a truthy value.
* This method inverts this to implement an `every` style iterator. It will return
* true if every call to `cb` returns `true`.
*/
function everyNodeChild(node: ts.Node, cb: (node: ts.Node) => boolean) {
return !ts.forEachChild(node, node => !cb(node));
}
export function isPrimitive(value: any): boolean {
return Object(value) !== value;
}
function isDefined(obj: any): boolean {
return obj !== undefined;
}
// import {propertyName as name} from 'place'
// import {name} from 'place'
export interface ImportSpecifierMetadata {
name: string;
propertyName?: string;
}
export interface ImportMetadata {
defaultName?: string; // import d from 'place'
namespace?: string; // import * as d from 'place'
namedImports?: ImportSpecifierMetadata[]; // import {a} from 'place'
from: string; // from 'place'
}
function getSourceFileOfNode(node: ts.Node | undefined): ts.SourceFile {
while (node && node.kind != ts.SyntaxKind.SourceFile) {
node = node.parent;
}
return <ts.SourceFile>node;
}
/* @internal */
export function errorSymbol(
message: string, node?: ts.Node, context?: {[name: string]: string},
sourceFile?: ts.SourceFile): MetadataError {
let result: MetadataError|undefined = undefined;
if (node) {
sourceFile = sourceFile || getSourceFileOfNode(node);
if (sourceFile) {
const {line, character} =
ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile));
result = {__symbolic: 'error', message, line, character};
}
}
if (!result) {
result = {__symbolic: 'error', message};
}
if (context) {
result.context = context;
}
return result;
}
/**
* Produce a symbolic representation of an expression folding values into their final value when
* possible.
*/
export class Evaluator {
constructor(
private symbols: Symbols, private nodeMap: Map<MetadataEntry, ts.Node>,
private options: CollectorOptions = {},
private recordExport?: (name: string, value: MetadataValue) => void) {}
nameOf(node: ts.Node|undefined): string|MetadataError {
if (node && node.kind == ts.SyntaxKind.Identifier) {
return (<ts.Identifier>node).text;
}
const result = node && this.evaluateNode(node);
if (isMetadataError(result) || typeof result === 'string') {
return result;
} else {
return errorSymbol(
'Name expected', node, {received: (node && node.getText()) || '<missing>'});
}
}
/**
* Returns true if the expression represented by `node` can be folded into a literal expression.
*
* For example, a literal is always foldable. This means that literal expressions such as `1.2`
* `"Some value"` `true` `false` are foldable.
*
* - An object literal is foldable if all the properties in the literal are foldable.
* - An array literal is foldable if all the elements are foldable.
* - A call is foldable if it is a call to a Array.prototype.concat or a call to CONST_EXPR.
* - A property access is foldable if the object is foldable.
* - A array index is foldable if index expression is foldable and the array is foldable.
* - Binary operator expressions are foldable if the left and right expressions are foldable and
* it is one of '+', '-', '*', '/', '%', '||', and '&&'.
* - An identifier is foldable if a value can be found for its symbol in the evaluator symbol
* table.
*/
public isFoldable(node: ts.Node): boolean {
return this.isFoldableWorker(node, new Map<ts.Node, boolean>());
}
private isFoldableWorker(node: ts.Node|undefined, folding: Map<ts.Node, boolean>): boolean {
if (node) {
switch (node.kind) {
case ts.SyntaxKind.ObjectLiteralExpression:
return everyNodeChild(node, child => {
if (child.kind === ts.SyntaxKind.PropertyAssignment) {
const propertyAssignment = <ts.PropertyAssignment>child;
return this.isFoldableWorker(propertyAssignment.initializer, folding);
}
return false;
});
case ts.SyntaxKind.ArrayLiteralExpression:
return everyNodeChild(node, child => this.isFoldableWorker(child, folding));
case ts.SyntaxKind.CallExpression:
const callExpression = <ts.CallExpression>node;
// We can fold a <array>.concat(<v>).
if (isMethodCallOf(callExpression, 'concat') &&
arrayOrEmpty(callExpression.arguments).length === 1) {
const arrayNode = (<ts.PropertyAccessExpression>callExpression.expression).expression;
if (this.isFoldableWorker(arrayNode, folding) &&
this.isFoldableWorker(callExpression.arguments[0], folding)) {
// It needs to be an array.
const arrayValue = this.evaluateNode(arrayNode);
if (arrayValue && Array.isArray(arrayValue)) {
return true;
}
}
}
// We can fold a call to CONST_EXPR
if (isCallOf(callExpression, 'CONST_EXPR') &&
arrayOrEmpty(callExpression.arguments).length === 1)
return this.isFoldableWorker(callExpression.arguments[0], folding);
return false;
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
case ts.SyntaxKind.StringLiteral:
case ts.SyntaxKind.NumericLiteral:
case ts.SyntaxKind.NullKeyword:
case ts.SyntaxKind.TrueKeyword:
case ts.SyntaxKind.FalseKeyword:
case ts.SyntaxKind.TemplateHead:
case ts.SyntaxKind.TemplateMiddle:
case ts.SyntaxKind.TemplateTail:
return true;
case ts.SyntaxKind.ParenthesizedExpression:
const parenthesizedExpression = <ts.ParenthesizedExpression>node;
return this.isFoldableWorker(parenthesizedExpression.expression, folding);
case ts.SyntaxKind.BinaryExpression:
const binaryExpression = <ts.BinaryExpression>node;
switch (binaryExpression.operatorToken.kind) {
case ts.SyntaxKind.PlusToken:
case ts.SyntaxKind.MinusToken:
case ts.SyntaxKind.AsteriskToken:
case ts.SyntaxKind.SlashToken:
case ts.SyntaxKind.PercentToken:
case ts.SyntaxKind.AmpersandAmpersandToken:
case ts.SyntaxKind.BarBarToken:
return this.isFoldableWorker(binaryExpression.left, folding) &&
this.isFoldableWorker(binaryExpression.right, folding);
default:
return false;
}
case ts.SyntaxKind.PropertyAccessExpression:
const propertyAccessExpression = <ts.PropertyAccessExpression>node;
return this.isFoldableWorker(propertyAccessExpression.expression, folding);
case ts.SyntaxKind.ElementAccessExpression:
const elementAccessExpression = <ts.ElementAccessExpression>node;
return this.isFoldableWorker(elementAccessExpression.expression, folding) &&
this.isFoldableWorker(elementAccessExpression.argumentExpression, folding);
case ts.SyntaxKind.Identifier:
let identifier = <ts.Identifier>node;
let reference = this.symbols.resolve(identifier.text);
if (reference !== undefined && isPrimitive(reference)) {
return true;
}
break;
case ts.SyntaxKind.TemplateExpression:
const templateExpression = <ts.TemplateExpression>node;
return templateExpression.templateSpans.every(
span => this.isFoldableWorker(span.expression, folding));
}
}
return false;
}
/**
* Produce a JSON serialiable object representing `node`. The foldable values in the expression
* tree are folded. For example, a node representing `1 + 2` is folded into `3`.
*/
public evaluateNode(node: ts.Node, preferReference?: boolean): MetadataValue {
const t = this;
let error: MetadataError|undefined;
function recordEntry(entry: MetadataValue, node: ts.Node): MetadataValue {
if (t.options.substituteExpression) {
const newEntry = t.options.substituteExpression(entry, node);
if (t.recordExport && newEntry != entry && isMetadataGlobalReferenceExpression(newEntry)) {
t.recordExport(newEntry.name, entry);
}
entry = newEntry;
}
t.nodeMap.set(entry, node);
return entry;
}
function isFoldableError(value: any): value is MetadataError {
return !t.options.verboseInvalidExpression && isMetadataError(value);
}
const resolveName = (name: string, preferReference?: boolean): MetadataValue => {
const reference = this.symbols.resolve(name, preferReference);
if (reference === undefined) {
// Encode as a global reference. StaticReflector will check the reference.
return recordEntry({__symbolic: 'reference', name}, node);
}
return reference;
};
switch (node.kind) {
case ts.SyntaxKind.ObjectLiteralExpression:
let obj: {[name: string]: any} = {};
let quoted: string[] = [];
ts.forEachChild(node, child => {
switch (child.kind) {
case ts.SyntaxKind.ShorthandPropertyAssignment:
case ts.SyntaxKind.PropertyAssignment:
const assignment = <ts.PropertyAssignment|ts.ShorthandPropertyAssignment>child;
if (assignment.name.kind == ts.SyntaxKind.StringLiteral) {
const name = (assignment.name as ts.StringLiteral).text;
quoted.push(name);
}
const propertyName = this.nameOf(assignment.name);
if (isFoldableError(propertyName)) {
error = propertyName;
return true;
}
const propertyValue = isPropertyAssignment(assignment) ?
this.evaluateNode(assignment.initializer, /* preferReference */ true) :
resolveName(propertyName, /* preferReference */ true);
if (isFoldableError(propertyValue)) {
error = propertyValue;
return true; // Stop the forEachChild.
} else {
obj[<string>propertyName] = isPropertyAssignment(assignment) ?
recordEntry(propertyValue, assignment.initializer) :
propertyValue;
}
}
});
if (error) return error;
if (this.options.quotedNames && quoted.length) {
obj['$quoted$'] = quoted;
}
return recordEntry(obj, node);
case ts.SyntaxKind.ArrayLiteralExpression:
let arr: MetadataValue[] = [];
ts.forEachChild(node, child => {
const value = this.evaluateNode(child, /* preferReference */ true);
// Check for error
if (isFoldableError(value)) {
error = value;
return true; // Stop the forEachChild.
}
// Handle spread expressions
if (isMetadataSymbolicSpreadExpression(value)) {
if (Array.isArray(value.expression)) {
for (const spreadValue of value.expression) {
arr.push(spreadValue);
}
return;
}
}
arr.push(value);
});
if (error) return error;
return recordEntry(arr, node);
case spreadElementSyntaxKind:
let spreadExpression = this.evaluateNode((node as any).expression);
return recordEntry({__symbolic: 'spread', expression: spreadExpression}, node);
case ts.SyntaxKind.CallExpression:
const callExpression = <ts.CallExpression>node;
if (isCallOf(callExpression, 'forwardRef') &&
arrayOrEmpty(callExpression.arguments).length === 1) {
const firstArgument = callExpression.arguments[0];
if (firstArgument.kind == ts.SyntaxKind.ArrowFunction) {
const arrowFunction = <ts.ArrowFunction>firstArgument;
return recordEntry(this.evaluateNode(arrowFunction.body), node);
}
}
const args = arrayOrEmpty(callExpression.arguments).map(arg => this.evaluateNode(arg));
if (!this.options.verboseInvalidExpression && args.some(isMetadataError)) {
return args.find(isMetadataError);
}
if (this.isFoldable(callExpression)) {
if (isMethodCallOf(callExpression, 'concat')) {
const arrayValue = <MetadataValue[]>this.evaluateNode(
(<ts.PropertyAccessExpression>callExpression.expression).expression);
if (isFoldableError(arrayValue)) return arrayValue;
return arrayValue.concat(args[0]);
}
}
// Always fold a CONST_EXPR even if the argument is not foldable.
if (isCallOf(callExpression, 'CONST_EXPR') &&
arrayOrEmpty(callExpression.arguments).length === 1) {
return recordEntry(args[0], node);
}
const expression = this.evaluateNode(callExpression.expression);
if (isFoldableError(expression)) {
return recordEntry(expression, node);
}
let result: MetadataSymbolicCallExpression = {__symbolic: 'call', expression: expression};
if (args && args.length) {
result.arguments = args;
}
return recordEntry(result, node);
case ts.SyntaxKind.NewExpression:
const newExpression = <ts.NewExpression>node;
const newArgs = arrayOrEmpty(newExpression.arguments).map(arg => this.evaluateNode(arg));
if (!this.options.verboseInvalidExpression && newArgs.some(isMetadataError)) {
return recordEntry(newArgs.find(isMetadataError), node);
}
const newTarget = this.evaluateNode(newExpression.expression);
if (isMetadataError(newTarget)) {
return recordEntry(newTarget, node);
}
const call: MetadataSymbolicCallExpression = {__symbolic: 'new', expression: newTarget};
if (newArgs.length) {
call.arguments = newArgs;
}
return recordEntry(call, node);
case ts.SyntaxKind.PropertyAccessExpression: {
const propertyAccessExpression = <ts.PropertyAccessExpression>node;
const expression = this.evaluateNode(propertyAccessExpression.expression);
if (isFoldableError(expression)) {
return recordEntry(expression, node);
}
const member = this.nameOf(propertyAccessExpression.name);
if (isFoldableError(member)) {
return recordEntry(member, node);
}
if (expression && this.isFoldable(propertyAccessExpression.expression))
return (<any>expression)[<string>member];
if (isMetadataModuleReferenceExpression(expression)) {
// A select into a module reference and be converted into a reference to the symbol
// in the module
return recordEntry(
{__symbolic: 'reference', module: expression.module, name: member}, node);
}
return recordEntry({__symbolic: 'select', expression, member}, node);
}
case ts.SyntaxKind.ElementAccessExpression: {
const elementAccessExpression = <ts.ElementAccessExpression>node;
const expression = this.evaluateNode(elementAccessExpression.expression);
if (isFoldableError(expression)) {
return recordEntry(expression, node);
}
if (!elementAccessExpression.argumentExpression) {
return recordEntry(errorSymbol('Expression form not supported', node), node);
}
const index = this.evaluateNode(elementAccessExpression.argumentExpression);
if (isFoldableError(expression)) {
return recordEntry(expression, node);
}
if (this.isFoldable(elementAccessExpression.expression) &&
this.isFoldable(elementAccessExpression.argumentExpression))
return (<any>expression)[<string|number>index];
return recordEntry({__symbolic: 'index', expression, index}, node);
}
case ts.SyntaxKind.Identifier:
const identifier = <ts.Identifier>node;
const name = identifier.text;
return resolveName(name, preferReference);
case ts.SyntaxKind.TypeReference:
const typeReferenceNode = <ts.TypeReferenceNode>node;
const typeNameNode = typeReferenceNode.typeName;
const getReference: (typeNameNode: ts.Identifier | ts.QualifiedName) => MetadataValue =
node => {
if (typeNameNode.kind === ts.SyntaxKind.QualifiedName) {
const qualifiedName = <ts.QualifiedName>node;
const left = this.evaluateNode(qualifiedName.left);
if (isMetadataModuleReferenceExpression(left)) {
return recordEntry(
<MetadataImportedSymbolReferenceExpression>{
__symbolic: 'reference',
module: left.module,
name: qualifiedName.right.text
},
node);
}
// Record a type reference to a declared type as a select.
return {__symbolic: 'select', expression: left, member: qualifiedName.right.text};
} else {
const identifier = <ts.Identifier>typeNameNode;
const symbol = this.symbols.resolve(identifier.text);
if (isFoldableError(symbol) || isMetadataSymbolicReferenceExpression(symbol)) {
return recordEntry(symbol, node);
}
return recordEntry(
errorSymbol('Could not resolve type', node, {typeName: identifier.text}), node);
}
};
const typeReference = getReference(typeNameNode);
if (isFoldableError(typeReference)) {
return recordEntry(typeReference, node);
}
if (!isMetadataModuleReferenceExpression(typeReference) &&
typeReferenceNode.typeArguments && typeReferenceNode.typeArguments.length) {
const args = typeReferenceNode.typeArguments.map(element => this.evaluateNode(element));
// TODO: Remove typecast when upgraded to 2.0 as it will be corretly inferred.
// Some versions of 1.9 do not infer this correctly.
(<MetadataImportedSymbolReferenceExpression>typeReference).arguments = args;
}
return recordEntry(typeReference, node);
case ts.SyntaxKind.UnionType:
const unionType = <ts.UnionTypeNode>node;
// Remove null and undefined from the list of unions.
const references = unionType.types
.filter(
n => n.kind != ts.SyntaxKind.NullKeyword &&
n.kind != ts.SyntaxKind.UndefinedKeyword)
.map(n => this.evaluateNode(n));
// The remmaining reference must be the same. If two have type arguments consider them
// different even if the type arguments are the same.
let candidate: any = null;
for (let i = 0; i < references.length; i++) {
const reference = references[i];
if (isMetadataSymbolicReferenceExpression(reference)) {
if (candidate) {
if ((reference as any).name == candidate.name &&
(reference as any).module == candidate.module && !(reference as any).arguments) {
candidate = reference;
}
} else {
candidate = reference;
}
} else {
return reference;
}
}
if (candidate) return candidate;
break;
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
case ts.SyntaxKind.StringLiteral:
case ts.SyntaxKind.TemplateHead:
case ts.SyntaxKind.TemplateTail:
case ts.SyntaxKind.TemplateMiddle:
return (<ts.LiteralLikeNode>node).text;
case ts.SyntaxKind.NumericLiteral:
return parseFloat((<ts.LiteralExpression>node).text);
case ts.SyntaxKind.AnyKeyword:
return recordEntry({__symbolic: 'reference', name: 'any'}, node);
case ts.SyntaxKind.StringKeyword:
return recordEntry({__symbolic: 'reference', name: 'string'}, node);
case ts.SyntaxKind.NumberKeyword:
return recordEntry({__symbolic: 'reference', name: 'number'}, node);
case ts.SyntaxKind.BooleanKeyword:
return recordEntry({__symbolic: 'reference', name: 'boolean'}, node);
case ts.SyntaxKind.ArrayType:
const arrayTypeNode = <ts.ArrayTypeNode>node;
return recordEntry(
{
__symbolic: 'reference',
name: 'Array',
arguments: [this.evaluateNode(arrayTypeNode.elementType)]
},
node);
case ts.SyntaxKind.NullKeyword:
return null;
case ts.SyntaxKind.TrueKeyword:
return true;
case ts.SyntaxKind.FalseKeyword:
return false;
case ts.SyntaxKind.ParenthesizedExpression:
const parenthesizedExpression = <ts.ParenthesizedExpression>node;
return this.evaluateNode(parenthesizedExpression.expression);
case ts.SyntaxKind.TypeAssertionExpression:
const typeAssertion = <ts.TypeAssertion>node;
return this.evaluateNode(typeAssertion.expression);
case ts.SyntaxKind.PrefixUnaryExpression:
const prefixUnaryExpression = <ts.PrefixUnaryExpression>node;
const operand = this.evaluateNode(prefixUnaryExpression.operand);
if (isDefined(operand) && isPrimitive(operand)) {
switch (prefixUnaryExpression.operator) {
case ts.SyntaxKind.PlusToken:
return +(operand as any);
case ts.SyntaxKind.MinusToken:
return -(operand as any);
case ts.SyntaxKind.TildeToken:
return ~(operand as any);
case ts.SyntaxKind.ExclamationToken:
return !operand;
}
}
let operatorText: string;
switch (prefixUnaryExpression.operator) {
case ts.SyntaxKind.PlusToken:
operatorText = '+';
break;
case ts.SyntaxKind.MinusToken:
operatorText = '-';
break;
case ts.SyntaxKind.TildeToken:
operatorText = '~';
break;
case ts.SyntaxKind.ExclamationToken:
operatorText = '!';
break;
default:
return undefined;
}
return recordEntry({__symbolic: 'pre', operator: operatorText, operand: operand}, node);
case ts.SyntaxKind.BinaryExpression:
const binaryExpression = <ts.BinaryExpression>node;
const left = this.evaluateNode(binaryExpression.left);
const right = this.evaluateNode(binaryExpression.right);
if (isDefined(left) && isDefined(right)) {
if (isPrimitive(left) && isPrimitive(right))
switch (binaryExpression.operatorToken.kind) {
case ts.SyntaxKind.BarBarToken:
return <any>left || <any>right;
case ts.SyntaxKind.AmpersandAmpersandToken:
return <any>left && <any>right;
case ts.SyntaxKind.AmpersandToken:
return <any>left & <any>right;
case ts.SyntaxKind.BarToken:
return <any>left | <any>right;
case ts.SyntaxKind.CaretToken:
return <any>left ^ <any>right;
case ts.SyntaxKind.EqualsEqualsToken:
return <any>left == <any>right;
case ts.SyntaxKind.ExclamationEqualsToken:
return <any>left != <any>right;
case ts.SyntaxKind.EqualsEqualsEqualsToken:
return <any>left === <any>right;
case ts.SyntaxKind.ExclamationEqualsEqualsToken:
return <any>left !== <any>right;
case ts.SyntaxKind.LessThanToken:
return <any>left < <any>right;
case ts.SyntaxKind.GreaterThanToken:
return <any>left > <any>right;
case ts.SyntaxKind.LessThanEqualsToken:
return <any>left <= <any>right;
case ts.SyntaxKind.GreaterThanEqualsToken:
return <any>left >= <any>right;
case ts.SyntaxKind.LessThanLessThanToken:
return (<any>left) << (<any>right);
case ts.SyntaxKind.GreaterThanGreaterThanToken:
return <any>left >> <any>right;
case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
return <any>left >>> <any>right;
case ts.SyntaxKind.PlusToken:
return <any>left + <any>right;
case ts.SyntaxKind.MinusToken:
return <any>left - <any>right;
case ts.SyntaxKind.AsteriskToken:
return <any>left * <any>right;
case ts.SyntaxKind.SlashToken:
return <any>left / <any>right;
case ts.SyntaxKind.PercentToken:
return <any>left % <any>right;
}
return recordEntry(
{
__symbolic: 'binop',
operator: binaryExpression.operatorToken.getText(),
left: left,
right: right
},
node);
}
break;
case ts.SyntaxKind.ConditionalExpression:
const conditionalExpression = <ts.ConditionalExpression>node;
const condition = this.evaluateNode(conditionalExpression.condition);
const thenExpression = this.evaluateNode(conditionalExpression.whenTrue);
const elseExpression = this.evaluateNode(conditionalExpression.whenFalse);
if (isPrimitive(condition)) {
return condition ? thenExpression : elseExpression;
}
return recordEntry({__symbolic: 'if', condition, thenExpression, elseExpression}, node);
case ts.SyntaxKind.FunctionExpression:
case ts.SyntaxKind.ArrowFunction:
return recordEntry(errorSymbol('Function call not supported', node), node);
case ts.SyntaxKind.TaggedTemplateExpression:
return recordEntry(
errorSymbol('Tagged template expressions are not supported in metadata', node), node);
case ts.SyntaxKind.TemplateExpression:
const templateExpression = <ts.TemplateExpression>node;
if (this.isFoldable(node)) {
return templateExpression.templateSpans.reduce(
(previous, current) => previous + <string>this.evaluateNode(current.expression) +
<string>this.evaluateNode(current.literal),
this.evaluateNode(templateExpression.head));
} else {
return templateExpression.templateSpans.reduce((previous, current) => {
const expr = this.evaluateNode(current.expression);
const literal = this.evaluateNode(current.literal);
if (isFoldableError(expr)) return expr;
if (isFoldableError(literal)) return literal;
if (typeof previous === 'string' && typeof expr === 'string' &&
typeof literal === 'string') {
return previous + expr + literal;
}
let result = expr;
if (previous !== '') {
result = {__symbolic: 'binop', operator: '+', left: previous, right: expr};
}
if (literal != '') {
result = {__symbolic: 'binop', operator: '+', left: result, right: literal};
}
return result;
}, this.evaluateNode(templateExpression.head));
}
case ts.SyntaxKind.AsExpression:
const asExpression = <ts.AsExpression>node;
return this.evaluateNode(asExpression.expression);
case ts.SyntaxKind.ClassExpression:
return {__symbolic: 'class'};
}
return recordEntry(errorSymbol('Expression form not supported', node), node);
}
}
function isPropertyAssignment(node: ts.Node): node is ts.PropertyAssignment {
return node.kind == ts.SyntaxKind.PropertyAssignment;
}
const empty = ts.createNodeArray<any>();
function arrayOrEmpty<T extends ts.Node>(v: ts.NodeArray<T>| undefined): ts.NodeArray<T> {
return v || empty;
}

View File

@ -1,58 +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 {BundlePrivateEntry} from './bundler';
const INDEX_HEADER = `/**
* Generated bundle index. Do not edit.
*/
`;
type MapEntry = [string, BundlePrivateEntry[]];
export function privateEntriesToIndex(index: string, privates: BundlePrivateEntry[]): string {
const results: string[] = [INDEX_HEADER];
// Export all of the index symbols.
results.push(`export * from '${index}';`, '');
// Simplify the exports
const exports = new Map<string, BundlePrivateEntry[]>();
for (const entry of privates) {
let entries = exports.get(entry.module);
if (!entries) {
entries = [];
exports.set(entry.module, entries);
}
entries.push(entry);
}
const compareEntries = compare((e: BundlePrivateEntry) => e.name);
const compareModules = compare((e: MapEntry) => e[0]);
const orderedExports =
Array.from(exports)
.map(([module, entries]) => <MapEntry>[module, entries.sort(compareEntries)])
.sort(compareModules);
for (const [module, entries] of orderedExports) {
let symbols = entries.map(e => `${e.name} as ${e.privateName}`);
results.push(`export {${symbols}} from '${module}';`);
}
return results.join('\n');
}
function compare<E, T>(select: (e: E) => T): (a: E, b: E) => number {
return (a, b) => {
const ak = select(a);
const bk = select(b);
return ak > bk ? 1 : ak < bk ? -1 : 0;
};
}

View File

@ -1,139 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as fs from 'fs';
import * as path from 'path';
import * as tsickle from 'tsickle';
import * as ts from 'typescript';
import {CliOptions} from './cli_options';
import {MetadataWriterHost} from './compiler_host';
import {CodegenExtension, createBundleIndexHost} from './main_no_tsickle';
import {check, tsc} from './tsc';
import {VinylFile, isVinylFile} from './vinyl_file';
const TS_EXT = /\.ts$/;
export function main(
project: string | VinylFile, cliOptions: CliOptions, codegen?: CodegenExtension,
options?: ts.CompilerOptions): Promise<any> {
try {
let projectDir = project;
// project is vinyl like file object
if (isVinylFile(project)) {
projectDir = path.dirname(project.path);
}
// project is path to project file
else if (fs.lstatSync(project).isFile()) {
projectDir = path.dirname(project);
}
// file names in tsconfig are resolved relative to this absolute path
const basePath = path.resolve(process.cwd(), cliOptions.basePath || projectDir);
// read the configuration options from wherever you store them
let {parsed, ngOptions} = tsc.readConfiguration(project, basePath, options);
ngOptions.basePath = basePath;
let rootFileNames: string[] = parsed.fileNames.slice(0);
const createProgram = (host: ts.CompilerHost, oldProgram?: ts.Program) => {
return ts.createProgram(rootFileNames.slice(0), parsed.options, host, oldProgram);
};
const addGeneratedFileName = (genFileName: string) => {
if (genFileName.startsWith(basePath) && TS_EXT.exec(genFileName)) {
rootFileNames.push(genFileName);
}
};
const diagnostics = (parsed.options as any).diagnostics;
if (diagnostics) (ts as any).performance.enable();
let host = ts.createCompilerHost(parsed.options, true);
// Make sure we do not `host.realpath()` from TS as we do not want to resolve symlinks.
// https://github.com/Microsoft/TypeScript/issues/9552
host.realpath = (fileName: string) => fileName;
// If the compilation is a flat module index then produce the flat module index
// metadata and the synthetic flat module index.
if (ngOptions.flatModuleOutFile && !ngOptions.skipMetadataEmit) {
const {host: bundleHost, indexName, errors} =
createBundleIndexHost(ngOptions, rootFileNames, host);
if (errors) check(errors);
if (indexName) addGeneratedFileName(indexName);
host = bundleHost;
}
const tsickleHost: tsickle.TsickleHost = {
shouldSkipTsickleProcessing: (fileName) => /\.d\.ts$/.test(fileName),
pathToModuleName: (context, importPath) => '',
shouldIgnoreWarningsForPath: (filePath) => false,
fileNameToModuleId: (fileName) => fileName,
googmodule: false,
untyped: true,
convertIndexImportShorthand: false,
transformDecorators: ngOptions.annotationsAs !== 'decorators',
transformTypesToClosure: ngOptions.annotateForClosureCompiler,
};
const program = createProgram(host);
const errors = program.getOptionsDiagnostics();
check(errors);
if (ngOptions.skipTemplateCodegen || !codegen) {
codegen = () => Promise.resolve([]);
}
if (diagnostics) console.time('NG codegen');
return codegen(ngOptions, cliOptions, program, host).then((genFiles) => {
if (diagnostics) console.timeEnd('NG codegen');
// Add the generated files to the configuration so they will become part of the program.
if (ngOptions.alwaysCompileGeneratedCode) {
genFiles.forEach(genFileName => addGeneratedFileName(genFileName));
}
if (!ngOptions.skipMetadataEmit) {
host = new MetadataWriterHost(host, ngOptions, true);
}
// Create a new program since codegen files were created after making the old program
let programWithCodegen = createProgram(host, program);
tsc.typeCheck(host, programWithCodegen);
if (diagnostics) console.time('Emit');
const {diagnostics: emitDiags} =
tsickle.emitWithTsickle(programWithCodegen, tsickleHost, host, ngOptions);
if (diagnostics) console.timeEnd('Emit');
check(emitDiags);
if (diagnostics) {
(ts as any).performance.forEachMeasure(
(name: string, duration: number) => { console.error(`TS ${name}: ${duration}ms`); });
}
});
} catch (e) {
return Promise.reject(e);
}
}
// CLI entry point
if (require.main === module) {
const args = process.argv.slice(2);
let {options, errors} = (ts as any).parseCommandLine(args);
check(errors);
const project = options.project || '.';
// TODO(alexeagle): command line should be TSC-compatible, remove "CliOptions" here
const cliOptions = new CliOptions(require('minimist')(args));
main(project, cliOptions, undefined, options)
.then((exitCode: any) => process.exit(exitCode))
.catch((e: any) => {
console.error(e.stack);
console.error('Compilation failed');
process.exit(1);
});
}

View File

@ -1,61 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as path from 'path';
import * as ts from 'typescript';
import {CompilerHostAdapter, MetadataBundler} from './bundler';
import {CliOptions} from './cli_options';
import {createSyntheticIndexHost} from './compiler_host';
import {privateEntriesToIndex} from './index_writer';
import NgOptions from './options';
export {UserError} from './tsc';
export interface CodegenExtension {
/**
* Returns the generated file names.
*/
(ngOptions: NgOptions, cliOptions: CliOptions, program: ts.Program,
host: ts.CompilerHost): Promise<string[]>;
}
const DTS = /\.d\.ts$/;
const JS_EXT = /(\.js|)$/;
export function createBundleIndexHost<H extends ts.CompilerHost>(
ngOptions: NgOptions, rootFiles: string[],
host: H): {host: H, indexName?: string, errors?: ts.Diagnostic[]} {
const files = rootFiles.filter(f => !DTS.test(f));
if (files.length != 1) {
return {
host,
errors: [{
file: null as any as ts.SourceFile,
start: null as any as number,
length: null as any as number,
messageText:
'Angular compiler option "flatModuleIndex" requires one and only one .ts file in the "files" field.',
category: ts.DiagnosticCategory.Error,
code: 0
}]
};
}
const file = files[0];
const indexModule = file.replace(/\.ts$/, '');
const bundler =
new MetadataBundler(indexModule, ngOptions.flatModuleId, new CompilerHostAdapter(host));
const metadataBundle = bundler.getMetadataBundle();
const metadata = JSON.stringify(metadataBundle.metadata);
const name =
path.join(path.dirname(indexModule), ngOptions.flatModuleOutFile !.replace(JS_EXT, '.ts'));
const libraryIndex = `./${path.basename(indexModule)}`;
const content = privateEntriesToIndex(libraryIndex, metadataBundle.privates);
host = createSyntheticIndexHost(host, {name, content, metadata});
return {host, indexName: name};
}

View File

@ -1,96 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
interface Options extends ts.CompilerOptions {
// Absolute path to a directory where generated file structure is written.
// If unspecified, generated files will be written alongside sources.
genDir?: string;
// Path to the directory containing the tsconfig.json file.
basePath?: string;
// Don't produce .metadata.json files (they don't work for bundled emit with --out)
skipMetadataEmit?: boolean;
// Produce an error if the metadata written for a class would produce an error if used.
strictMetadataEmit?: boolean;
// Don't produce .ngfactory.ts or .ngstyle.ts files
skipTemplateCodegen?: boolean;
// Whether to generate a flat module index of the given name and the corresponding
// flat module metadata. This option is intended to be used when creating flat
// modules similar to how `@angular/core` and `@angular/common` are packaged.
// When this option is used the `package.json` for the library should referred to the
// generated flat module index instead of the library index file. When using this
// option only one .metadata.json file is produced that contains all the metadata
// necessary for symbols exported from the library index.
// In the generated .ngfactory.ts files flat module index is used to import symbols
// includes both the public API from the library index as well as shrowded internal
// symbols.
// By default the .ts file supplied in the `files` files field is assumed to be
// library index. If more than one is specified, uses `libraryIndex` to select the
// file to use. If more than on .ts file is supplied and no `libraryIndex` is supplied
// an error is produced.
// A flat module index .d.ts and .js will be created with the given `flatModuleOutFile`
// name in the same location as the library index .d.ts file is emitted.
// For example, if a library uses `public_api.ts` file as the library index of the
// module the `tsconfig.json` `files` field would be `["public_api.ts"]`. The
// `flatModuleOutFile` options could then be set to, for example `"index.js"`, which
// produces `index.d.ts` and `index.metadata.json` files. The library's
// `package.json`'s `module` field would be `"index.js"` and the `typings` field would
// be `"index.d.ts"`.
flatModuleOutFile?: string;
// Preferred module id to use for importing flat module. References generated by `ngc`
// will use this module name when importing symbols from the flat module. This is only
// meaningful when `flatModuleOutFile` is also supplied. It is otherwise ignored.
flatModuleId?: string;
// Whether to generate code for library code.
// If true, produce .ngfactory.ts and .ngstyle.ts files for .d.ts inputs.
// Default is true.
generateCodeForLibraries?: boolean;
// Insert JSDoc type annotations needed by Closure Compiler
annotateForClosureCompiler?: boolean;
// Modify how angular annotations are emitted to improve tree-shaking.
// Default is static fields.
// decorators: Leave the Decorators in-place. This makes compilation faster.
// TypeScript will emit calls to the __decorate helper.
// `--emitDecoratorMetadata` can be used for runtime reflection.
// However, the resulting code will not properly tree-shake.
// static fields: Replace decorators with a static field in the class.
// Allows advanced tree-shakers like Closure Compiler to remove
// unused classes.
annotationsAs?: 'decorators'|'static fields';
// Print extra information while running the compiler
trace?: boolean;
// Whether to enable support for <template> and the template attribute (false by default)
enableLegacyTemplate?: boolean;
// Whether to generate .ngsummary.ts files that allow to use AOTed artifacts
// in JIT mode. This is off by default.
enableSummariesForJit?: boolean;
// Whether to compile generated .ngfacgtory.ts files, even when they are no
// matched by the `files` / `includes` in the `tsconfig.json`.
// This is off by default.
alwaysCompileGeneratedCode?: boolean;
// Whether to remove blank text nodes from compiled templates. It is `true` by default
// in Angular 5 and will be re-visited in Angular 6.
preserveWhitespaces?: boolean;
}
export default Options;

View File

@ -1,284 +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
*/
// Metadata Schema
// If you make a backwards incompatible change to the schema, increment the VERSION number.
// If you make a backwards compatible change to the metadata (such as adding an option field) then
// leave VERSION the same. If possible, as many versions of the metadata that can represent the
// semantics of the file in an array. For example, when generating a version 2 file, if version 1
// can accurately represent the metadata, generate both version 1 and version 2 in an array.
export const VERSION = 3;
export type MetadataEntry = ClassMetadata | InterfaceMetadata | FunctionMetadata | MetadataValue;
export interface ModuleMetadata {
__symbolic: 'module';
version: number;
exports?: ModuleExportMetadata[];
importAs?: string;
metadata: {[name: string]: MetadataEntry};
origins?: {[name: string]: string};
}
export function isModuleMetadata(value: any): value is ModuleMetadata {
return value && value.__symbolic === 'module';
}
export interface ModuleExportMetadata {
export?: (string|{name: string, as: string})[];
from: string;
}
export interface ClassMetadata {
__symbolic: 'class';
extends?: MetadataSymbolicExpression|MetadataError;
arity?: number;
decorators?: (MetadataSymbolicExpression|MetadataError)[];
members?: MetadataMap;
statics?: {[name: string]: MetadataValue | FunctionMetadata};
}
export function isClassMetadata(value: any): value is ClassMetadata {
return value && value.__symbolic === 'class';
}
export interface InterfaceMetadata { __symbolic: 'interface'; }
export function isInterfaceMetadata(value: any): value is InterfaceMetadata {
return value && value.__symbolic === 'interface';
}
export interface MetadataMap { [name: string]: MemberMetadata[]; }
export interface MemberMetadata {
__symbolic: 'constructor'|'method'|'property';
decorators?: (MetadataSymbolicExpression|MetadataError)[];
}
export function isMemberMetadata(value: any): value is MemberMetadata {
if (value) {
switch (value.__symbolic) {
case 'constructor':
case 'method':
case 'property':
return true;
}
}
return false;
}
export interface MethodMetadata extends MemberMetadata {
__symbolic: 'constructor'|'method';
parameterDecorators?: ((MetadataSymbolicExpression | MetadataError)[]|undefined)[];
}
export function isMethodMetadata(value: any): value is MethodMetadata {
return value && (value.__symbolic === 'constructor' || value.__symbolic === 'method');
}
export interface ConstructorMetadata extends MethodMetadata {
__symbolic: 'constructor';
parameters?: (MetadataSymbolicExpression|MetadataError|null|undefined)[];
}
export function isConstructorMetadata(value: any): value is ConstructorMetadata {
return value && value.__symbolic === 'constructor';
}
export interface FunctionMetadata {
__symbolic: 'function';
parameters: string[];
defaults?: MetadataValue[];
value: MetadataValue;
}
export function isFunctionMetadata(value: any): value is FunctionMetadata {
return value && value.__symbolic === 'function';
}
export type MetadataValue = string | number | boolean | undefined | null | MetadataObject |
MetadataArray | MetadataSymbolicExpression | MetadataError;
export interface MetadataObject { [name: string]: MetadataValue; }
export interface MetadataArray { [name: number]: MetadataValue; }
export interface MetadataSymbolicExpression {
__symbolic: 'binary'|'call'|'index'|'new'|'pre'|'reference'|'select'|'spread'|'if';
}
export function isMetadataSymbolicExpression(value: any): value is MetadataSymbolicExpression {
if (value) {
switch (value.__symbolic) {
case 'binary':
case 'call':
case 'index':
case 'new':
case 'pre':
case 'reference':
case 'select':
case 'spread':
case 'if':
return true;
}
}
return false;
}
export interface MetadataSymbolicBinaryExpression extends MetadataSymbolicExpression {
__symbolic: 'binary';
operator: '&&'|'||'|'|'|'^'|'&'|'=='|'!='|'==='|'!=='|'<'|'>'|'<='|'>='|'instanceof'|'in'|'as'|
'<<'|'>>'|'>>>'|'+'|'-'|'*'|'/'|'%'|'**';
left: MetadataValue;
right: MetadataValue;
}
export function isMetadataSymbolicBinaryExpression(value: any):
value is MetadataSymbolicBinaryExpression {
return value && value.__symbolic === 'binary';
}
export interface MetadataSymbolicIndexExpression extends MetadataSymbolicExpression {
__symbolic: 'index';
expression: MetadataValue;
index: MetadataValue;
}
export function isMetadataSymbolicIndexExpression(value: any):
value is MetadataSymbolicIndexExpression {
return value && value.__symbolic === 'index';
}
export interface MetadataSymbolicCallExpression extends MetadataSymbolicExpression {
__symbolic: 'call'|'new';
expression: MetadataValue;
arguments?: MetadataValue[];
}
export function isMetadataSymbolicCallExpression(value: any):
value is MetadataSymbolicCallExpression {
return value && (value.__symbolic === 'call' || value.__symbolic === 'new');
}
export interface MetadataSymbolicPrefixExpression extends MetadataSymbolicExpression {
__symbolic: 'pre';
operator: '+'|'-'|'~'|'!';
operand: MetadataValue;
}
export function isMetadataSymbolicPrefixExpression(value: any):
value is MetadataSymbolicPrefixExpression {
return value && value.__symbolic === 'pre';
}
export interface MetadataSymbolicIfExpression extends MetadataSymbolicExpression {
__symbolic: 'if';
condition: MetadataValue;
thenExpression: MetadataValue;
elseExpression: MetadataValue;
}
export function isMetadataSymbolicIfExpression(value: any): value is MetadataSymbolicIfExpression {
return value && value.__symbolic === 'if';
}
export interface MetadataGlobalReferenceExpression extends MetadataSymbolicExpression {
__symbolic: 'reference';
name: string;
arguments?: MetadataValue[];
}
export function isMetadataGlobalReferenceExpression(value: any):
value is MetadataGlobalReferenceExpression {
return value && value.name && !value.module && isMetadataSymbolicReferenceExpression(value);
}
export interface MetadataModuleReferenceExpression extends MetadataSymbolicExpression {
__symbolic: 'reference';
module: string;
}
export function isMetadataModuleReferenceExpression(value: any):
value is MetadataModuleReferenceExpression {
return value && value.module && !value.name && !value.default &&
isMetadataSymbolicReferenceExpression(value);
}
export interface MetadataImportedSymbolReferenceExpression extends MetadataSymbolicExpression {
__symbolic: 'reference';
module: string;
name: string;
arguments?: MetadataValue[];
}
export function isMetadataImportedSymbolReferenceExpression(value: any):
value is MetadataImportedSymbolReferenceExpression {
return value && value.module && !!value.name && isMetadataSymbolicReferenceExpression(value);
}
export interface MetadataImportedDefaultReferenceExpression extends MetadataSymbolicExpression {
__symbolic: 'reference';
module: string;
default:
boolean;
arguments?: MetadataValue[];
}
export function isMetadataImportDefaultReference(value: any):
value is MetadataImportedDefaultReferenceExpression {
return value.module && value.default && isMetadataSymbolicReferenceExpression(value);
}
export type MetadataSymbolicReferenceExpression = MetadataGlobalReferenceExpression |
MetadataModuleReferenceExpression | MetadataImportedSymbolReferenceExpression |
MetadataImportedDefaultReferenceExpression;
export function isMetadataSymbolicReferenceExpression(value: any):
value is MetadataSymbolicReferenceExpression {
return value && value.__symbolic === 'reference';
}
export interface MetadataSymbolicSelectExpression extends MetadataSymbolicExpression {
__symbolic: 'select';
expression: MetadataValue;
name: string;
}
export function isMetadataSymbolicSelectExpression(value: any):
value is MetadataSymbolicSelectExpression {
return value && value.__symbolic === 'select';
}
export interface MetadataSymbolicSpreadExpression extends MetadataSymbolicExpression {
__symbolic: 'spread';
expression: MetadataValue;
}
export function isMetadataSymbolicSpreadExpression(value: any):
value is MetadataSymbolicSpreadExpression {
return value && value.__symbolic === 'spread';
}
export interface MetadataError {
__symbolic: 'error';
/**
* This message should be short and relatively discriptive and should be fixed once it is created.
* If the reader doesn't recognize the message, it will display the message unmodified. If the
* reader recognizes the error message is it free to use substitute message the is more
* descriptive and/or localized.
*/
message: string;
/**
* The line number of the error in the .ts file the metadata was created for.
*/
line?: number;
/**
* The number of utf8 code-units from the beginning of the file of the error.
*/
character?: number;
/**
* The module of the error (only used in bundled metadata)
*/
module?: string;
/**
* Context information that can be used to generate a more descriptive error message. The content
* of the context is dependent on the error message.
*/
context?: {[name: string]: string};
}
export function isMetadataError(value: any): value is MetadataError {
return value && value.__symbolic === 'error';
}

View File

@ -1,130 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {MetadataSymbolicReferenceExpression, MetadataValue} from './schema';
export class Symbols {
private _symbols: Map<string, MetadataValue>;
private references = new Map<string, MetadataSymbolicReferenceExpression>();
constructor(private sourceFile: ts.SourceFile) {}
resolve(name: string, preferReference?: boolean): MetadataValue|undefined {
return (preferReference && this.references.get(name)) || this.symbols.get(name);
}
define(name: string, value: MetadataValue) { this.symbols.set(name, value); }
defineReference(name: string, value: MetadataSymbolicReferenceExpression) {
this.references.set(name, value);
}
has(name: string): boolean { return this.symbols.has(name); }
private get symbols(): Map<string, MetadataValue> {
let result = this._symbols;
if (!result) {
result = this._symbols = new Map<string, MetadataValue>();
populateBuiltins(result);
this.buildImports();
}
return result;
}
private buildImports(): void {
const symbols = this._symbols;
// Collect the imported symbols into this.symbols
const stripQuotes = (s: string) => s.replace(/^['"]|['"]$/g, '');
const visit = (node: ts.Node) => {
switch (node.kind) {
case ts.SyntaxKind.ImportEqualsDeclaration:
const importEqualsDeclaration = <ts.ImportEqualsDeclaration>node;
if (importEqualsDeclaration.moduleReference.kind ===
ts.SyntaxKind.ExternalModuleReference) {
const externalReference =
<ts.ExternalModuleReference>importEqualsDeclaration.moduleReference;
if (externalReference.expression) {
// An `import <identifier> = require(<module-specifier>);
if (!externalReference.expression.parent) {
// The `parent` field of a node is set by the TypeScript binder (run as
// part of the type checker). Setting it here allows us to call `getText()`
// even if the `SourceFile` was not type checked (which looks for `SourceFile`
// in the parent chain). This doesn't damage the node as the binder unconditionally
// sets the parent.
externalReference.expression.parent = externalReference;
externalReference.parent = this.sourceFile as any;
}
const from = stripQuotes(externalReference.expression.getText());
symbols.set(
importEqualsDeclaration.name.text, {__symbolic: 'reference', module: from});
break;
}
}
symbols.set(
importEqualsDeclaration.name.text,
{__symbolic: 'error', message: `Unsupported import syntax`});
break;
case ts.SyntaxKind.ImportDeclaration:
const importDecl = <ts.ImportDeclaration>node;
if (!importDecl.importClause) {
// An `import <module-specifier>` clause which does not bring symbols into scope.
break;
}
if (!importDecl.moduleSpecifier.parent) {
// See note above in the `ImportEqualDeclaration` case.
importDecl.moduleSpecifier.parent = importDecl;
importDecl.parent = this.sourceFile;
}
const from = stripQuotes(importDecl.moduleSpecifier.getText());
if (importDecl.importClause.name) {
// An `import <identifier> form <module-specifier>` clause. Record the defualt symbol.
symbols.set(
importDecl.importClause.name.text,
{__symbolic: 'reference', module: from, default: true});
}
const bindings = importDecl.importClause.namedBindings;
if (bindings) {
switch (bindings.kind) {
case ts.SyntaxKind.NamedImports:
// An `import { [<identifier> [, <identifier>] } from <module-specifier>` clause
for (const binding of (<ts.NamedImports>bindings).elements) {
symbols.set(binding.name.text, {
__symbolic: 'reference',
module: from,
name: binding.propertyName ? binding.propertyName.text : binding.name.text
});
}
break;
case ts.SyntaxKind.NamespaceImport:
// An `input * as <identifier> from <module-specifier>` clause.
symbols.set(
(<ts.NamespaceImport>bindings).name.text,
{__symbolic: 'reference', module: from});
break;
}
}
break;
}
ts.forEachChild(node, visit);
};
if (this.sourceFile) {
ts.forEachChild(this.sourceFile, visit);
}
}
}
function populateBuiltins(symbols: Map<string, MetadataValue>) {
// From lib.core.d.ts (all "define const")
['Object', 'Function', 'String', 'Number', 'Array', 'Boolean', 'Map', 'NaN', 'Infinity', 'Math',
'Date', 'RegExp', 'Error', 'Error', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError',
'TypeError', 'URIError', 'JSON', 'ArrayBuffer', 'DataView', 'Int8Array', 'Uint8Array',
'Uint8ClampedArray', 'Uint16Array', 'Int16Array', 'Int32Array', 'Uint32Array', 'Float32Array',
'Float64Array']
.forEach(name => symbols.set(name, {__symbolic: 'reference', name}));
}

View File

@ -1,182 +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 {existsSync} from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import AngularCompilerOptions from './options';
import {VinylFile, isVinylFile} from './vinyl_file';
/**
* Our interface to the TypeScript standard compiler.
* If you write an Angular compiler plugin for another build tool,
* you should implement a similar interface.
*/
export interface CompilerInterface {
readConfiguration(
project: string|VinylFile, basePath: string, existingOptions?: ts.CompilerOptions):
{parsed: ts.ParsedCommandLine, ngOptions: AngularCompilerOptions};
typeCheck(compilerHost: ts.CompilerHost, program: ts.Program): void;
emit(program: ts.Program): number;
}
export class UserError extends Error {
private _nativeError: Error;
constructor(message: string) {
super(message);
// Required for TS 2.1, see
// https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
(Object as any).setPrototypeOf(this, UserError.prototype);
const nativeError = new Error(message) as any as Error;
this._nativeError = nativeError;
}
get message() { return this._nativeError.message; }
set message(message) {
if (this._nativeError) this._nativeError.message = message;
}
get name() { return this._nativeError.name; }
set name(name) {
if (this._nativeError) this._nativeError.name = name;
}
get stack() { return (this._nativeError as any).stack; }
set stack(value) {
if (this._nativeError) (this._nativeError as any).stack = value;
}
toString() { return this._nativeError.toString(); }
}
const DEBUG = false;
function debug(msg: string, ...o: any[]) {
// tslint:disable-next-line:no-console
if (DEBUG) console.log(msg, ...o);
}
export function formatDiagnostics(diags: ts.Diagnostic[]): string {
return diags
.map((d) => {
let res = ts.DiagnosticCategory[d.category];
if (d.file) {
res += ' at ' + d.file.fileName + ':';
if (d.start !== undefined) {
const {line, character} = d.file.getLineAndCharacterOfPosition(d.start);
res += (line + 1) + ':' + (character + 1) + ':';
}
}
res += ' ' + ts.flattenDiagnosticMessageText(d.messageText, '\n');
return res;
})
.join('\n');
}
export function check(diags: ts.Diagnostic[] | undefined) {
if (diags && diags.length && diags[0]) {
throw new UserError(formatDiagnostics(diags));
}
}
export function validateAngularCompilerOptions(options: AngularCompilerOptions): ts.Diagnostic[]|
undefined {
if (options.annotationsAs) {
switch (options.annotationsAs) {
case 'decorators':
case 'static fields':
break;
default:
return [{
file: null as any as ts.SourceFile,
start: null as any as number,
length: null as any as number,
messageText:
'Angular compiler options "annotationsAs" only supports "static fields" and "decorators"',
category: ts.DiagnosticCategory.Error,
code: 0
}];
}
}
}
export class Tsc implements CompilerInterface {
private parseConfigHost: ts.ParseConfigHost;
constructor(private readFile = ts.sys.readFile, private readDirectory = ts.sys.readDirectory) {
this.parseConfigHost = {
useCaseSensitiveFileNames: true,
fileExists: existsSync,
readDirectory: this.readDirectory,
readFile: ts.sys.readFile
};
}
readConfiguration(
project: string|VinylFile, basePath: string, existingOptions?: ts.CompilerOptions) {
// Allow a directory containing tsconfig.json as the project value
// Note, TS@next returns an empty array, while earlier versions throw
try {
if (!isVinylFile(project) && this.readDirectory(project).length > 0) {
project = path.join(project, 'tsconfig.json');
}
} catch (e) {
// Was not a directory, continue on assuming it's a file
}
let {config, error} = (() => {
// project is vinyl like file object
if (isVinylFile(project)) {
return {config: JSON.parse(project.contents.toString()), error: null};
}
// project is path to project file
else {
return ts.readConfigFile(project, this.readFile);
}
})();
check([error !]);
const parsed =
ts.parseJsonConfigFileContent(config, this.parseConfigHost, basePath, existingOptions);
check(parsed.errors);
// Default codegen goes to the current directory
// Parsed options are already converted to absolute paths
const ngOptions = config.angularCompilerOptions || {};
ngOptions.genDir = path.join(basePath, ngOptions.genDir || '.');
for (const key of Object.keys(parsed.options)) {
ngOptions[key] = parsed.options[key];
}
check(validateAngularCompilerOptions(ngOptions));
return {parsed, ngOptions};
}
typeCheck(compilerHost: ts.CompilerHost, program: ts.Program): void {
debug('Checking global diagnostics...');
check(program.getGlobalDiagnostics());
const diagnostics: ts.Diagnostic[] = [];
debug('Type checking...');
for (const sf of program.getSourceFiles()) {
diagnostics.push(...ts.getPreEmitDiagnostics(program, sf));
}
check(diagnostics);
}
emit(program: ts.Program): number {
debug('Emitting outputs...');
const emitResult = program.emit();
const diagnostics: ts.Diagnostic[] = [];
diagnostics.push(...emitResult.diagnostics);
return emitResult.emitSkipped ? 1 : 0;
}
}
export const tsc: CompilerInterface = new Tsc();

View File

@ -1,18 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export interface VinylFile extends Object {
// Absolute path to the virtual file
path: string;
// Content of the virtual file
contents: Buffer;
}
export function isVinylFile(obj: any): obj is VinylFile {
return (typeof obj === 'object') && ('path' in obj) && ('contents' in obj);
}

View File

@ -1,280 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as path from 'path';
import * as ts from 'typescript';
import {MetadataBundler, MetadataBundlerHost} from '../src/bundler';
import {MetadataCollector} from '../src/collector';
import {ClassMetadata, MetadataGlobalReferenceExpression, ModuleMetadata} from '../src/schema';
import {Directory, open} from './typescript.mocks';
describe('metadata bundler', () => {
it('should be able to bundle a simple library', () => {
const host = new MockStringBundlerHost('/', SIMPLE_LIBRARY);
const bundler = new MetadataBundler('/lib/index', undefined, host);
const result = bundler.getMetadataBundle();
expect(Object.keys(result.metadata.metadata).sort()).toEqual([
'ONE_CLASSES', 'One', 'OneMore', 'TWO_CLASSES', 'Two', 'TwoMore', 'ɵa', 'ɵb'
]);
const originalOne = './src/one';
const originalTwo = './src/two/index';
expect(Object.keys(result.metadata.origins)
.sort()
.map(name => ({name, value: result.metadata.origins ![name]})))
.toEqual([
{name: 'ONE_CLASSES', value: originalOne}, {name: 'One', value: originalOne},
{name: 'OneMore', value: originalOne}, {name: 'TWO_CLASSES', value: originalTwo},
{name: 'Two', value: originalTwo}, {name: 'TwoMore', value: originalTwo},
{name: 'ɵa', value: originalOne}, {name: 'ɵb', value: originalTwo}
]);
expect(result.privates).toEqual([
{privateName: 'ɵa', name: 'PrivateOne', module: originalOne},
{privateName: 'ɵb', name: 'PrivateTwo', module: originalTwo}
]);
});
it('should be able to bundle an oddly constructed library', () => {
const host = new MockStringBundlerHost('/', {
'lib': {
'index.ts': `
export * from './src/index';
`,
'src': {
'index.ts': `
export {One, OneMore, ONE_CLASSES} from './one';
export {Two, TwoMore, TWO_CLASSES} from './two/index';
`,
'one.ts': `
class One {}
class OneMore extends One {}
class PrivateOne {}
const ONE_CLASSES = [One, OneMore, PrivateOne];
export {One, OneMore, PrivateOne, ONE_CLASSES};
`,
'two': {
'index.ts': `
class Two {}
class TwoMore extends Two {}
class PrivateTwo {}
const TWO_CLASSES = [Two, TwoMore, PrivateTwo];
export {Two, TwoMore, PrivateTwo, TWO_CLASSES};
`
}
}
}
});
const bundler = new MetadataBundler('/lib/index', undefined, host);
const result = bundler.getMetadataBundle();
expect(Object.keys(result.metadata.metadata).sort()).toEqual([
'ONE_CLASSES', 'One', 'OneMore', 'TWO_CLASSES', 'Two', 'TwoMore', 'ɵa', 'ɵb'
]);
expect(result.privates).toEqual([
{privateName: 'ɵa', name: 'PrivateOne', module: './src/one'},
{privateName: 'ɵb', name: 'PrivateTwo', module: './src/two/index'}
]);
});
it('should not output windows paths in metadata', () => {
const host = new MockStringBundlerHost('/', {
'index.ts': `
export * from './exports/test';
`,
'exports': {'test.ts': `export class TestExport {}`}
});
const bundler = new MetadataBundler('/index', undefined, host);
const result = bundler.getMetadataBundle();
expect(result.metadata.origins).toEqual({'TestExport': './exports/test'});
});
it('should convert re-exported to the export', () => {
const host = new MockStringBundlerHost('/', {
'index.ts': `
export * from './bar';
export * from './foo';
`,
'bar.ts': `
import {Foo} from './foo';
export class Bar extends Foo {
}
`,
'foo.ts': `
export {Foo} from 'foo';
`
});
const bundler = new MetadataBundler('/index', undefined, host);
const result = bundler.getMetadataBundle();
// Expect the extends reference to refer to the imported module
expect((result.metadata.metadata as any).Bar.extends.module).toEqual('foo');
expect(result.privates).toEqual([]);
});
it('should treat import then export as a simple export', () => {
const host = new MockStringBundlerHost('/', {
'index.ts': `
export * from './a';
export * from './c';
`,
'a.ts': `
import { B } from './b';
export { B };
`,
'b.ts': `
export class B { }
`,
'c.ts': `
import { B } from './b';
export class C extends B { }
`
});
const bundler = new MetadataBundler('/index', undefined, host);
const result = bundler.getMetadataBundle();
expect(Object.keys(result.metadata.metadata).sort()).toEqual(['B', 'C']);
expect(result.privates).toEqual([]);
});
it('should be able to bundle a private from a un-exported module', () => {
const host = new MockStringBundlerHost('/', {
'index.ts': `
export * from './foo';
`,
'foo.ts': `
import {Bar} from './bar';
export class Foo extends Bar {
}
`,
'bar.ts': `
export class Bar {}
`
});
const bundler = new MetadataBundler('/index', undefined, host);
const result = bundler.getMetadataBundle();
expect(Object.keys(result.metadata.metadata).sort()).toEqual(['Foo', 'ɵa']);
expect(result.privates).toEqual([{privateName: 'ɵa', name: 'Bar', module: './bar'}]);
});
it('should be able to bundle a library with re-exported symbols', () => {
const host = new MockStringBundlerHost('/', {
'public-api.ts': `
export * from './src/core';
export * from './src/externals';
`,
'src': {
'core.ts': `
export class A {}
export class B extends A {}
`,
'externals.ts': `
export {E, F, G} from 'external_one';
export * from 'external_two';
`
}
});
const bundler = new MetadataBundler('/public-api', undefined, host);
const result = bundler.getMetadataBundle();
expect(result.metadata.exports).toEqual([
{from: 'external_two'}, {
export: [{name: 'E', as: 'E'}, {name: 'F', as: 'F'}, {name: 'G', as: 'G'}],
from: 'external_one'
}
]);
expect(result.metadata.origins !['E']).toBeUndefined();
});
it('should be able to de-duplicate symbols of re-exported modules', () => {
const host = new MockStringBundlerHost('/', {
'public-api.ts': `
export {A as A2, A, B as B1, B as B2} from './src/core';
export {A as A3} from './src/alternate';
`,
'src': {
'core.ts': `
export class A {}
export class B {}
`,
'alternate.ts': `
export class A {}
`,
}
});
const bundler = new MetadataBundler('/public-api', undefined, host);
const result = bundler.getMetadataBundle();
const {A, A2, A3, B1, B2} = result.metadata.metadata as{
A: ClassMetadata,
A2: MetadataGlobalReferenceExpression,
A3: ClassMetadata,
B1: ClassMetadata,
B2: MetadataGlobalReferenceExpression
};
expect(A.__symbolic).toEqual('class');
expect(A2.__symbolic).toEqual('reference');
expect(A2.name).toEqual('A');
expect(A3.__symbolic).toEqual('class');
expect(B1.__symbolic).toEqual('class');
expect(B2.__symbolic).toEqual('reference');
expect(B2.name).toEqual('B1');
});
});
export class MockStringBundlerHost implements MetadataBundlerHost {
collector = new MetadataCollector();
constructor(private dirName: string, private directory: Directory) {}
getMetadataFor(moduleName: string): ModuleMetadata|undefined {
const fileName = path.join(this.dirName, moduleName) + '.ts';
const text = open(this.directory, fileName);
if (typeof text == 'string') {
const sourceFile = ts.createSourceFile(
fileName, text, ts.ScriptTarget.Latest, /* setParent */ true, ts.ScriptKind.TS);
const diagnostics: ts.Diagnostic[] = (sourceFile as any).parseDiagnostics;
if (diagnostics && diagnostics.length) {
throw Error('Unexpected syntax error in test');
}
const result = this.collector.getMetadata(sourceFile);
return result;
}
}
}
export const SIMPLE_LIBRARY = {
'lib': {
'index.ts': `
export * from './src/index';
`,
'src': {
'index.ts': `
export {One, OneMore, ONE_CLASSES} from './one';
export {Two, TwoMore, TWO_CLASSES} from './two/index';
`,
'one.ts': `
export class One {}
export class OneMore extends One {}
export class PrivateOne {}
export const ONE_CLASSES = [One, OneMore, PrivateOne];
`,
'two': {
'index.ts': `
export class Two {}
export class TwoMore extends Two {}
export class PrivateTwo {}
export const TWO_CLASSES = [Two, TwoMore, PrivateTwo];
`
}
}
}
};

File diff suppressed because it is too large Load Diff

View File

@ -1,373 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as fs from 'fs';
import * as ts from 'typescript';
import {Evaluator} from '../src/evaluator';
import {Symbols} from '../src/symbols';
import {Directory, Host, expectNoDiagnostics, findVar, findVarInitializer} from './typescript.mocks';
describe('Evaluator', () => {
const documentRegistry = ts.createDocumentRegistry();
let host: ts.LanguageServiceHost;
let service: ts.LanguageService;
let program: ts.Program;
let typeChecker: ts.TypeChecker;
let symbols: Symbols;
let evaluator: Evaluator;
beforeEach(() => {
host = new Host(FILES, [
'expressions.ts', 'consts.ts', 'const_expr.ts', 'forwardRef.ts', 'classes.ts',
'newExpression.ts', 'errors.ts', 'declared.ts'
]);
service = ts.createLanguageService(host, documentRegistry);
program = service.getProgram();
typeChecker = program.getTypeChecker();
symbols = new Symbols(null as any as ts.SourceFile);
evaluator = new Evaluator(symbols, new Map());
});
it('should not have typescript errors in test data', () => {
expectNoDiagnostics(service.getCompilerOptionsDiagnostics());
for (const sourceFile of program.getSourceFiles()) {
expectNoDiagnostics(service.getSyntacticDiagnostics(sourceFile.fileName));
if (sourceFile.fileName != 'errors.ts') {
// Skip errors.ts because we it has intentional semantic errors that we are testing for.
expectNoDiagnostics(service.getSemanticDiagnostics(sourceFile.fileName));
}
}
});
it('should be able to fold literal expressions', () => {
const consts = program.getSourceFile('consts.ts');
expect(evaluator.isFoldable(findVarInitializer(consts, 'someName'))).toBeTruthy();
expect(evaluator.isFoldable(findVarInitializer(consts, 'someBool'))).toBeTruthy();
expect(evaluator.isFoldable(findVarInitializer(consts, 'one'))).toBeTruthy();
expect(evaluator.isFoldable(findVarInitializer(consts, 'two'))).toBeTruthy();
});
it('should be able to fold expressions with foldable references', () => {
const expressions = program.getSourceFile('expressions.ts');
symbols.define('someName', 'some-name');
symbols.define('someBool', true);
symbols.define('one', 1);
symbols.define('two', 2);
expect(evaluator.isFoldable(findVarInitializer(expressions, 'three'))).toBeTruthy();
expect(evaluator.isFoldable(findVarInitializer(expressions, 'four'))).toBeTruthy();
symbols.define('three', 3);
symbols.define('four', 4);
expect(evaluator.isFoldable(findVarInitializer(expressions, 'obj'))).toBeTruthy();
expect(evaluator.isFoldable(findVarInitializer(expressions, 'arr'))).toBeTruthy();
});
it('should be able to evaluate literal expressions', () => {
const consts = program.getSourceFile('consts.ts');
expect(evaluator.evaluateNode(findVarInitializer(consts, 'someName'))).toBe('some-name');
expect(evaluator.evaluateNode(findVarInitializer(consts, 'someBool'))).toBe(true);
expect(evaluator.evaluateNode(findVarInitializer(consts, 'one'))).toBe(1);
expect(evaluator.evaluateNode(findVarInitializer(consts, 'two'))).toBe(2);
});
it('should be able to evaluate expressions', () => {
const expressions = program.getSourceFile('expressions.ts');
symbols.define('someName', 'some-name');
symbols.define('someBool', true);
symbols.define('one', 1);
symbols.define('two', 2);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'three'))).toBe(3);
symbols.define('three', 3);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'four'))).toBe(4);
symbols.define('four', 4);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'obj')))
.toEqual({one: 1, two: 2, three: 3, four: 4});
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'arr'))).toEqual([1, 2, 3, 4]);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bTrue'))).toEqual(true);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bFalse'))).toEqual(false);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bAnd'))).toEqual(true);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bOr'))).toEqual(true);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'nDiv'))).toEqual(2);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'nMod'))).toEqual(1);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bLOr'))).toEqual(false || true);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bLAnd'))).toEqual(true && true);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bBOr'))).toEqual(0x11 | 0x22);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bBAnd'))).toEqual(0x11 & 0x03);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bXor'))).toEqual(0x11 ^ 0x21);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bEqual')))
.toEqual(1 == <any>'1');
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bNotEqual')))
.toEqual(1 != <any>'1');
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bIdentical')))
.toEqual(1 === <any>'1');
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bNotIdentical')))
.toEqual(1 !== <any>'1');
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bLessThan'))).toEqual(1 < 2);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bGreaterThan'))).toEqual(1 > 2);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bLessThanEqual')))
.toEqual(1 <= 2);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bGreaterThanEqual')))
.toEqual(1 >= 2);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bShiftLeft'))).toEqual(1 << 2);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bShiftRight'))).toEqual(-1 >> 2);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bShiftRightU')))
.toEqual(-1 >>> 2);
});
it('should report recursive references as symbolic', () => {
const expressions = program.getSourceFile('expressions.ts');
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'recursiveA')))
.toEqual({__symbolic: 'reference', name: 'recursiveB'});
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'recursiveB')))
.toEqual({__symbolic: 'reference', name: 'recursiveA'});
});
it('should correctly handle special cases for CONST_EXPR', () => {
const const_expr = program.getSourceFile('const_expr.ts');
expect(evaluator.evaluateNode(findVarInitializer(const_expr, 'bTrue'))).toEqual(true);
expect(evaluator.evaluateNode(findVarInitializer(const_expr, 'bFalse'))).toEqual(false);
});
it('should resolve a forwardRef', () => {
const forwardRef = program.getSourceFile('forwardRef.ts');
expect(evaluator.evaluateNode(findVarInitializer(forwardRef, 'bTrue'))).toEqual(true);
expect(evaluator.evaluateNode(findVarInitializer(forwardRef, 'bFalse'))).toEqual(false);
});
it('should return new expressions', () => {
symbols.define('Value', {__symbolic: 'reference', module: './classes', name: 'Value'});
evaluator = new Evaluator(symbols, new Map());
const newExpression = program.getSourceFile('newExpression.ts');
expect(evaluator.evaluateNode(findVarInitializer(newExpression, 'someValue'))).toEqual({
__symbolic: 'new',
expression: {__symbolic: 'reference', name: 'Value', module: './classes'},
arguments: ['name', 12]
});
expect(evaluator.evaluateNode(findVarInitializer(newExpression, 'complex'))).toEqual({
__symbolic: 'new',
expression: {__symbolic: 'reference', name: 'Value', module: './classes'},
arguments: ['name', 12]
});
});
it('should support referene to a declared module type', () => {
const declared = program.getSourceFile('declared.ts');
const aDecl = findVar(declared, 'a') !;
expect(evaluator.evaluateNode(aDecl.type !)).toEqual({
__symbolic: 'select',
expression: {__symbolic: 'reference', name: 'Foo'},
member: 'A'
});
});
it('should return errors for unsupported expressions', () => {
const errors = program.getSourceFile('errors.ts');
const fDecl = findVar(errors, 'f') !;
expect(evaluator.evaluateNode(fDecl.initializer !))
.toEqual(
{__symbolic: 'error', message: 'Function call not supported', line: 1, character: 12});
const eDecl = findVar(errors, 'e') !;
expect(evaluator.evaluateNode(eDecl.type !)).toEqual({
__symbolic: 'error',
message: 'Could not resolve type',
line: 2,
character: 11,
context: {typeName: 'NotFound'}
});
const sDecl = findVar(errors, 's') !;
expect(evaluator.evaluateNode(sDecl.initializer !)).toEqual({
__symbolic: 'error',
message: 'Name expected',
line: 3,
character: 14,
context: {received: '1'}
});
const tDecl = findVar(errors, 't') !;
expect(evaluator.evaluateNode(tDecl.initializer !)).toEqual({
__symbolic: 'error',
message: 'Expression form not supported',
line: 4,
character: 12
});
});
it('should be able to fold an array spread', () => {
const expressions = program.getSourceFile('expressions.ts');
symbols.define('arr', [1, 2, 3, 4]);
const arrSpread = findVar(expressions, 'arrSpread') !;
expect(evaluator.evaluateNode(arrSpread.initializer !)).toEqual([0, 1, 2, 3, 4, 5]);
});
it('should be able to produce a spread expression', () => {
const expressions = program.getSourceFile('expressions.ts');
const arrSpreadRef = findVar(expressions, 'arrSpreadRef') !;
expect(evaluator.evaluateNode(arrSpreadRef.initializer !)).toEqual([
0, {__symbolic: 'spread', expression: {__symbolic: 'reference', name: 'arrImport'}}, 5
]);
});
it('should be able to handle a new expression with no arguments', () => {
const source = sourceFileOf(`
export var a = new f;
`);
const expr = findVar(source, 'a') !;
expect(evaluator.evaluateNode(expr.initializer !))
.toEqual({__symbolic: 'new', expression: {__symbolic: 'reference', name: 'f'}});
});
describe('with substitution', () => {
let evaluator: Evaluator;
const lambdaTemp = 'lambdaTemp';
beforeEach(() => {
evaluator = new Evaluator(symbols, new Map(), {
substituteExpression: (value, node) => {
if (node.kind == ts.SyntaxKind.ArrowFunction) {
return {__symbolic: 'reference', name: lambdaTemp};
}
return value;
}
});
});
it('should be able to substitute a lambda with a reference', () => {
const source = sourceFileOf(`
var b = 1;
export var a = () => b;
`);
const expr = findVar(source, 'a');
expect(evaluator.evaluateNode(expr !.initializer !))
.toEqual({__symbolic: 'reference', name: lambdaTemp});
});
it('should be able to substitute a lambda in an expression', () => {
const source = sourceFileOf(`
var b = 1;
export var a = [
{ provide: 'someValue': useFactory: () => b }
];
`);
const expr = findVar(source, 'a');
expect(evaluator.evaluateNode(expr !.initializer !)).toEqual([
{provide: 'someValue', useFactory: {__symbolic: 'reference', name: lambdaTemp}}
]);
});
});
});
function sourceFileOf(text: string): ts.SourceFile {
return ts.createSourceFile('test.ts', text, ts.ScriptTarget.Latest, true);
}
const FILES: Directory = {
'directives.ts': `
export function Pipe(options: { name?: string, pure?: boolean}) {
return function(fn: Function) { }
}
`,
'classes.ts': `
export class Value {
constructor(public name: string, public value: any) {}
}
`,
'consts.ts': `
export var someName = 'some-name';
export var someBool = true;
export var one = 1;
export var two = 2;
export var arrImport = [1, 2, 3, 4];
`,
'expressions.ts': `
import {arrImport} from './consts';
export var someName = 'some-name';
export var someBool = true;
export var one = 1;
export var two = 2;
export var three = one + two;
export var four = two * two;
export var obj = { one: one, two: two, three: three, four: four };
export var arr = [one, two, three, four];
export var bTrue = someBool;
export var bFalse = !someBool;
export var bAnd = someBool && someBool;
export var bOr = someBool || someBool;
export var nDiv = four / two;
export var nMod = (four + one) % two;
export var bLOr = false || true; // true
export var bLAnd = true && true; // true
export var bBOr = 0x11 | 0x22; // 0x33
export var bBAnd = 0x11 & 0x03; // 0x01
export var bXor = 0x11 ^ 0x21; // 0x20
export var bEqual = 1 == <any>"1"; // true
export var bNotEqual = 1 != <any>"1"; // false
export var bIdentical = 1 === <any>"1"; // false
export var bNotIdentical = 1 !== <any>"1"; // true
export var bLessThan = 1 < 2; // true
export var bGreaterThan = 1 > 2; // false
export var bLessThanEqual = 1 <= 2; // true
export var bGreaterThanEqual = 1 >= 2; // false
export var bShiftLeft = 1 << 2; // 0x04
export var bShiftRight = -1 >> 2; // -1
export var bShiftRightU = -1 >>> 2; // 0x3fffffff
export var arrSpread = [0, ...arr, 5];
export var arrSpreadRef = [0, ...arrImport, 5];
export var recursiveA = recursiveB;
export var recursiveB = recursiveA;
`,
'A.ts': `
import {Pipe} from './directives';
@Pipe({name: 'A', pure: false})
export class A {}`,
'B.ts': `
import {Pipe} from './directives';
import {someName, someBool} from './consts';
@Pipe({name: someName, pure: someBool})
export class B {}`,
'const_expr.ts': `
function CONST_EXPR(value: any) { return value; }
export var bTrue = CONST_EXPR(true);
export var bFalse = CONST_EXPR(false);
`,
'forwardRef.ts': `
function forwardRef(value: any) { return value; }
export var bTrue = forwardRef(() => true);
export var bFalse = forwardRef(() => false);
`,
'newExpression.ts': `
import {Value} from './classes';
function CONST_EXPR(value: any) { return value; }
function forwardRef(value: any) { return value; }
export const someValue = new Value("name", 12);
export const complex = CONST_EXPR(new Value("name", forwardRef(() => 12)));
`,
'errors.ts': `
let f = () => 1;
let e: NotFound;
let s = { 1: 1, 2: 2 };
let t = typeof 12;
`,
'declared.ts': `
declare namespace Foo {
type A = string;
}
let a: Foo.A = 'some value';
`
};

View File

@ -1,25 +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 {MetadataBundler} from '../src/bundler';
import {MetadataCollector} from '../src/collector';
import {privateEntriesToIndex} from '../src/index_writer';
import {MockStringBundlerHost, SIMPLE_LIBRARY} from './bundler_spec';
describe('index_writer', () => {
it('should be able to write the index of a simple library', () => {
const host = new MockStringBundlerHost('/', SIMPLE_LIBRARY);
const bundler = new MetadataBundler('/lib/index', undefined, host);
const bundle = bundler.getMetadataBundle();
const result = privateEntriesToIndex('./index', bundle.privates);
expect(result).toContain(`export * from './index';`);
expect(result).toContain(`export {PrivateOne as ɵa} from './src/one';`);
expect(result).toContain(`export {PrivateTwo as ɵb} from './src/two/index';`);
});
});

View File

@ -1,422 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as fs from 'fs';
import * as path from 'path';
import {main} from '../src/main';
import {makeTempDir} from './test_support';
describe('tsc-wrapped', () => {
let basePath: string;
let write: (fileName: string, content: string) => void;
beforeEach(() => {
basePath = makeTempDir();
write = (fileName: string, content: string) => {
fs.writeFileSync(path.join(basePath, fileName), content, {encoding: 'utf-8'});
};
write('decorators.ts', '/** @Annotation */ export var Component: Function;');
fs.mkdirSync(path.join(basePath, 'dep'));
write('dep/index.ts', `
export const A = 1;
export const B = 2;
`);
write('test.ts', `
import {Component} from './decorators';
export * from './dep';
@Component({})
export class Comp {
/**
* Comment that is
* multiple lines
*/
method(x: string): void {}
}
`);
});
function readOut(ext: string) {
return fs.readFileSync(path.join(basePath, 'built', `test.${ext}`), {encoding: 'utf-8'});
}
it('should report error if project not found', () => {
main('not-exist', null as any)
.then(() => fail('should report error'))
.catch(e => expect(e.message).toContain('ENOENT'));
});
it('should pre-process sources', (done) => {
write('tsconfig.json', `{
"compilerOptions": {
"experimentalDecorators": true,
"types": [],
"outDir": "built",
"declaration": true,
"moduleResolution": "node",
"target": "es2015"
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true
},
"files": ["test.ts"]
}`);
main(basePath, {basePath})
.then(() => {
const out = readOut('js');
// No helpers since decorators were lowered
expect(out).not.toContain('__decorate');
// Expand `export *` and fix index import
expect(out).toContain(`export { A, B } from "./dep"`);
// Annotated for Closure compiler
expect(out).toContain('* @param {?} x');
// Comments should stay multi-line
expect(out).not.toContain('Comment that is multiple lines');
// Decorator is now an annotation
expect(out).toMatch(/Comp.decorators = \[\s+\{ type: Component/);
const decl = readOut('d.ts');
expect(decl).toContain('declare class Comp');
const metadata = readOut('metadata.json');
expect(metadata).toContain('"Comp":{"__symbolic":"class"');
done();
})
.catch(e => done.fail(e));
});
it('should pre-process sources using config from vinyl like object', (done) => {
const config = {
path: basePath + '/tsconfig.json',
contents: new Buffer(JSON.stringify({
compilerOptions: {
experimentalDecorators: true,
types: [],
outDir: 'built',
declaration: true,
moduleResolution: 'node',
target: 'es2015'
},
angularCompilerOptions: {annotateForClosureCompiler: true},
files: ['test.ts']
}))
};
main(config, {basePath})
.then(() => {
const out = readOut('js');
// Expand `export *` and fix index import
expect(out).toContain(`export { A, B } from "./dep"`);
// Annotated for Closure compiler
expect(out).toContain('* @param {?} x');
done();
})
.catch(e => done.fail(e));
});
it('should allow all options disabled', (done) => {
write('tsconfig.json', `{
"compilerOptions": {
"experimentalDecorators": true,
"types": [],
"outDir": "built",
"declaration": false,
"module": "es2015",
"moduleResolution": "node"
},
"angularCompilerOptions": {
"annotateForClosureCompiler": false,
"annotationsAs": "decorators",
"skipMetadataEmit": true,
"skipTemplateCodegen": true
},
"files": ["test.ts"]
}`);
main(basePath, {basePath})
.then(() => {
const out = readOut('js');
// TypeScript's decorator emit
expect(out).toContain('__decorate');
// Not annotated for Closure compiler
expect(out).not.toContain('* @param {?} x');
expect(() => fs.accessSync(path.join(basePath, 'built', 'test.metadata.json'))).toThrow();
expect(() => fs.accessSync(path.join(basePath, 'built', 'test.d.ts'))).toThrow();
done();
})
.catch(e => done.fail(e));
});
it('should allow all options disabled with metadata emit', (done) => {
write('tsconfig.json', `{
"compilerOptions": {
"experimentalDecorators": true,
"types": [],
"outDir": "built",
"declaration": false,
"module": "es2015",
"moduleResolution": "node"
},
"angularCompilerOptions": {
"annotateForClosureCompiler": false,
"annotationsAs": "decorators",
"skipMetadataEmit": false,
"skipTemplateCodegen": true
},
"files": ["test.ts"]
}`);
main(basePath, {basePath})
.then(() => {
const out = readOut('js');
// TypeScript's decorator emit
expect(out).toContain('__decorate');
// Not annotated for Closure compiler
expect(out).not.toContain('* @param {?} x');
expect(() => fs.accessSync(path.join(basePath, 'built', 'test.d.ts'))).toThrow();
const metadata = readOut('metadata.json');
expect(metadata).toContain('"Comp":{"__symbolic":"class"');
done();
})
.catch(e => done.fail(e));
});
it('should allow JSDoc annotations without decorator downleveling', (done) => {
write('tsconfig.json', `{
"compilerOptions": {
"experimentalDecorators": true,
"types": [],
"outDir": "built",
"declaration": true
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"annotationsAs": "decorators"
},
"files": ["test.ts"]
}`);
main(basePath, {basePath}).then(() => done()).catch(e => done.fail(e));
});
xit('should run quickly (performance baseline)', (done) => {
for (let i = 0; i < 1000; i++) {
write(`input${i}.ts`, `
import {Component} from './decorators';
@Component({})
export class Input${i} {
private __brand: string;
}
`);
}
write('tsconfig.json', `{
"compilerOptions": {
"experimentalDecorators": true,
"types": [],
"outDir": "built",
"declaration": true,
"diagnostics": true
},
"angularCompilerOptions": {
"annotateForClosureCompiler": false,
"annotationsAs": "decorators",
"skipMetadataEmit": true
},
"include": ["input*.ts"]
}`);
console.time('BASELINE');
main(basePath, {basePath})
.then(() => {
console.timeEnd('BASELINE');
done();
})
.catch(e => done.fail(e));
});
xit('should run quickly (performance test)', (done) => {
for (let i = 0; i < 1000; i++) {
write(`input${i}.ts`, `
import {Component} from './decorators';
@Component({})
export class Input${i} {
private __brand: string;
}
`);
}
write('tsconfig.json', `{
"compilerOptions": {
"experimentalDecorators": true,
"types": [],
"outDir": "built",
"declaration": true,
"diagnostics": true,
"skipLibCheck": true
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true
},
"include": ["input*.ts"]
}`);
console.time('TSICKLE');
main(basePath, {basePath})
.then(() => {
console.timeEnd('TSICKLE');
done();
})
.catch(e => done.fail(e));
});
it('should produce valid source maps', (done) => {
write('tsconfig.json', `{
"compilerOptions": {
"experimentalDecorators": true,
"types": [],
"outDir": "built",
"declaration": true,
"moduleResolution": "node",
"target": "es2015",
"sourceMap": true
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true
},
"files": ["test.ts"]
}`);
main(basePath, {basePath})
.then(() => {
const out = readOut('js.map');
expect(out).toContain('"sources":["../test.ts"]');
done();
})
.catch(e => done.fail(e));
});
it('should accept input source maps', (done) => {
write('tsconfig.json', `{
"compilerOptions": {
"experimentalDecorators": true,
"types": [],
"outDir": "built",
"declaration": true,
"moduleResolution": "node",
"target": "es2015",
"sourceMap": true
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true
},
"files": ["test.ts"]
}`);
// Provide a file called test.ts that has an inline source map
// which says that it was transpiled from a file other_test.ts
// with exactly the same content.
const inputSourceMap =
`{"version":3,"sources":["other_test.ts"],"names":[],"mappings":"AAAA,MAAM,EAAE,EAAE,CAAC","file":"../test.ts","sourceRoot":""}`;
const encodedSourceMap = new Buffer(inputSourceMap, 'utf8').toString('base64');
write('test.ts', `const x = 3;
//# sourceMappingURL=data:application/json;base64,${encodedSourceMap}`);
main(basePath, {basePath})
.then(() => {
const out = readOut('js.map');
expect(out).toContain('"sources":["other_test.ts"]');
done();
})
.catch(e => done.fail(e));
});
it(`should accept input source maps that don't match the file name`, (done) => {
write('tsconfig.json', `{
"compilerOptions": {
"experimentalDecorators": true,
"types": [],
"outDir": "built",
"declaration": true,
"moduleResolution": "node",
"target": "es2015",
"sourceMap": true
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true
},
"files": ["test.ts"]
}`);
// Provide a file called test.ts that has an inline source map
// which says that it was transpiled from a file other_test.ts
// with exactly the same content.
const inputSourceMap =
`{"version":3,"sources":["other_test.ts"],"names":[],"mappings":"AAAA,MAAM,EAAE,EAAE,CAAC","file":"test.ts","sourceRoot":""}`;
const encodedSourceMap = new Buffer(inputSourceMap, 'utf8').toString('base64');
write('test.ts', `const x = 3;
//# sourceMappingURL=data:application/json;base64,${encodedSourceMap}`);
main(basePath, {basePath})
.then(() => {
const out = readOut('js.map');
expect(out).toContain('"sources":["other_test.ts"]');
done();
})
.catch(e => done.fail(e));
});
it('should expand shorthand imports for ES2015 modules', (done) => {
write('tsconfig.json', `{
"compilerOptions": {
"experimentalDecorators": true,
"types": [],
"outDir": "built",
"declaration": true,
"moduleResolution": "node",
"target": "es2015",
"module": "es2015"
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true
},
"files": ["test.ts"]
}`);
main(basePath, {basePath})
.then(() => {
const fileOutput = readOut('js');
expect(fileOutput).toContain(`export { A, B } from "./dep"`);
done();
})
.catch(e => done.fail(e));
});
it('should not expand shorthand imports for ES5 CommonJS modules', (done) => {
write('tsconfig.json', `{
"compilerOptions": {
"experimentalDecorators": true,
"types": [],
"outDir": "built",
"declaration": true,
"moduleResolution": "node",
"target": "es5",
"module": "commonjs"
},
"angularCompilerOptions": {
"annotateForClosureCompiler": true
},
"files": ["test.ts"]
}`);
main(basePath, {basePath})
.then(() => {
const fileOutput = readOut('js');
expect(fileOutput).toContain(`var dep_1 = require("./dep");`);
done();
})
.catch(e => done.fail(e));
});
});

View File

@ -1,133 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {isMetadataGlobalReferenceExpression} from '../src/schema';
import {Symbols} from '../src/symbols';
import {Directory, Host, expectNoDiagnostics} from './typescript.mocks';
describe('Symbols', () => {
let symbols: Symbols;
const someValue = 'some-value';
beforeEach(() => symbols = new Symbols(null as any as ts.SourceFile));
it('should be able to add a symbol', () => symbols.define('someSymbol', someValue));
beforeEach(() => symbols.define('someSymbol', someValue));
it('should be able to `has` a symbol', () => expect(symbols.has('someSymbol')).toBeTruthy());
it('should be able to `get` a symbol value',
() => expect(symbols.resolve('someSymbol')).toBe(someValue));
it('should be able to `get` a symbol value',
() => expect(symbols.resolve('someSymbol')).toBe(someValue));
it('should be able to determine symbol is missing',
() => expect(symbols.has('missingSymbol')).toBeFalsy());
it('should return undefined from `get` for a missing symbol',
() => expect(symbols.resolve('missingSymbol')).toBeUndefined());
let host: ts.LanguageServiceHost;
let service: ts.LanguageService;
let program: ts.Program;
let expressions: ts.SourceFile;
let imports: ts.SourceFile;
beforeEach(() => {
host = new Host(FILES, ['consts.ts', 'expressions.ts', 'imports.ts']);
service = ts.createLanguageService(host);
program = service.getProgram();
expressions = program.getSourceFile('expressions.ts');
imports = program.getSourceFile('imports.ts');
});
it('should not have syntax errors in the test sources', () => {
expectNoDiagnostics(service.getCompilerOptionsDiagnostics());
for (const sourceFile of program.getSourceFiles()) {
expectNoDiagnostics(service.getSyntacticDiagnostics(sourceFile.fileName));
}
});
it('should be able to find the source files', () => {
expect(expressions).toBeDefined();
expect(imports).toBeDefined();
});
it('should be able to create symbols for a source file', () => {
const symbols = new Symbols(expressions);
expect(symbols).toBeDefined();
});
it('should be able to find symbols in expression', () => {
const symbols = new Symbols(expressions);
expect(symbols.has('someName')).toBeTruthy();
expect(symbols.resolve('someName'))
.toEqual({__symbolic: 'reference', module: './consts', name: 'someName'});
expect(symbols.has('someBool')).toBeTruthy();
expect(symbols.resolve('someBool'))
.toEqual({__symbolic: 'reference', module: './consts', name: 'someBool'});
});
it('should be able to detect a * import', () => {
const symbols = new Symbols(imports);
expect(symbols.resolve('b')).toEqual({__symbolic: 'reference', module: 'b'});
});
it('should be able to detect importing a default export', () => {
const symbols = new Symbols(imports);
expect(symbols.resolve('d')).toEqual({__symbolic: 'reference', module: 'd', default: true});
});
it('should be able to import a renamed symbol', () => {
const symbols = new Symbols(imports);
expect(symbols.resolve('g')).toEqual({__symbolic: 'reference', name: 'f', module: 'f'});
});
it('should be able to resolve any symbol in core global scope', () => {
const core = program.getSourceFiles().find(source => source.fileName.endsWith('lib.d.ts'));
expect(core).toBeDefined();
const visit = (node: ts.Node): boolean => {
switch (node.kind) {
case ts.SyntaxKind.VariableStatement:
case ts.SyntaxKind.VariableDeclarationList:
return !!ts.forEachChild(node, visit);
case ts.SyntaxKind.VariableDeclaration:
const variableDeclaration = <ts.VariableDeclaration>node;
const nameNode = <ts.Identifier>variableDeclaration.name;
const name = nameNode.text;
const result = symbols.resolve(name);
expect(isMetadataGlobalReferenceExpression(result) && result.name).toEqual(name);
// Ignore everything after Float64Array as it is IE specific.
return name === 'Float64Array';
}
return false;
};
ts.forEachChild(core !, visit);
});
});
const FILES: Directory = {
'consts.ts': `
export var someName = 'some-name';
export var someBool = true;
export var one = 1;
export var two = 2;
`,
'expressions.ts': `
import {someName, someBool, one, two} from './consts';
`,
'imports.ts': `
import * as b from 'b';
import 'c';
import d from 'd';
import {f as g} from 'f';
`
};

View File

@ -1,28 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
const tmpdir = process.env.TEST_TMPDIR || os.tmpdir();
export function writeTempFile(name: string, contents: string): string {
// TEST_TMPDIR is set by bazel.
const id = (Math.random() * 1000000).toFixed(0);
const fn = path.join(tmpdir, `tmp.${id}.${name}`);
fs.writeFileSync(fn, contents);
return fn;
}
export function makeTempDir(): string {
const id = (Math.random() * 1000000).toFixed(0);
const dir = path.join(tmpdir, `tmp.${id}`);
fs.mkdirSync(dir);
return dir;
}

View File

@ -1,54 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {Tsc} from '../src/tsc';
import {VinylFile} from '../src/vinyl_file';
describe('options parsing', () => {
const configData = `
{
"angularCompilerOptions": {
"googleClosureOutput": true
},
"compilerOptions": {
"module": "commonjs",
"outDir": "built"
}
}`;
const tsc = new Tsc(() => configData, () => ['tsconfig.json']);
const config = {path: 'basePath/tsconfig.json', contents: new Buffer(configData)};
it('should combine all options into ngOptions', () => {
const {parsed, ngOptions} =
tsc.readConfiguration('projectDir', 'basePath', {target: ts.ScriptTarget.ES2015});
expect(ngOptions).toEqual({
genDir: 'basePath',
googleClosureOutput: true,
module: ts.ModuleKind.CommonJS,
outDir: 'basePath/built',
configFilePath: undefined,
target: ts.ScriptTarget.ES2015
});
});
it('should combine all options into ngOptions from vinyl like object', () => {
const {parsed, ngOptions} = tsc.readConfiguration(config as VinylFile, 'basePath');
expect(ngOptions).toEqual({
genDir: 'basePath',
googleClosureOutput: true,
module: ts.ModuleKind.CommonJS,
outDir: 'basePath/built',
configFilePath: undefined
});
});
});

View File

@ -1,204 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as fs from 'fs';
import * as ts from 'typescript';
export interface Directory { [name: string]: (Directory|string); }
export class Host implements ts.LanguageServiceHost {
private overrides = new Map<string, string>();
private version = 1;
constructor(private directory: Directory, private scripts: string[]) {}
getCompilationSettings(): ts.CompilerOptions {
return {
experimentalDecorators: true,
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES5
};
}
getScriptFileNames(): string[] { return this.scripts; }
getScriptVersion(fileName: string): string { return this.version.toString(); }
getScriptSnapshot(fileName: string): ts.IScriptSnapshot|undefined {
const content = this.getFileContent(fileName);
if (content) return ts.ScriptSnapshot.fromString(content);
}
fileExists(fileName: string): boolean { return this.getFileContent(fileName) != null; }
getCurrentDirectory(): string { return '/'; }
getDefaultLibFileName(options: ts.CompilerOptions): string { return 'lib.d.ts'; }
overrideFile(fileName: string, content: string) {
this.overrides.set(fileName, content);
this.version++;
}
addFile(fileName: string) {
this.scripts.push(fileName);
this.version++;
}
private getFileContent(fileName: string): string|undefined {
if (this.overrides.has(fileName)) {
return this.overrides.get(fileName);
}
if (fileName.endsWith('lib.d.ts')) {
return fs.readFileSync(ts.getDefaultLibFilePath(this.getCompilationSettings()), 'utf8');
}
const current = open(this.directory, fileName);
if (typeof current === 'string') return current;
}
}
export function open(directory: Directory, fileName: string): Directory|string|undefined {
// Path might be normalized by the current node environment. But it could also happen that this
// path directly comes from the compiler in POSIX format. Support both separators for development.
const names = fileName.split(/[\\/]/);
let current: Directory|string = directory;
if (names.length && names[0] === '') names.shift();
for (const name of names) {
if (!current || typeof current === 'string') return undefined;
current = current[name];
}
return current;
}
export class MockNode implements ts.Node {
constructor(
public kind: ts.SyntaxKind = ts.SyntaxKind.Identifier, public flags: ts.NodeFlags = 0,
public pos: number = 0, public end: number = 0) {}
getSourceFile(): ts.SourceFile { return null as any as ts.SourceFile; }
getChildCount(sourceFile?: ts.SourceFile): number { return 0; }
getChildAt(index: number, sourceFile?: ts.SourceFile): ts.Node { return null as any as ts.Node; }
getChildren(sourceFile?: ts.SourceFile): ts.Node[] { return []; }
getStart(sourceFile?: ts.SourceFile): number { return 0; }
getFullStart(): number { return 0; }
getEnd(): number { return 0; }
getWidth(sourceFile?: ts.SourceFile): number { return 0; }
getFullWidth(): number { return 0; }
getLeadingTriviaWidth(sourceFile?: ts.SourceFile): number { return 0; }
getFullText(sourceFile?: ts.SourceFile): string { return ''; }
getText(sourceFile?: ts.SourceFile): string { return ''; }
getFirstToken(sourceFile?: ts.SourceFile): ts.Node { return null as any as ts.Node; }
getLastToken(sourceFile?: ts.SourceFile): ts.Node { return null as any as ts.Node; }
forEachChild<T>(
cbNode: (node: ts.Node) => T | undefined,
cbNodeArray?: (nodes: ts.NodeArray<ts.Node>) => T | undefined): T|undefined {
return undefined;
}
}
export class MockIdentifier extends MockNode implements ts.Identifier {
public text: string;
// tslint:disable
public _primaryExpressionBrand: any;
public _memberExpressionBrand: any;
public _leftHandSideExpressionBrand: any;
public _incrementExpressionBrand: any;
public _unaryExpressionBrand: any;
public _expressionBrand: any;
public _updateExpressionBrand: any;
// tslint:enable
constructor(
public name: string, public kind: ts.SyntaxKind.Identifier = ts.SyntaxKind.Identifier,
flags: ts.NodeFlags = 0, pos: number = 0, end: number = 0) {
super(kind, flags, pos, end);
this.text = name;
}
}
export class MockVariableDeclaration extends MockNode implements ts.VariableDeclaration {
// tslint:disable-next-line
public _declarationBrand: any;
constructor(
public name: ts.Identifier,
public kind: ts.SyntaxKind.VariableDeclaration = ts.SyntaxKind.VariableDeclaration,
flags: ts.NodeFlags = 0, pos: number = 0, end: number = 0) {
super(kind, flags, pos, end);
}
static of (name: string): MockVariableDeclaration {
return new MockVariableDeclaration(new MockIdentifier(name));
}
}
export class MockSymbol implements ts.Symbol {
constructor(
public name: string, private node: ts.Declaration = MockVariableDeclaration.of(name),
public flags: ts.SymbolFlags = 0) {}
getFlags(): ts.SymbolFlags { return this.flags; }
getName(): string { return this.name; }
getDeclarations(): ts.Declaration[] { return [this.node]; }
getDocumentationComment(): ts.SymbolDisplayPart[] { return []; }
// TODO(vicb): removed in TS 2.2
getJsDocTags(): any[] { return []; }
static of (name: string): MockSymbol { return new MockSymbol(name); }
}
export function expectNoDiagnostics(diagnostics: ts.Diagnostic[]) {
for (const diagnostic of diagnostics) {
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
if (diagnostic.file && diagnostic.start) {
const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
// tslint:disable-next-line:no-console
console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
}
}
expect(diagnostics.length).toBe(0);
}
export function expectValidSources(service: ts.LanguageService, program: ts.Program) {
expectNoDiagnostics(service.getCompilerOptionsDiagnostics());
for (const sourceFile of program.getSourceFiles()) {
expectNoDiagnostics(service.getSyntacticDiagnostics(sourceFile.fileName));
expectNoDiagnostics(service.getSemanticDiagnostics(sourceFile.fileName));
}
}
export function allChildren<T>(node: ts.Node, cb: (node: ts.Node) => T | undefined): T|undefined {
return ts.forEachChild(node, child => cb(node) || allChildren(child, cb));
}
export function findClass(sourceFile: ts.SourceFile, name: string): ts.ClassDeclaration|undefined {
return ts.forEachChild(
sourceFile, node => isClass(node) && isNamed(node.name, name) ? node : undefined);
}
export function findVar(sourceFile: ts.SourceFile, name: string): ts.VariableDeclaration|undefined {
return allChildren(
sourceFile, node => isVar(node) && isNamed(node.name, name) ? node : undefined);
}
export function findVarInitializer(sourceFile: ts.SourceFile, name: string): ts.Expression {
const v = findVar(sourceFile, name);
expect(v && v.initializer).toBeDefined();
return v !.initializer !;
}
export function isClass(node: ts.Node): node is ts.ClassDeclaration {
return node.kind === ts.SyntaxKind.ClassDeclaration;
}
export function isNamed(node: ts.Node | undefined, name: string): node is ts.Identifier {
return !!node && node.kind === ts.SyntaxKind.Identifier && (<ts.Identifier>node).text === name;
}
export function isVar(node: ts.Node): node is ts.VariableDeclaration {
return node.kind === ts.SyntaxKind.VariableDeclaration;
}

View File

@ -1,10 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/packages-dist/tsc-wrapped",
"types": [
"node",
"jasmine"
]
}
}

View File

@ -1,22 +0,0 @@
{
"compilerOptions": {
"baseUrl": ".",
"declaration": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "commonjs",
"moduleResolution": "node",
"outDir": "../../dist/all/@angular/tsc-wrapped",
"strictNullChecks": true,
"noImplicitAny": true,
"noFallthroughCasesInSwitch": true,
"paths": {
},
"rootDir": ".",
"sourceMap": true,
"inlineSources": true,
"lib": ["es6", "dom"],
"target": "es5",
"skipLibCheck": true
}
}

View File

@ -0,0 +1,24 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"paths": {
"@angular/*": ["./*"]
}
},
"exclude": [
"bazel",
"compiler/test",
"compiler-cli/integrationtest",
"platform-server/integrationtest",
"common/locales",
"examples",
"**/*_spec.ts",
"**/*.spec.ts"
],
"angularCompilerOptions": {
"skipTemplateCodegen": true
}
}

View File

@ -28,7 +28,7 @@
"bazel",
"compiler-cli/integrationtest",
"platform-server/integrationtest",
"tsc-wrapped",
"common/locales"
]
}

View File

@ -19,6 +19,8 @@
],
"angularCompilerOptions": {
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "static.js",
"flatModuleId": "@angular/upgrade/static"
}

View File

@ -21,7 +21,8 @@
"angularCompilerOptions": {
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"strictMetadataEmit": false,
"skipTemplateCodegen": true,
"flatModuleOutFile": "upgrade.js",
"flatModuleId": "@angular/upgrade"
}

View File

@ -52,14 +52,13 @@ fi
travisFoldStart "tsc tools"
$(npm bin)/tsc -p tools
$(npm bin)/tsc -p packages/tsc-wrapped/tsconfig-build.json
cp packages/tsc-wrapped/package.json dist/packages-dist/tsc-wrapped
$(npm bin)/tsc -p packages/tsc-wrapped/tsconfig.json
cp packages/tsc-wrapped/package.json dist/all/@angular/tsc-wrapped
$(npm bin)/tsc -p packages/compiler/tsconfig-tools.json
$(npm bin)/tsc -p packages/compiler-cli/tsconfig-tools.json
travisFoldEnd "tsc tools"
travisFoldStart "tsc all"
node --max-old-space-size=3000 dist/packages-dist/tsc-wrapped/src/main -p packages
node --max-old-space-size=3000 dist/packages-dist/tsc-wrapped/src/main -p modules
$(npm bin)/tsc -p packages
node dist/tools/@angular/compiler-cli/src/main -p packages/tsconfig-metadata.json
$(npm bin)/tsc -p modules
travisFoldEnd "tsc all"

View File

@ -5,7 +5,7 @@ set -u -e -o pipefail
# These ones can be `npm link`ed for fast development
LINKABLE_PKGS=(
$(pwd)/dist/packages-dist/{common,forms,core,compiler,compiler-cli,platform-{browser,server},platform-browser-dynamic,router,http,animations,tsc-wrapped}
$(pwd)/dist/packages-dist/{common,forms,core,compiler,compiler-cli,platform-{browser,server},platform-browser-dynamic,router,http,animations}
)
TYPESCRIPT_2_4=typescript@2.4.x
@ -46,10 +46,10 @@ cp -v package.json $TMP
#./node_modules/.bin/ngc -p tsconfig-build.json --i18nFile=src/messages.fi.xtb --locale=fi --i18nFormat=xtb
# Generate the metadata for the third-party modules
node ./node_modules/@angular/tsc-wrapped/src/main -p third_party_src/tsconfig-build.json
./node_modules/.bin/ngc -p third_party_src/tsconfig-build.json
# Generate the the bundle modules
node ./node_modules/@angular/tsc-wrapped/src/main -p flat_module/tsconfig-build.json
./node_modules/.bin/ngc -p flat_module/tsconfig-build.json
# Copy the html files from source to the emitted output
cp flat_module/src/*.html node_modules/flat_module/src

View File

@ -9,8 +9,6 @@ source ${thisDir}/_travis-fold.sh
# Run unit tests for our tools/ directory
travisFoldStart "test.unit.tools"
node ./dist/tools/tsc-watch/ tools runCmdsOnly
# TODO(i) could this be rolled into the tools tests above? why is it separate?
travisFoldStart "test.unit.validate-commit-message"
(
@ -30,8 +28,7 @@ travisFoldEnd "test.unit.node"
# rebuild to revert files in @angular/compiler/test
# TODO(tbosch): remove this and teach karma to serve the right files
travisFoldStart "test.unit.rebuildHack"
node --max-old-space-size=3000 dist/packages-dist/tsc-wrapped/src/main -p packages/tsconfig.json
node --max-old-space-size=3000 dist/packages-dist/tsc-wrapped/src/main -p modules/tsconfig.json
node dist/tools/@angular/compiler-cli/src/main -p packages/tsconfig-metadata.json
travisFoldStart "test.unit.rebuildHack"

View File

@ -19,12 +19,13 @@ else
fi
echo "Compiling tools..."
$(npm bin)/tsc -p tools
$(npm bin)/tsc -p packages/tsc-wrapped
if [[ $1 == 'node' ]]; then
# Note: .metadata.json files are needed for the language service tests!
echo "Creating .metadata.json files..."
node --max-old-space-size=3000 dist/all/@angular/tsc-wrapped/src/main -p packages
node --max-old-space-size=3000 dist/all/@angular/tsc-wrapped/src/main -p modules
echo "Building compiler..."
$(npm bin)/tsc -p packages/compiler/tsconfig-tools.json
$(npm bin)/tsc -p packages/compiler-cli/tsconfig-tools.json
echo "Creating packages .metadata.json files..."
node --max-old-space-size=3000 dist/tools/@angular/compiler-cli/src/main -p packages/tsconfig-metadata.json
fi
node dist/tools/tsc-watch/ $1 watch $2
fi

View File

@ -16,9 +16,6 @@ module.exports = (gulp) => () => {
// Ignore node_modules directories
'!**/node_modules/**',
// Ignore TypeScript mocks because it's not managed by us
'!./tools/@angular/tsc-wrapped/test/typescript.mocks.ts',
// Ignore generated files due to lack of copyright header
// todo(alfaproject): make generated files lintable
'!**/*.d.ts',

View File

@ -2,7 +2,7 @@ const fs = require('fs');
const path = require('path');
const sourceMapTest = require('../source-map-test');
const excludedPackages = ['bazel', 'tsc-wrapped', 'benchpress', 'compiler-cli', 'language-service'];
const excludedPackages = ['bazel', 'benchpress', 'compiler-cli', 'language-service'];
module.exports = (gulp) => () => {
const packageDir = path.resolve(process.cwd(), 'dist/packages-dist/');

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