feat(compiler-cli): reflect static methods added to classes in metadata (#21926)
PR Close #21926
This commit is contained in:
parent
1aa2947f70
commit
eb8ddd2983
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 * 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) {
|
||||||
|
@ -674,7 +691,7 @@ class AngularCompilerProgram implements Program {
|
||||||
this.emittedLibrarySummaries.push({fileName: genFile.genFileUrl, text: outData});
|
this.emittedLibrarySummaries.push({fileName: genFile.genFileUrl, text: outData});
|
||||||
if (!this.options.declaration) {
|
if (!this.options.declaration) {
|
||||||
// If we don't emit declarations, still record an empty .ngfactory.d.ts file,
|
// 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 =
|
const ngFactoryDts =
|
||||||
genFile.genFileUrl.substring(0, genFile.genFileUrl.length - 15) + '.ngfactory.d.ts';
|
genFile.genFileUrl.substring(0, genFile.genFileUrl.length - 15) + '.ngfactory.d.ts';
|
||||||
this.emittedLibrarySummaries.push({fileName: ngFactoryDts, text: ''});
|
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.
|
// 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.
|
||||||
|
@ -855,7 +872,7 @@ export function i18nSerialize(
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPathNormalizer(basePath?: string) {
|
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) => {
|
return (sourcePath: string) => {
|
||||||
sourcePath = basePath ? path.relative(basePath, sourcePath) : sourcePath;
|
sourcePath = basePath ? path.relative(basePath, sourcePath) : sourcePath;
|
||||||
return sourcePath.split(path.sep).join('/');
|
return sourcePath.split(path.sep).join('/');
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -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';
|
||||||
|
|
Loading…
Reference in New Issue