diff --git a/packages/compiler-cli/src/metadata/collector.ts b/packages/compiler-cli/src/metadata/collector.ts index 77611644f1..bb57a77306 100644 --- a/packages/compiler-cli/src/metadata/collector.ts +++ b/packages/compiler-cli/src/metadata/collector.ts @@ -76,6 +76,9 @@ export class MetadataCollector { } function recordEntry(entry: T, node: ts.Node): T { + if (composedSubstituter) { + entry = composedSubstituter(entry as MetadataValue, node) as T; + } return recordMapEntry(entry, node, nodeMap, sourceFile); } diff --git a/packages/compiler-cli/src/transformers/lower_expressions.ts b/packages/compiler-cli/src/transformers/lower_expressions.ts index 9ac0f8174d..0793782a50 100644 --- a/packages/compiler-cli/src/transformers/lower_expressions.ts +++ b/packages/compiler-cli/src/transformers/lower_expressions.ts @@ -10,6 +10,7 @@ import {createLoweredSymbol, isLoweredSymbol} from '@angular/compiler'; import * as ts from 'typescript'; import {CollectorOptions, MetadataCollector, MetadataValue, ModuleMetadata, isMetadataGlobalReferenceExpression} from '../metadata/index'; +import {MetadataCache, MetadataTransformer, ValueTransform} from './metadata_cache'; export interface LoweringRequest { kind: ts.SyntaxKind; @@ -249,35 +250,39 @@ function isLiteralFieldNamed(node: ts.Node, names: Set): boolean { const LOWERABLE_FIELD_NAMES = new Set(['useValue', 'useFactory', 'data']); -export class LowerMetadataCache implements RequestsMap { - private collector: MetadataCollector; - private metadataCache = new Map(); - - constructor(options: CollectorOptions, private strict?: boolean) { - this.collector = new MetadataCollector(options); - } - - getMetadata(sourceFile: ts.SourceFile): ModuleMetadata|undefined { - return this.ensureMetadataAndRequests(sourceFile).metadata; - } +export class LowerMetadataTransform implements RequestsMap, MetadataTransformer { + private cache: MetadataCache; + private requests = new Map(); + // RequestMap getRequests(sourceFile: ts.SourceFile): RequestLocationMap { - return this.ensureMetadataAndRequests(sourceFile).requests; - } - - private ensureMetadataAndRequests(sourceFile: ts.SourceFile): MetadataAndLoweringRequests { - let result = this.metadataCache.get(sourceFile.fileName); + let result = this.requests.get(sourceFile.fileName); if (!result) { - result = this.getMetadataAndRequests(sourceFile); - this.metadataCache.set(sourceFile.fileName, result); + // Force the metadata for this source file to be collected which + // will recursively call start() populating the request map; + this.cache.getMetadata(sourceFile); + + // If we still don't have the requested metadata, the file is not a module + // or is a declaration file so return an empty map. + result = this.requests.get(sourceFile.fileName) || new Map(); } return result; } - private getMetadataAndRequests(sourceFile: ts.SourceFile): MetadataAndLoweringRequests { + // MetadataTransformer + connect(cache: MetadataCache): void { this.cache = cache; } + + start(sourceFile: ts.SourceFile): ValueTransform|undefined { let identNumber = 0; const freshIdent = () => createLoweredSymbol(identNumber++); const requests = new Map(); + this.requests.set(sourceFile.fileName, requests); + + const replaceNode = (node: ts.Node) => { + const name = freshIdent(); + requests.set(node.pos, {name, kind: node.kind, location: node.pos, end: node.end}); + return {__symbolic: 'reference', name}; + }; const isExportedSymbol = (() => { let exportTable: Set; @@ -303,13 +308,8 @@ export class LowerMetadataCache implements RequestsMap { } return false; }; - const replaceNode = (node: ts.Node) => { - const name = freshIdent(); - requests.set(node.pos, {name, kind: node.kind, location: node.pos, end: node.end}); - return {__symbolic: 'reference', name}; - }; - const substituteExpression = (value: MetadataValue, node: ts.Node): MetadataValue => { + return (value: MetadataValue, node: ts.Node): MetadataValue => { if (!isPrimitive(value) && !isRewritten(value)) { if ((node.kind === ts.SyntaxKind.ArrowFunction || node.kind === ts.SyntaxKind.FunctionExpression) && @@ -323,18 +323,6 @@ export class LowerMetadataCache implements RequestsMap { } return value; }; - - // Do not validate or lower metadata in a declaration file. Declaration files are requested - // when we need to update the version of the metadata to add information that might be missing - // in the out-of-date version that can be recovered from the .d.ts file. - const declarationFile = sourceFile.isDeclarationFile; - const moduleFile = ts.isExternalModule(sourceFile); - - const metadata = this.collector.getMetadata( - sourceFile, this.strict && !declarationFile, - moduleFile && !declarationFile ? substituteExpression : undefined); - - return {metadata, requests}; } } diff --git a/packages/compiler-cli/src/transformers/metadata_cache.ts b/packages/compiler-cli/src/transformers/metadata_cache.ts new file mode 100644 index 0000000000..fe6033050f --- /dev/null +++ b/packages/compiler-cli/src/transformers/metadata_cache.ts @@ -0,0 +1,66 @@ +/** + * @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 {MetadataCollector, MetadataValue, ModuleMetadata} from '../metadata/index'; + +import {MetadataProvider} from './compiler_host'; + +export type ValueTransform = (value: MetadataValue, node: ts.Node) => MetadataValue; + +export interface MetadataTransformer { + connect?(cache: MetadataCache): void; + start(sourceFile: ts.SourceFile): ValueTransform|undefined; +} + +/** + * Cache, and potentially transform, metadata as it is being collected. + */ +export class MetadataCache implements MetadataProvider { + private metadataCache = new Map(); + + constructor( + private collector: MetadataCollector, private strict: boolean, + private transformers: MetadataTransformer[]) { + for (let transformer of transformers) { + if (transformer.connect) { + transformer.connect(this); + } + } + } + + getMetadata(sourceFile: ts.SourceFile): ModuleMetadata|undefined { + if (this.metadataCache.has(sourceFile.fileName)) { + return this.metadataCache.get(sourceFile.fileName); + } + let substitute: ValueTransform|undefined = undefined; + + // Only process transformers on modules that are not declaration files. + const declarationFile = sourceFile.isDeclarationFile; + const moduleFile = ts.isExternalModule(sourceFile); + if (!declarationFile && moduleFile) { + for (let transform of this.transformers) { + const transformSubstitute = transform.start(sourceFile); + if (transformSubstitute) { + if (substitute) { + const previous: ValueTransform = substitute; + substitute = (value: MetadataValue, node: ts.Node) => + transformSubstitute(previous(value, node), node); + } else { + substitute = transformSubstitute; + } + } + } + } + + const result = this.collector.getMetadata(sourceFile, this.strict, substitute); + this.metadataCache.set(sourceFile.fileName, result); + return result; + } +} \ No newline at end of file diff --git a/packages/compiler-cli/src/transformers/program.ts b/packages/compiler-cli/src/transformers/program.ts index 9200819ad3..ebe789dbd4 100644 --- a/packages/compiler-cli/src/transformers/program.ts +++ b/packages/compiler-cli/src/transformers/program.ts @@ -13,16 +13,19 @@ import * as path from 'path'; import * as ts from 'typescript'; import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diagnostics'; -import {ModuleMetadata, createBundleIndexHost} from '../metadata/index'; +import {MetadataCollector, ModuleMetadata, createBundleIndexHost} from '../metadata/index'; import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, DiagnosticMessageChain, EmitFlags, LazyRoute, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api'; import {CodeGenerator, TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host'; -import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions'; +import {LowerMetadataTransform, getExpressionLoweringTransformFactory} from './lower_expressions'; +import {MetadataCache, MetadataTransformer} from './metadata_cache'; import {getAngularEmitterTransformFactory} from './node_emitter_transform'; +import {PartialModuleMetadataTransformer} from './r3_metadata_transform'; import {getAngularClassTransformerFactory} from './r3_transform'; import {GENERATED_FILES, StructureIsReused, createMessageDiagnostic, isInRootDir, ngToTsDiagnostic, tsStructureIsReused, userError} from './util'; + /** * Maximum number of files that are emitable via calling ts.Program.emit * passing individual targetSourceFiles. @@ -43,7 +46,8 @@ const defaultEmitCallback: TsEmitCallback = class AngularCompilerProgram implements Program { private rootNames: string[]; - private metadataCache: LowerMetadataCache; + private metadataCache: MetadataCache; + private loweringMetadataTransform: LowerMetadataTransform; private oldProgramLibrarySummaries: Map|undefined; private oldProgramEmittedGeneratedFiles: Map|undefined; private oldProgramEmittedSourceFiles: Map|undefined; @@ -93,7 +97,14 @@ class AngularCompilerProgram implements Program { this.host = bundleHost; } } - this.metadataCache = new LowerMetadataCache({quotedNames: true}, !!options.strictMetadataEmit); + this.loweringMetadataTransform = new LowerMetadataTransform(); + this.metadataCache = this.createMetadataCache([this.loweringMetadataTransform]); + } + + private createMetadataCache(transformers: MetadataTransformer[]) { + return new MetadataCache( + new MetadataCollector({quotedNames: true}), !!this.options.strictMetadataEmit, + transformers); } getLibrarySummaries(): Map { @@ -183,7 +194,7 @@ class AngularCompilerProgram implements Program { const {tmpProgram, sourceFiles, rootNames} = this._createProgramWithBasicStubs(); return this.compiler.loadFilesAsync(sourceFiles).then(analyzedModules => { if (this._analyzedModules) { - throw new Error('Angular structure loaded both synchronously and asynchronsly'); + throw new Error('Angular structure loaded both synchronously and asynchronously'); } this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, rootNames); }); @@ -231,7 +242,7 @@ class AngularCompilerProgram implements Program { const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS; - const tsCustomTansformers = this.calculateTransforms( + const tsCustomTransformers = this.calculateTransforms( /* genFiles */ undefined, /* partialModules */ modules, customTransformers); const emitResult = emitCallback({ @@ -239,7 +250,7 @@ class AngularCompilerProgram implements Program { host: this.host, options: this.options, writeFile: writeTsFile, emitOnlyDtsFiles, - customTransformers: tsCustomTansformers + customTransformers: tsCustomTransformers }); return emitResult; @@ -293,7 +304,7 @@ class AngularCompilerProgram implements Program { } this.writeFile(outFileName, outData, writeByteOrderMark, onError, genFile, sourceFiles); }; - const tsCustomTansformers = this.calculateTransforms( + const tsCustomTransformers = this.calculateTransforms( genFileByFileName, /* partialModules */ undefined, customTransformers); const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS; // Restore the original references before we emit so TypeScript doesn't emit @@ -330,7 +341,7 @@ class AngularCompilerProgram implements Program { host: this.host, options: this.options, writeFile: writeTsFile, emitOnlyDtsFiles, - customTransformers: tsCustomTansformers, + customTransformers: tsCustomTransformers, targetSourceFile: this.tsProgram.getSourceFile(fileName), }))); emittedUserTsCount = sourceFilesToEmit.length; @@ -340,7 +351,7 @@ class AngularCompilerProgram implements Program { host: this.host, options: this.options, writeFile: writeTsFile, emitOnlyDtsFiles, - customTransformers: tsCustomTansformers + customTransformers: tsCustomTransformers }); emittedUserTsCount = this.tsProgram.getSourceFiles().length - genTsFiles.length; } @@ -454,13 +465,19 @@ class AngularCompilerProgram implements Program { customTransformers?: CustomTransformers): ts.CustomTransformers { const beforeTs: ts.TransformerFactory[] = []; if (!this.options.disableExpressionLowering) { - beforeTs.push(getExpressionLoweringTransformFactory(this.metadataCache, this.tsProgram)); + beforeTs.push( + getExpressionLoweringTransformFactory(this.loweringMetadataTransform, this.tsProgram)); } if (genFiles) { beforeTs.push(getAngularEmitterTransformFactory(genFiles, this.getTsProgram())); } if (partialModules) { beforeTs.push(getAngularClassTransformerFactory(partialModules)); + + // If we have partial modules, the cached metadata might be incorrect as it doesn't reflect + // the partial module transforms. + this.metadataCache = this.createMetadataCache( + [this.loweringMetadataTransform, new PartialModuleMetadataTransformer(partialModules)]); } if (customTransformers && customTransformers.beforeTs) { beforeTs.push(...customTransformers.beforeTs); @@ -505,7 +522,7 @@ class AngularCompilerProgram implements Program { sourceFiles: string[], } { if (this._analyzedModules) { - throw new Error(`Internal Error: already initalized!`); + throw new Error(`Internal Error: already initialized!`); } // Note: This is important to not produce a memory leak! const oldTsProgram = this.oldTsProgram; @@ -522,7 +539,7 @@ class AngularCompilerProgram implements Program { if (this.options.generateCodeForLibraries !== false) { // if we should generateCodeForLibraries, never include // generated files in the program as otherwise we will - // ovewrite them and typescript will report the error + // overwrite them and typescript will report the error // TS5055: Cannot write file ... because it would overwrite input file. rootNames = rootNames.filter(fn => !GENERATED_FILES.test(fn)); } @@ -551,7 +568,7 @@ class AngularCompilerProgram implements Program { if (sf.fileName.endsWith('.ngfactory.ts')) { const {generate, baseFileName} = this.hostAdapter.shouldGenerateFile(sf.fileName); if (generate) { - // Note: ! is ok as hostAdapter.shouldGenerateFile will always return a basefileName + // Note: ! is ok as hostAdapter.shouldGenerateFile will always return a baseFileName // for .ngfactory.ts files. const genFile = this.compiler.emitTypeCheckStub(sf.fileName, baseFileName !); if (genFile) { @@ -674,7 +691,7 @@ class AngularCompilerProgram implements Program { this.emittedLibrarySummaries.push({fileName: genFile.genFileUrl, text: outData}); if (!this.options.declaration) { // If we don't emit declarations, still record an empty .ngfactory.d.ts file, - // as we might need it lateron for resolving module names from summaries. + // as we might need it later on for resolving module names from summaries. const ngFactoryDts = genFile.genFileUrl.substring(0, genFile.genFileUrl.length - 15) + '.ngfactory.d.ts'; this.emittedLibrarySummaries.push({fileName: ngFactoryDts, text: ''}); @@ -688,7 +705,7 @@ class AngularCompilerProgram implements Program { } } // Filter out generated files for which we didn't generate code. - // This can happen as the stub caclulation is not completely exact. + // This can happen as the stub calculation is not completely exact. // Note: sourceFile refers to the .ngfactory.ts / .ngsummary.ts file // node_emitter_transform already set the file contents to be empty, // so this code only needs to skip the file if !allowEmptyCodegenFiles. @@ -855,7 +872,7 @@ export function i18nSerialize( } function getPathNormalizer(basePath?: string) { - // normalize sourcepaths by removing the base path and always using "/" as a separator + // normalize source paths by removing the base path and always using "/" as a separator return (sourcePath: string) => { sourcePath = basePath ? path.relative(basePath, sourcePath) : sourcePath; return sourcePath.split(path.sep).join('/'); diff --git a/packages/compiler-cli/src/transformers/r3_metadata_transform.ts b/packages/compiler-cli/src/transformers/r3_metadata_transform.ts new file mode 100644 index 0000000000..ace19fea11 --- /dev/null +++ b/packages/compiler-cli/src/transformers/r3_metadata_transform.ts @@ -0,0 +1,55 @@ +/** + * @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 {ClassStmt, PartialModule, Statement, StmtModifier} from '@angular/compiler'; +import * as ts from 'typescript'; + +import {MetadataCollector, MetadataValue, ModuleMetadata, isClassMetadata} from '../metadata/index'; + +import {MetadataTransformer, ValueTransform} from './metadata_cache'; + +export class PartialModuleMetadataTransformer implements MetadataTransformer { + private moduleMap: Map; + + constructor(modules: PartialModule[]) { + this.moduleMap = new Map(modules.map<[string, PartialModule]>(m => [m.fileName, m])); + } + + start(sourceFile: ts.SourceFile): ValueTransform|undefined { + const partialModule = this.moduleMap.get(sourceFile.fileName); + if (partialModule) { + const classMap = new Map( + partialModule.statements.filter(isClassStmt).map<[string, ClassStmt]>(s => [s.name, s])); + if (classMap.size > 0) { + return (value: MetadataValue, node: ts.Node): MetadataValue => { + // For class metadata that is going to be transformed to have a static method ensure the + // metadata contains a static declaration the new static method. + if (isClassMetadata(value) && node.kind === ts.SyntaxKind.ClassDeclaration) { + const classDeclaration = node as ts.ClassDeclaration; + if (classDeclaration.name) { + const partialClass = classMap.get(classDeclaration.name.text); + if (partialClass) { + for (const field of partialClass.fields) { + if (field.name && field.modifiers && + field.modifiers.some(modifier => modifier === StmtModifier.Static)) { + value.statics = {...(value.statics || {}), [field.name]: {}}; + } + } + } + } + } + return value; + }; + } + } + } +} + +function isClassStmt(v: Statement): v is ClassStmt { + return v instanceof ClassStmt; +} diff --git a/packages/compiler-cli/test/transformers/lower_expressions_spec.ts b/packages/compiler-cli/test/transformers/lower_expressions_spec.ts index 2f34c235dc..8866993519 100644 --- a/packages/compiler-cli/test/transformers/lower_expressions_spec.ts +++ b/packages/compiler-cli/test/transformers/lower_expressions_spec.ts @@ -8,8 +8,9 @@ import * as ts from 'typescript'; -import {ModuleMetadata} from '../../src/metadata/index'; -import {LowerMetadataCache, LoweringRequest, RequestLocationMap, getExpressionLoweringTransformFactory} from '../../src/transformers/lower_expressions'; +import {MetadataCollector, ModuleMetadata} from '../../src/metadata/index'; +import {LowerMetadataTransform, LoweringRequest, RequestLocationMap, getExpressionLoweringTransformFactory} from '../../src/transformers/lower_expressions'; +import {MetadataCache} from '../../src/transformers/metadata_cache'; import {Directory, MockAotContext, MockCompilerHost} from '../mocks'; describe('Expression lowering', () => { @@ -110,7 +111,8 @@ describe('Expression lowering', () => { }); it('should throw a validation exception for invalid files', () => { - const cache = new LowerMetadataCache({}, /* strict */ true); + const cache = new MetadataCache( + new MetadataCollector({}), /* strict */ true, [new LowerMetadataTransform()]); const sourceFile = ts.createSourceFile( 'foo.ts', ` import {Injectable} from '@angular/core'; @@ -126,7 +128,8 @@ describe('Expression lowering', () => { }); it('should not report validation errors on a .d.ts file', () => { - const cache = new LowerMetadataCache({}, /* strict */ true); + const cache = new MetadataCache( + new MetadataCollector({}), /* strict */ true, [new LowerMetadataTransform()]); const dtsFile = ts.createSourceFile( 'foo.d.ts', ` import {Injectable} from '@angular/core'; @@ -241,11 +244,12 @@ function normalizeResult(result: string): string { function collect(annotatedSource: string) { const {annotations, unannotatedSource} = getAnnotations(annotatedSource); - const cache = new LowerMetadataCache({}); + const transformer = new LowerMetadataTransform(); + const cache = new MetadataCache(new MetadataCollector({}), false, [transformer]); const sourceFile = ts.createSourceFile( 'someName.ts', unannotatedSource, ts.ScriptTarget.Latest, /* setParentNodes */ true); return { metadata: cache.getMetadata(sourceFile), - requests: cache.getRequests(sourceFile), annotations + requests: transformer.getRequests(sourceFile), annotations }; } \ No newline at end of file diff --git a/packages/compiler-cli/test/transformers/r3_metadata_transform_spec.ts b/packages/compiler-cli/test/transformers/r3_metadata_transform_spec.ts new file mode 100644 index 0000000000..47b1d23dfb --- /dev/null +++ b/packages/compiler-cli/test/transformers/r3_metadata_transform_spec.ts @@ -0,0 +1,58 @@ +/** + * @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 {ClassField, ClassMethod, ClassStmt, PartialModule, Statement, StmtModifier} from '@angular/compiler'; +import * as ts from 'typescript'; + +import {MetadataCollector, isClassMetadata} from '../../src/metadata/index'; +import {MetadataCache} from '../../src/transformers/metadata_cache'; +import {PartialModuleMetadataTransformer} from '../../src/transformers/r3_metadata_transform'; + +describe('r3_transform_spec', () => { + + it('should add a static method to collected metadata', () => { + const fileName = '/some/directory/someFileName.ts'; + const className = 'SomeClass'; + const newFieldName = 'newStaticField'; + const source = ` + export class ${className} { + myMethod(): void {} + } + `; + + const sourceFile = + ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, /* setParentNodes */ true); + const partialModule: PartialModule = { + fileName, + statements: [new ClassStmt( + className, /* parent */ null, /* fields */[new ClassField( + /* name */ newFieldName, /* type */ null, /* modifiers */[StmtModifier.Static])], + /* getters */[], + /* constructorMethod */ new ClassMethod(/* name */ null, /* params */[], /* body */[]), + /* methods */[])] + }; + + const cache = new MetadataCache( + new MetadataCollector(), /* strict */ true, + [new PartialModuleMetadataTransformer([partialModule])]); + const metadata = cache.getMetadata(sourceFile); + expect(metadata).toBeDefined('Expected metadata from test source file'); + if (metadata) { + const classData = metadata.metadata[className]; + expect(classData && isClassMetadata(classData)) + .toBeDefined(`Expected metadata to contain data for "${className}"`); + if (classData && isClassMetadata(classData)) { + const statics = classData.statics; + expect(statics).toBeDefined(`Expected "${className}" metadata to contain statics`); + if (statics) { + expect(statics[newFieldName]).toEqual({}, 'Expected new field to recorded as a function'); + } + } + } + }); +}); \ No newline at end of file diff --git a/packages/compiler/src/compiler.ts b/packages/compiler/src/compiler.ts index 960c40a288..5c6b5a4482 100644 --- a/packages/compiler/src/compiler.ts +++ b/packages/compiler/src/compiler.ts @@ -67,7 +67,7 @@ export * from './ml_parser/html_tags'; export * from './ml_parser/interpolation_config'; export * from './ml_parser/tags'; export {NgModuleCompiler} from './ng_module_compiler'; -export {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassMethod, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, ThrowStmt, TryCatchStmt, WriteKeyExpr, WritePropExpr, WriteVarExpr, StmtModifier, Statement, collectExternalReferences} from './output/output_ast'; +export {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassField, ClassMethod, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, ThrowStmt, TryCatchStmt, WriteKeyExpr, WritePropExpr, WriteVarExpr, StmtModifier, Statement, collectExternalReferences} from './output/output_ast'; export {EmitterVisitorContext} from './output/abstract_emitter'; export * from './output/ts_emitter'; export * from './parse_util';