feat(compiler-cli): reflect static methods added to classes in metadata (#21926)

PR Close #21926
This commit is contained in:
Chuck Jazdzewski 2018-01-30 17:17:54 -08:00 committed by Alex Rickabaugh
parent 1aa2947f70
commit eb8ddd2983
8 changed files with 252 additions and 61 deletions

View File

@ -76,6 +76,9 @@ export class MetadataCollector {
} }
function recordEntry<T extends MetadataEntry>(entry: T, node: ts.Node): T { function recordEntry<T extends MetadataEntry>(entry: T, node: ts.Node): T {
if (composedSubstituter) {
entry = composedSubstituter(entry as MetadataValue, node) as T;
}
return recordMapEntry(entry, node, nodeMap, sourceFile); return recordMapEntry(entry, node, nodeMap, sourceFile);
} }

View File

@ -10,6 +10,7 @@ import {createLoweredSymbol, isLoweredSymbol} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {CollectorOptions, MetadataCollector, MetadataValue, ModuleMetadata, isMetadataGlobalReferenceExpression} from '../metadata/index'; import {CollectorOptions, MetadataCollector, MetadataValue, ModuleMetadata, isMetadataGlobalReferenceExpression} from '../metadata/index';
import {MetadataCache, MetadataTransformer, ValueTransform} from './metadata_cache';
export interface LoweringRequest { export interface LoweringRequest {
kind: ts.SyntaxKind; kind: ts.SyntaxKind;
@ -249,35 +250,39 @@ function isLiteralFieldNamed(node: ts.Node, names: Set<string>): boolean {
const LOWERABLE_FIELD_NAMES = new Set(['useValue', 'useFactory', 'data']); const LOWERABLE_FIELD_NAMES = new Set(['useValue', 'useFactory', 'data']);
export class LowerMetadataCache implements RequestsMap { export class LowerMetadataTransform implements RequestsMap, MetadataTransformer {
private collector: MetadataCollector; private cache: MetadataCache;
private metadataCache = new Map<string, MetadataAndLoweringRequests>(); private requests = new Map<string, RequestLocationMap>();
constructor(options: CollectorOptions, private strict?: boolean) {
this.collector = new MetadataCollector(options);
}
getMetadata(sourceFile: ts.SourceFile): ModuleMetadata|undefined {
return this.ensureMetadataAndRequests(sourceFile).metadata;
}
// RequestMap
getRequests(sourceFile: ts.SourceFile): RequestLocationMap { getRequests(sourceFile: ts.SourceFile): RequestLocationMap {
return this.ensureMetadataAndRequests(sourceFile).requests; let result = this.requests.get(sourceFile.fileName);
}
private ensureMetadataAndRequests(sourceFile: ts.SourceFile): MetadataAndLoweringRequests {
let result = this.metadataCache.get(sourceFile.fileName);
if (!result) { if (!result) {
result = this.getMetadataAndRequests(sourceFile); // Force the metadata for this source file to be collected which
this.metadataCache.set(sourceFile.fileName, result); // 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<number, LoweringRequest>();
} }
return result; 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; let identNumber = 0;
const freshIdent = () => createLoweredSymbol(identNumber++); const freshIdent = () => createLoweredSymbol(identNumber++);
const requests = new Map<number, LoweringRequest>(); const requests = new Map<number, LoweringRequest>();
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 = (() => { const isExportedSymbol = (() => {
let exportTable: Set<string>; let exportTable: Set<string>;
@ -303,13 +308,8 @@ export class LowerMetadataCache implements RequestsMap {
} }
return false; 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 (!isPrimitive(value) && !isRewritten(value)) {
if ((node.kind === ts.SyntaxKind.ArrowFunction || if ((node.kind === ts.SyntaxKind.ArrowFunction ||
node.kind === ts.SyntaxKind.FunctionExpression) && node.kind === ts.SyntaxKind.FunctionExpression) &&
@ -323,18 +323,6 @@ export class LowerMetadataCache implements RequestsMap {
} }
return value; 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};
} }
} }

View File

@ -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<string, ModuleMetadata|undefined>();
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;
}
}

View File

@ -13,16 +13,19 @@ import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diagnostics'; 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 {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 {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 {getAngularEmitterTransformFactory} from './node_emitter_transform';
import {PartialModuleMetadataTransformer} from './r3_metadata_transform';
import {getAngularClassTransformerFactory} from './r3_transform'; import {getAngularClassTransformerFactory} from './r3_transform';
import {GENERATED_FILES, StructureIsReused, createMessageDiagnostic, isInRootDir, ngToTsDiagnostic, tsStructureIsReused, userError} from './util'; import {GENERATED_FILES, StructureIsReused, createMessageDiagnostic, isInRootDir, ngToTsDiagnostic, tsStructureIsReused, userError} from './util';
/** /**
* Maximum number of files that are emitable via calling ts.Program.emit * Maximum number of files that are emitable via calling ts.Program.emit
* passing individual targetSourceFiles. * passing individual targetSourceFiles.
@ -43,7 +46,8 @@ const defaultEmitCallback: TsEmitCallback =
class AngularCompilerProgram implements Program { class AngularCompilerProgram implements Program {
private rootNames: string[]; private rootNames: string[];
private metadataCache: LowerMetadataCache; private metadataCache: MetadataCache;
private loweringMetadataTransform: LowerMetadataTransform;
private oldProgramLibrarySummaries: Map<string, LibrarySummary>|undefined; private oldProgramLibrarySummaries: Map<string, LibrarySummary>|undefined;
private oldProgramEmittedGeneratedFiles: Map<string, GeneratedFile>|undefined; private oldProgramEmittedGeneratedFiles: Map<string, GeneratedFile>|undefined;
private oldProgramEmittedSourceFiles: Map<string, ts.SourceFile>|undefined; private oldProgramEmittedSourceFiles: Map<string, ts.SourceFile>|undefined;
@ -93,7 +97,14 @@ class AngularCompilerProgram implements Program {
this.host = bundleHost; 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<string, LibrarySummary> { getLibrarySummaries(): Map<string, LibrarySummary> {
@ -183,7 +194,7 @@ class AngularCompilerProgram implements Program {
const {tmpProgram, sourceFiles, rootNames} = this._createProgramWithBasicStubs(); const {tmpProgram, sourceFiles, rootNames} = this._createProgramWithBasicStubs();
return this.compiler.loadFilesAsync(sourceFiles).then(analyzedModules => { return this.compiler.loadFilesAsync(sourceFiles).then(analyzedModules => {
if (this._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); this._updateProgramWithTypeCheckStubs(tmpProgram, analyzedModules, rootNames);
}); });
@ -231,7 +242,7 @@ class AngularCompilerProgram implements Program {
const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS; const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS;
const tsCustomTansformers = this.calculateTransforms( const tsCustomTransformers = this.calculateTransforms(
/* genFiles */ undefined, /* partialModules */ modules, customTransformers); /* genFiles */ undefined, /* partialModules */ modules, customTransformers);
const emitResult = emitCallback({ const emitResult = emitCallback({
@ -239,7 +250,7 @@ class AngularCompilerProgram implements Program {
host: this.host, host: this.host,
options: this.options, options: this.options,
writeFile: writeTsFile, emitOnlyDtsFiles, writeFile: writeTsFile, emitOnlyDtsFiles,
customTransformers: tsCustomTansformers customTransformers: tsCustomTransformers
}); });
return emitResult; return emitResult;
@ -293,7 +304,7 @@ class AngularCompilerProgram implements Program {
} }
this.writeFile(outFileName, outData, writeByteOrderMark, onError, genFile, sourceFiles); this.writeFile(outFileName, outData, writeByteOrderMark, onError, genFile, sourceFiles);
}; };
const tsCustomTansformers = this.calculateTransforms( const tsCustomTransformers = this.calculateTransforms(
genFileByFileName, /* partialModules */ undefined, customTransformers); genFileByFileName, /* partialModules */ undefined, customTransformers);
const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS; const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS;
// Restore the original references before we emit so TypeScript doesn't emit // Restore the original references before we emit so TypeScript doesn't emit
@ -330,7 +341,7 @@ class AngularCompilerProgram implements Program {
host: this.host, host: this.host,
options: this.options, options: this.options,
writeFile: writeTsFile, emitOnlyDtsFiles, writeFile: writeTsFile, emitOnlyDtsFiles,
customTransformers: tsCustomTansformers, customTransformers: tsCustomTransformers,
targetSourceFile: this.tsProgram.getSourceFile(fileName), targetSourceFile: this.tsProgram.getSourceFile(fileName),
}))); })));
emittedUserTsCount = sourceFilesToEmit.length; emittedUserTsCount = sourceFilesToEmit.length;
@ -340,7 +351,7 @@ class AngularCompilerProgram implements Program {
host: this.host, host: this.host,
options: this.options, options: this.options,
writeFile: writeTsFile, emitOnlyDtsFiles, writeFile: writeTsFile, emitOnlyDtsFiles,
customTransformers: tsCustomTansformers customTransformers: tsCustomTransformers
}); });
emittedUserTsCount = this.tsProgram.getSourceFiles().length - genTsFiles.length; emittedUserTsCount = this.tsProgram.getSourceFiles().length - genTsFiles.length;
} }
@ -454,13 +465,19 @@ class AngularCompilerProgram implements Program {
customTransformers?: CustomTransformers): ts.CustomTransformers { customTransformers?: CustomTransformers): ts.CustomTransformers {
const beforeTs: ts.TransformerFactory<ts.SourceFile>[] = []; const beforeTs: ts.TransformerFactory<ts.SourceFile>[] = [];
if (!this.options.disableExpressionLowering) { if (!this.options.disableExpressionLowering) {
beforeTs.push(getExpressionLoweringTransformFactory(this.metadataCache, this.tsProgram)); beforeTs.push(
getExpressionLoweringTransformFactory(this.loweringMetadataTransform, this.tsProgram));
} }
if (genFiles) { if (genFiles) {
beforeTs.push(getAngularEmitterTransformFactory(genFiles, this.getTsProgram())); beforeTs.push(getAngularEmitterTransformFactory(genFiles, this.getTsProgram()));
} }
if (partialModules) { if (partialModules) {
beforeTs.push(getAngularClassTransformerFactory(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) { if (customTransformers && customTransformers.beforeTs) {
beforeTs.push(...customTransformers.beforeTs); beforeTs.push(...customTransformers.beforeTs);
@ -505,7 +522,7 @@ class AngularCompilerProgram implements Program {
sourceFiles: string[], sourceFiles: string[],
} { } {
if (this._analyzedModules) { 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! // Note: This is important to not produce a memory leak!
const oldTsProgram = this.oldTsProgram; const oldTsProgram = this.oldTsProgram;
@ -522,7 +539,7 @@ class AngularCompilerProgram implements Program {
if (this.options.generateCodeForLibraries !== false) { if (this.options.generateCodeForLibraries !== false) {
// if we should generateCodeForLibraries, never include // if we should generateCodeForLibraries, never include
// generated files in the program as otherwise we will // 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. // TS5055: Cannot write file ... because it would overwrite input file.
rootNames = rootNames.filter(fn => !GENERATED_FILES.test(fn)); rootNames = rootNames.filter(fn => !GENERATED_FILES.test(fn));
} }
@ -551,7 +568,7 @@ class AngularCompilerProgram implements Program {
if (sf.fileName.endsWith('.ngfactory.ts')) { if (sf.fileName.endsWith('.ngfactory.ts')) {
const {generate, baseFileName} = this.hostAdapter.shouldGenerateFile(sf.fileName); const {generate, baseFileName} = this.hostAdapter.shouldGenerateFile(sf.fileName);
if (generate) { 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. // for .ngfactory.ts files.
const genFile = this.compiler.emitTypeCheckStub(sf.fileName, baseFileName !); const genFile = this.compiler.emitTypeCheckStub(sf.fileName, baseFileName !);
if (genFile) { if (genFile) {
@ -688,7 +705,7 @@ class AngularCompilerProgram implements Program {
} }
} }
// Filter out generated files for which we didn't generate code. // 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 // Note: sourceFile refers to the .ngfactory.ts / .ngsummary.ts file
// node_emitter_transform already set the file contents to be empty, // node_emitter_transform already set the file contents to be empty,
// so this code only needs to skip the file if !allowEmptyCodegenFiles. // so this code only needs to skip the file if !allowEmptyCodegenFiles.

View File

@ -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<string, PartialModule>;
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<string, ClassStmt>(
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;
}

View File

@ -8,8 +8,9 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ModuleMetadata} from '../../src/metadata/index'; import {MetadataCollector, ModuleMetadata} from '../../src/metadata/index';
import {LowerMetadataCache, LoweringRequest, RequestLocationMap, getExpressionLoweringTransformFactory} from '../../src/transformers/lower_expressions'; import {LowerMetadataTransform, LoweringRequest, RequestLocationMap, getExpressionLoweringTransformFactory} from '../../src/transformers/lower_expressions';
import {MetadataCache} from '../../src/transformers/metadata_cache';
import {Directory, MockAotContext, MockCompilerHost} from '../mocks'; import {Directory, MockAotContext, MockCompilerHost} from '../mocks';
describe('Expression lowering', () => { describe('Expression lowering', () => {
@ -110,7 +111,8 @@ describe('Expression lowering', () => {
}); });
it('should throw a validation exception for invalid files', () => { 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( const sourceFile = ts.createSourceFile(
'foo.ts', ` 'foo.ts', `
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
@ -126,7 +128,8 @@ describe('Expression lowering', () => {
}); });
it('should not report validation errors on a .d.ts file', () => { 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( const dtsFile = ts.createSourceFile(
'foo.d.ts', ` 'foo.d.ts', `
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
@ -241,11 +244,12 @@ function normalizeResult(result: string): string {
function collect(annotatedSource: string) { function collect(annotatedSource: string) {
const {annotations, unannotatedSource} = getAnnotations(annotatedSource); 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( const sourceFile = ts.createSourceFile(
'someName.ts', unannotatedSource, ts.ScriptTarget.Latest, /* setParentNodes */ true); 'someName.ts', unannotatedSource, ts.ScriptTarget.Latest, /* setParentNodes */ true);
return { return {
metadata: cache.getMetadata(sourceFile), metadata: cache.getMetadata(sourceFile),
requests: cache.getRequests(sourceFile), annotations requests: transformer.getRequests(sourceFile), annotations
}; };
} }

View File

@ -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');
}
}
}
});
});

View File

@ -67,7 +67,7 @@ export * from './ml_parser/html_tags';
export * from './ml_parser/interpolation_config'; export * from './ml_parser/interpolation_config';
export * from './ml_parser/tags'; export * from './ml_parser/tags';
export {NgModuleCompiler} from './ng_module_compiler'; 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 {EmitterVisitorContext} from './output/abstract_emitter';
export * from './output/ts_emitter'; export * from './output/ts_emitter';
export * from './parse_util'; export * from './parse_util';