Skip to content
Permalink
Browse files

feat(compiler-cli): reflect static methods added to classes in metada…

…ta (#21926)

PR Close #21926
  • Loading branch information...
chuckjaz authored and alxhub committed Jan 31, 2018
1 parent 1aa2947 commit eb8ddd2983f2ad5b8521e6a0387adc21342efe5c
@@ -76,6 +76,9 @@ export class MetadataCollector {
}

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

@@ -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<string>): boolean {

const LOWERABLE_FIELD_NAMES = new Set(['useValue', 'useFactory', 'data']);

export class LowerMetadataCache implements RequestsMap {
private collector: MetadataCollector;
private metadataCache = new Map<string, MetadataAndLoweringRequests>();

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<string, RequestLocationMap>();

// 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<number, LoweringRequest>();
}
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<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 = (() => {
let exportTable: Set<string>;
@@ -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};
}
}

@@ -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;
}
}
@@ -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<string, LibrarySummary>|undefined;
private oldProgramEmittedGeneratedFiles: Map<string, GeneratedFile>|undefined;
private oldProgramEmittedSourceFiles: Map<string, ts.SourceFile>|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<string, LibrarySummary> {
@@ -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,15 +242,15 @@ 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({
program: this.tsProgram,
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<ts.SourceFile>[] = [];
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('/');

0 comments on commit eb8ddd2

Please sign in to comment.
You can’t perform that action at this time.