290 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			290 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @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 {SummaryResolver} from '../summary_resolver';
							 | 
						||
| 
								 | 
							
								import {ValueTransformer, visitValue} from '../util';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import {StaticSymbol, StaticSymbolCache} from './static_symbol';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export class ResolvedStaticSymbol {
							 | 
						||
| 
								 | 
							
								  constructor(public symbol: StaticSymbol, public metadata: any) {}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * The host of the SymbolResolverHost disconnects the implementation from TypeScript / other
							 | 
						||
| 
								 | 
							
								 * language
							 | 
						||
| 
								 | 
							
								 * services and from underlying file systems.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								export interface StaticSymbolResolverHost {
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Return a ModuleMetadata for the given module.
							 | 
						||
| 
								 | 
							
								   * Angular 2 CLI will produce this metadata for a module whenever a .d.ts files is
							 | 
						||
| 
								 | 
							
								   * produced and the module has exported variables or classes with decorators. Module metadata can
							 | 
						||
| 
								 | 
							
								   * also be produced directly from TypeScript sources by using MetadataCollector in tools/metadata.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param modulePath is a string identifier for a module as an absolute path.
							 | 
						||
| 
								 | 
							
								   * @returns the metadata for the given module.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  getMetadataFor(modulePath: string): {[key: string]: any}[];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Converts a module name that is used in an `import` to a file path.
							 | 
						||
| 
								 | 
							
								   * I.e.
							 | 
						||
| 
								 | 
							
								   * `path/to/containingFile.ts` containing `import {...} from 'module-name'`.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  moduleNameToFileName(moduleName: string, containingFile: string): string /*|null*/;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const SUPPORTED_SCHEMA_VERSION = 3;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * This class is responsible for loading metadata per symbol,
							 | 
						||
| 
								 | 
							
								 * and normalizing references between symbols.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								export class StaticSymbolResolver {
							 | 
						||
| 
								 | 
							
								  private metadataCache = new Map<string, {[key: string]: any}>();
							 | 
						||
| 
								 | 
							
								  private resolvedSymbols = new Map<StaticSymbol, ResolvedStaticSymbol>();
							 | 
						||
| 
								 | 
							
								  private resolvedFilePaths = new Set<string>();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  constructor(
							 | 
						||
| 
								 | 
							
								      private host: StaticSymbolResolverHost, private staticSymbolCache: StaticSymbolCache,
							 | 
						||
| 
								 | 
							
								      private summaryResolver: SummaryResolver<StaticSymbol>,
							 | 
						||
| 
								 | 
							
								      private errorRecorder?: (error: any, fileName: string) => void) {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  resolveSymbol(staticSymbol: StaticSymbol): ResolvedStaticSymbol {
							 | 
						||
| 
								 | 
							
								    if (staticSymbol.members.length > 0) {
							 | 
						||
| 
								 | 
							
								      return this._resolveSymbolMembers(staticSymbol);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    let result = this._resolveSymbolFromSummary(staticSymbol);
							 | 
						||
| 
								 | 
							
								    if (!result) {
							 | 
						||
| 
								 | 
							
								      // Note: Some users use libraries that were not compiled with ngc, i.e. they don't
							 | 
						||
| 
								 | 
							
								      // have summaries, only .d.ts files. So we always need to check both, the summary
							 | 
						||
| 
								 | 
							
								      // and metadata.
							 | 
						||
| 
								 | 
							
								      this._createSymbolsOf(staticSymbol.filePath);
							 | 
						||
| 
								 | 
							
								      result = this.resolvedSymbols.get(staticSymbol);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return result;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  private _resolveSymbolMembers(staticSymbol: StaticSymbol): ResolvedStaticSymbol {
							 | 
						||
| 
								 | 
							
								    const members = staticSymbol.members;
							 | 
						||
| 
								 | 
							
								    const baseResolvedSymbol =
							 | 
						||
| 
								 | 
							
								        this.resolveSymbol(this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name));
							 | 
						||
| 
								 | 
							
								    if (!baseResolvedSymbol) {
							 | 
						||
| 
								 | 
							
								      return null;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const baseMetadata = baseResolvedSymbol.metadata;
							 | 
						||
| 
								 | 
							
								    if (baseMetadata instanceof StaticSymbol) {
							 | 
						||
| 
								 | 
							
								      return new ResolvedStaticSymbol(
							 | 
						||
| 
								 | 
							
								          staticSymbol, this.getStaticSymbol(baseMetadata.filePath, baseMetadata.name, members));
							 | 
						||
| 
								 | 
							
								    } else if (baseMetadata && baseMetadata.__symbolic === 'class') {
							 | 
						||
| 
								 | 
							
								      if (baseMetadata.statics && members.length === 1) {
							 | 
						||
| 
								 | 
							
								        return new ResolvedStaticSymbol(staticSymbol, baseMetadata.statics[members[0]]);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      let value = baseMetadata;
							 | 
						||
| 
								 | 
							
								      for (var i = 0; i < members.length && value; i++) {
							 | 
						||
| 
								 | 
							
								        value = value[members[i]];
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      return new ResolvedStaticSymbol(staticSymbol, value);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  private _resolveSymbolFromSummary(staticSymbol: StaticSymbol): ResolvedStaticSymbol {
							 | 
						||
| 
								 | 
							
								    const summary = this.summaryResolver.resolveSummary(staticSymbol);
							 | 
						||
| 
								 | 
							
								    return summary ? new ResolvedStaticSymbol(staticSymbol, summary.metadata) : null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * getStaticSymbol produces a Type whose metadata is known but whose implementation is not loaded.
							 | 
						||
| 
								 | 
							
								   * All types passed to the StaticResolver should be pseudo-types returned by this method.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param declarationFile the absolute path of the file where the symbol is declared
							 | 
						||
| 
								 | 
							
								   * @param name the name of the type.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol {
							 | 
						||
| 
								 | 
							
								    return this.staticSymbolCache.get(declarationFile, name, members);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  getSymbolsOf(filePath: string): StaticSymbol[] {
							 | 
						||
| 
								 | 
							
								    // Note: Some users use libraries that were not compiled with ngc, i.e. they don't
							 | 
						||
| 
								 | 
							
								    // have summaries, only .d.ts files. So we always need to check both, the summary
							 | 
						||
| 
								 | 
							
								    // and metadata.
							 | 
						||
| 
								 | 
							
								    let symbols = new Set<StaticSymbol>(this.summaryResolver.getSymbolsOf(filePath));
							 | 
						||
| 
								 | 
							
								    this._createSymbolsOf(filePath);
							 | 
						||
| 
								 | 
							
								    this.resolvedSymbols.forEach((resolvedSymbol) => {
							 | 
						||
| 
								 | 
							
								      if (resolvedSymbol.symbol.filePath === filePath) {
							 | 
						||
| 
								 | 
							
								        symbols.add(resolvedSymbol.symbol);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								    return Array.from(symbols);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  private _createSymbolsOf(filePath: string) {
							 | 
						||
| 
								 | 
							
								    if (this.resolvedFilePaths.has(filePath)) {
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    this.resolvedFilePaths.add(filePath);
							 | 
						||
| 
								 | 
							
								    const resolvedSymbols: ResolvedStaticSymbol[] = [];
							 | 
						||
| 
								 | 
							
								    const metadata = this.getModuleMetadata(filePath);
							 | 
						||
| 
								 | 
							
								    if (metadata['metadata']) {
							 | 
						||
| 
								 | 
							
								      // handle direct declarations of the symbol
							 | 
						||
| 
								 | 
							
								      Object.keys(metadata['metadata']).forEach((symbolName) => {
							 | 
						||
| 
								 | 
							
								        const symbolMeta = metadata['metadata'][symbolName];
							 | 
						||
| 
								 | 
							
								        resolvedSymbols.push(
							 | 
						||
| 
								 | 
							
								            this.createResolvedSymbol(this.getStaticSymbol(filePath, symbolName), symbolMeta));
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // handle the symbols in one of the re-export location
							 | 
						||
| 
								 | 
							
								    if (metadata['exports']) {
							 | 
						||
| 
								 | 
							
								      for (const moduleExport of metadata['exports']) {
							 | 
						||
| 
								 | 
							
								        // handle the symbols in the list of explicitly re-exported symbols.
							 | 
						||
| 
								 | 
							
								        if (moduleExport.export) {
							 | 
						||
| 
								 | 
							
								          moduleExport.export.forEach((exportSymbol: any) => {
							 | 
						||
| 
								 | 
							
								            let symbolName: string;
							 | 
						||
| 
								 | 
							
								            if (typeof exportSymbol === 'string') {
							 | 
						||
| 
								 | 
							
								              symbolName = exportSymbol;
							 | 
						||
| 
								 | 
							
								            } else {
							 | 
						||
| 
								 | 
							
								              symbolName = exportSymbol.as;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            let symName = symbolName;
							 | 
						||
| 
								 | 
							
								            if (typeof exportSymbol !== 'string') {
							 | 
						||
| 
								 | 
							
								              symName = exportSymbol.name;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            const resolvedModule = this.resolveModule(moduleExport.from, filePath);
							 | 
						||
| 
								 | 
							
								            if (resolvedModule) {
							 | 
						||
| 
								 | 
							
								              const targetSymbol = this.getStaticSymbol(resolvedModule, symName);
							 | 
						||
| 
								 | 
							
								              const sourceSymbol = this.getStaticSymbol(filePath, symbolName);
							 | 
						||
| 
								 | 
							
								              resolvedSymbols.push(new ResolvedStaticSymbol(sourceSymbol, targetSymbol));
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								          });
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          // handle the symbols via export * directives.
							 | 
						||
| 
								 | 
							
								          const resolvedModule = this.resolveModule(moduleExport.from, filePath);
							 | 
						||
| 
								 | 
							
								          if (resolvedModule) {
							 | 
						||
| 
								 | 
							
								            const nestedExports = this.getSymbolsOf(resolvedModule);
							 | 
						||
| 
								 | 
							
								            nestedExports.forEach((targetSymbol) => {
							 | 
						||
| 
								 | 
							
								              const sourceSymbol = this.getStaticSymbol(filePath, targetSymbol.name);
							 | 
						||
| 
								 | 
							
								              resolvedSymbols.push(new ResolvedStaticSymbol(sourceSymbol, targetSymbol));
							 | 
						||
| 
								 | 
							
								            });
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    resolvedSymbols.forEach(
							 | 
						||
| 
								 | 
							
								        (resolvedSymbol) => this.resolvedSymbols.set(resolvedSymbol.symbol, resolvedSymbol));
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  private createResolvedSymbol(sourceSymbol: StaticSymbol, metadata: any): ResolvedStaticSymbol {
							 | 
						||
| 
								 | 
							
								    const self = this;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    class ReferenceTransformer extends ValueTransformer {
							 | 
						||
| 
								 | 
							
								      visitStringMap(map: {[key: string]: any}, functionParams: string[]): any {
							 | 
						||
| 
								 | 
							
								        const symbolic = map['__symbolic'];
							 | 
						||
| 
								 | 
							
								        if (symbolic === 'function') {
							 | 
						||
| 
								 | 
							
								          const oldLen = functionParams.length;
							 | 
						||
| 
								 | 
							
								          functionParams.push(...(map['parameters'] || []));
							 | 
						||
| 
								 | 
							
								          const result = super.visitStringMap(map, functionParams);
							 | 
						||
| 
								 | 
							
								          functionParams.length = oldLen;
							 | 
						||
| 
								 | 
							
								          return result;
							 | 
						||
| 
								 | 
							
								        } else if (symbolic === 'reference') {
							 | 
						||
| 
								 | 
							
								          const module = map['module'];
							 | 
						||
| 
								 | 
							
								          const name = map['name'];
							 | 
						||
| 
								 | 
							
								          if (!name) {
							 | 
						||
| 
								 | 
							
								            return null;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          let filePath: string;
							 | 
						||
| 
								 | 
							
								          if (module) {
							 | 
						||
| 
								 | 
							
								            filePath = self.resolveModule(module, sourceSymbol.filePath);
							 | 
						||
| 
								 | 
							
								            if (!filePath) {
							 | 
						||
| 
								 | 
							
								              return {
							 | 
						||
| 
								 | 
							
								                __symbolic: 'error',
							 | 
						||
| 
								 | 
							
								                message: `Could not resolve ${module} relative to ${sourceSymbol.filePath}.`
							 | 
						||
| 
								 | 
							
								              };
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								          } else {
							 | 
						||
| 
								 | 
							
								            const isFunctionParam = functionParams.indexOf(name) >= 0;
							 | 
						||
| 
								 | 
							
								            if (!isFunctionParam) {
							 | 
						||
| 
								 | 
							
								              filePath = sourceSymbol.filePath;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          if (filePath) {
							 | 
						||
| 
								 | 
							
								            return self.getStaticSymbol(filePath, name);
							 | 
						||
| 
								 | 
							
								          } else {
							 | 
						||
| 
								 | 
							
								            // reference to a function parameter
							 | 
						||
| 
								 | 
							
								            return {__symbolic: 'reference', name: name};
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          return super.visitStringMap(map, functionParams);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const transformedMeta = visitValue(metadata, new ReferenceTransformer(), []);
							 | 
						||
| 
								 | 
							
								    return new ResolvedStaticSymbol(sourceSymbol, transformedMeta);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  private reportError(error: Error, context: StaticSymbol, path?: string) {
							 | 
						||
| 
								 | 
							
								    if (this.errorRecorder) {
							 | 
						||
| 
								 | 
							
								      this.errorRecorder(error, (context && context.filePath) || path);
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      throw error;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * @param module an absolute path to a module file.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  private getModuleMetadata(module: string): {[key: string]: any} {
							 | 
						||
| 
								 | 
							
								    let moduleMetadata = this.metadataCache.get(module);
							 | 
						||
| 
								 | 
							
								    if (!moduleMetadata) {
							 | 
						||
| 
								 | 
							
								      const moduleMetadatas = this.host.getMetadataFor(module);
							 | 
						||
| 
								 | 
							
								      if (moduleMetadatas) {
							 | 
						||
| 
								 | 
							
								        let maxVersion = -1;
							 | 
						||
| 
								 | 
							
								        moduleMetadatas.forEach((md) => {
							 | 
						||
| 
								 | 
							
								          if (md['version'] > maxVersion) {
							 | 
						||
| 
								 | 
							
								            maxVersion = md['version'];
							 | 
						||
| 
								 | 
							
								            moduleMetadata = md;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      if (!moduleMetadata) {
							 | 
						||
| 
								 | 
							
								        moduleMetadata =
							 | 
						||
| 
								 | 
							
								            {__symbolic: 'module', version: SUPPORTED_SCHEMA_VERSION, module: module, metadata: {}};
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      if (moduleMetadata['version'] != SUPPORTED_SCHEMA_VERSION) {
							 | 
						||
| 
								 | 
							
								        const errorMessage = moduleMetadata['version'] == 2 ?
							 | 
						||
| 
								 | 
							
								            `Unsupported metadata version ${moduleMetadata['version']} for module ${module}. This module should be compiled with a newer version of ngc` :
							 | 
						||
| 
								 | 
							
								            `Metadata version mismatch for module ${module}, found version ${moduleMetadata['version']}, expected ${SUPPORTED_SCHEMA_VERSION}`;
							 | 
						||
| 
								 | 
							
								        this.reportError(new Error(errorMessage), null);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      this.metadataCache.set(module, moduleMetadata);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return moduleMetadata;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  getSymbolByModule(module: string, symbolName: string, containingFile?: string): StaticSymbol {
							 | 
						||
| 
								 | 
							
								    const filePath = this.resolveModule(module, containingFile);
							 | 
						||
| 
								 | 
							
								    if (!filePath) {
							 | 
						||
| 
								 | 
							
								      throw new Error(`Could not resolve module ${module} relative to ${containingFile}`);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return this.getStaticSymbol(filePath, symbolName);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  private resolveModule(module: string, containingFile: string): string {
							 | 
						||
| 
								 | 
							
								    try {
							 | 
						||
| 
								 | 
							
								      return this.host.moduleNameToFileName(module, containingFile);
							 | 
						||
| 
								 | 
							
								    } catch (e) {
							 | 
						||
| 
								 | 
							
								      console.error(`Could not resolve module '${module}' relative to file ${containingFile}`);
							 | 
						||
| 
								 | 
							
								      this.reportError(new e, null, containingFile);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 |