diff --git a/packages/bazel/src/ngc-wrapped/index.ts b/packages/bazel/src/ngc-wrapped/index.ts index fd0b47ee17b..2d1da6a0533 100644 --- a/packages/bazel/src/ngc-wrapped/index.ts +++ b/packages/bazel/src/ngc-wrapped/index.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import type {AngularCompilerOptions, CompilerHost as NgCompilerHost, TsEmitCallback, Program, Diagnostics, Diagnostic as NgDiagnostic, CompilerOptions} from '@angular/compiler-cli'; +import type {AngularCompilerOptions, CompilerHost as NgCompilerHost, TsEmitCallback, Program, CompilerOptions} from '@angular/compiler-cli'; import {BazelOptions, CachedFileLoader, CompilerHost, constructManifest, debug, FileCache, FileLoader, parseTsconfig, resolveNormalizedPath, runAsWorker, runWorkerLoop, UncachedFileLoader} from '@bazel/typescript'; import * as fs from 'fs'; import * as path from 'path'; @@ -195,9 +195,9 @@ export function compile({ bazelOpts: BazelOptions, files: string[], expectedOuts: string[], - gatherDiagnostics?: (program: Program) => Diagnostics, + gatherDiagnostics?: (program: Program) => readonly ts.Diagnostic[], bazelHost?: CompilerHost, ng: CompilerCliModule, -}): {diagnostics: Diagnostics, program: Program} { +}): {diagnostics: readonly ts.Diagnostic[], program: Program} { let fileLoader: FileLoader; if (bazelOpts.maxCacheSizeMb !== undefined) { @@ -449,7 +449,7 @@ function convertToForwardSlashPath(filePath: string): string { function gatherDiagnosticsForInputsOnly( options: CompilerOptions, bazelOpts: BazelOptions, ngProgram: Program, - ng: CompilerCliModule): (NgDiagnostic|ts.Diagnostic)[] { + ng: CompilerCliModule): ts.Diagnostic[] { const tsProgram = ngProgram.getTsProgram(); // For the Ivy compiler, track the amount of time spent fetching TypeScript diagnostics. @@ -457,7 +457,7 @@ function gatherDiagnosticsForInputsOnly( if (ngProgram instanceof ng.NgtscProgram) { previousPhase = ngProgram.compiler.perfRecorder.phase(ng.PerfPhase.TypeScriptDiagnostics); } - const diagnostics: (NgDiagnostic|ts.Diagnostic)[] = []; + const diagnostics: ts.Diagnostic[] = []; // These checks mirror ts.getPreEmitDiagnostics, with the important // exception of avoiding b/30708240, which is that if you call // program.getDeclarationDiagnostics() it somehow corrupts the emit. diff --git a/packages/common/test/pipes/date_pipe_spec.ts b/packages/common/test/pipes/date_pipe_spec.ts index 2d6856f0c27..fe51efa864e 100644 --- a/packages/common/test/pipes/date_pipe_spec.ts +++ b/packages/common/test/pipes/date_pipe_spec.ts @@ -9,7 +9,6 @@ import {DatePipe} from '@angular/common'; import localeEn from '@angular/common/locales/en'; import localeEnExtra from '@angular/common/locales/extra/en'; -import {PipeResolver} from '@angular/compiler/src/pipe_resolver'; import {ɵregisterLocaleData, ɵunregisterLocaleData} from '@angular/core'; { diff --git a/packages/common/test/pipes/i18n_plural_pipe_spec.ts b/packages/common/test/pipes/i18n_plural_pipe_spec.ts index a2608ac6f61..b03cef64438 100644 --- a/packages/common/test/pipes/i18n_plural_pipe_spec.ts +++ b/packages/common/test/pipes/i18n_plural_pipe_spec.ts @@ -7,7 +7,6 @@ */ import {I18nPluralPipe, NgLocalization} from '@angular/common'; -import {PipeResolver} from '@angular/compiler/src/pipe_resolver'; { describe('I18nPluralPipe', () => { diff --git a/packages/common/test/pipes/i18n_select_pipe_spec.ts b/packages/common/test/pipes/i18n_select_pipe_spec.ts index bf8da3195fb..4a26cba763b 100644 --- a/packages/common/test/pipes/i18n_select_pipe_spec.ts +++ b/packages/common/test/pipes/i18n_select_pipe_spec.ts @@ -7,7 +7,6 @@ */ import {I18nSelectPipe} from '@angular/common'; -import {PipeResolver} from '@angular/compiler/src/pipe_resolver'; { describe('I18nSelectPipe', () => { diff --git a/packages/compiler-cli/index.ts b/packages/compiler-cli/index.ts index 2bb68b1e84f..d0d8e636501 100644 --- a/packages/compiler-cli/index.ts +++ b/packages/compiler-cli/index.ts @@ -7,7 +7,6 @@ */ import {NodeJSFileSystem, setFileSystem} from './src/ngtsc/file_system'; -export {AotCompilerHost, AotCompilerHost as StaticReflectorHost, StaticReflector, StaticSymbol} from '@angular/compiler'; export {VERSION} from './src/version'; export * from './src/transformers/api'; @@ -18,8 +17,6 @@ export * from './src/perform_compile'; // TODO(tbosch): remove this once usages in G3 are changed to `CompilerOptions` export {CompilerOptions as AngularCompilerOptions} from './src/transformers/api'; -export {ngToTsDiagnostic} from './src/transformers/util'; - // Internal exports needed for packages relying on the compiler-cli. // TODO: Remove this when the CLI has switched to the private entry-point. export * from './private/tooling'; diff --git a/packages/compiler-cli/src/main.ts b/packages/compiler-cli/src/main.ts index a93c926caa2..8146405d9c0 100644 --- a/packages/compiler-cli/src/main.ts +++ b/packages/compiler-cli/src/main.ts @@ -9,7 +9,7 @@ import ts from 'typescript'; import type {TsickleHost} from 'tsickle'; import yargs from 'yargs'; -import {Diagnostics, exitCodeFromResult, formatDiagnostics, ParsedConfiguration, performCompilation, readConfiguration} from './perform_compile'; +import {exitCodeFromResult, formatDiagnostics, ParsedConfiguration, performCompilation, readConfiguration} from './perform_compile'; import {createPerformWatchHost, performWatchCompilation} from './perform_watch'; import * as api from './transformers/api'; import {GENERATED_FILES} from './transformers/util'; @@ -57,7 +57,7 @@ export function mainDiagnosticsForTest( programReuse?: {program: api.Program|undefined}, modifiedResourceFiles?: Set|null, tsickle?: TsickleModule): { exitCode: number, - diagnostics: ReadonlyArray, + diagnostics: ReadonlyArray, } { let {rootNames, options, errors: configErrors, emitFlags} = config || readNgcCommandLineAndConfiguration(args); @@ -223,7 +223,7 @@ function getFormatDiagnosticsHost(options?: api.CompilerOptions): ts.FormatDiagn } function reportErrorsAndExit( - allDiagnostics: Diagnostics, options?: api.CompilerOptions, + allDiagnostics: ReadonlyArray, options?: api.CompilerOptions, consoleError: (s: string) => void = console.error): number { const errorsAndWarnings = allDiagnostics.filter(d => d.category !== ts.DiagnosticCategory.Message); @@ -239,8 +239,8 @@ export function watchMode( } function printDiagnostics( - diagnostics: ReadonlyArray, - options: api.CompilerOptions|undefined, consoleError: (s: string) => void): void { + diagnostics: ReadonlyArray, options: api.CompilerOptions|undefined, + consoleError: (s: string) => void): void { if (diagnostics.length === 0) { return; } diff --git a/packages/compiler-cli/src/ngtsc/program.ts b/packages/compiler-cli/src/ngtsc/program.ts index 0fa22bd1441..a52c8cce339 100644 --- a/packages/compiler-cli/src/ngtsc/program.ts +++ b/packages/compiler-cli/src/ngtsc/program.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {GeneratedFile, HtmlParser, MessageBundle} from '@angular/compiler'; +import {HtmlParser, MessageBundle} from '@angular/compiler'; import ts from 'typescript'; import * as api from '../transformers/api'; @@ -179,18 +179,18 @@ export class NgtscProgram implements api.Program { } getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken| - undefined): readonly(ts.Diagnostic|api.Diagnostic)[] { + undefined): readonly ts.Diagnostic[] { return this.compiler.getOptionDiagnostics(); } getNgStructuralDiagnostics(cancellationToken?: ts.CancellationToken| - undefined): readonly api.Diagnostic[] { + undefined): readonly ts.Diagnostic[] { return []; } getNgSemanticDiagnostics( - fileName?: string|undefined, cancellationToken?: ts.CancellationToken|undefined): - readonly(ts.Diagnostic|api.Diagnostic)[] { + fileName?: string|undefined, + cancellationToken?: ts.CancellationToken|undefined): readonly ts.Diagnostic[] { let sf: ts.SourceFile|undefined = undefined; if (fileName !== undefined) { sf = this.tsProgram.getSourceFile(fileName); @@ -337,14 +337,6 @@ export class NgtscProgram implements api.Program { return this.compiler.getIndexedComponents(); } - getLibrarySummaries(): Map { - throw new Error('Method not implemented.'); - } - - getEmittedGeneratedFiles(): Map { - throw new Error('Method not implemented.'); - } - getEmittedSourceFiles(): Map { throw new Error('Method not implemented.'); } diff --git a/packages/compiler-cli/src/perform_compile.ts b/packages/compiler-cli/src/perform_compile.ts index 831c8b7ae8f..b4b0a39a579 100644 --- a/packages/compiler-cli/src/perform_compile.ts +++ b/packages/compiler-cli/src/perform_compile.ts @@ -6,99 +6,31 @@ * found in the LICENSE file at https://angular.io/license */ -import {isSyntaxError, Position} from '@angular/compiler'; +import {isSyntaxError} from '@angular/compiler'; import ts from 'typescript'; import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem, ReadonlyFileSystem, relative, resolve} from '../src/ngtsc/file_system'; -import {NgCompilerOptions} from './ngtsc/core/api'; +import {NgCompilerOptions} from './ngtsc/core/api'; import {replaceTsWithNgInErrors} from './ngtsc/diagnostics'; import * as api from './transformers/api'; import * as ng from './transformers/entry_points'; import {createMessageDiagnostic} from './transformers/util'; -export type Diagnostics = ReadonlyArray; - const defaultFormatHost: ts.FormatDiagnosticsHost = { getCurrentDirectory: () => ts.sys.getCurrentDirectory(), getCanonicalFileName: fileName => fileName, getNewLine: () => ts.sys.newLine }; -function displayFileName(fileName: string, host: ts.FormatDiagnosticsHost): string { - return relative( - resolve(host.getCurrentDirectory()), resolve(host.getCanonicalFileName(fileName))); -} - -export function formatDiagnosticPosition( - position: Position, host: ts.FormatDiagnosticsHost = defaultFormatHost): string { - return `${displayFileName(position.fileName, host)}(${position.line + 1},${position.column + 1})`; -} - -export function flattenDiagnosticMessageChain( - chain: api.DiagnosticMessageChain, host: ts.FormatDiagnosticsHost = defaultFormatHost, - indent = 0): string { - const newLine = host.getNewLine(); - let result = ''; - if (indent) { - result += newLine; - - for (let i = 0; i < indent; i++) { - result += ' '; - } - } - result += chain.messageText; - - const position = chain.position; - // add position if available, and we are not at the depest frame - if (position && indent !== 0) { - result += ` at ${formatDiagnosticPosition(position, host)}`; - } - - indent++; - if (chain.next) { - for (const kid of chain.next) { - result += flattenDiagnosticMessageChain(kid, host, indent); - } - } - return result; -} - -export function formatDiagnostic( - diagnostic: api.Diagnostic, host: ts.FormatDiagnosticsHost = defaultFormatHost) { - let result = ''; - const newLine = host.getNewLine(); - const span = diagnostic.span; - if (span) { - result += `${ - formatDiagnosticPosition( - {fileName: span.start.file.url, line: span.start.line, column: span.start.col}, - host)}: `; - } else if (diagnostic.position) { - result += `${formatDiagnosticPosition(diagnostic.position, host)}: `; - } - if (diagnostic.span && diagnostic.span.details) { - result += `${diagnostic.span.details}, ${diagnostic.messageText}${newLine}`; - } else if (diagnostic.chain) { - result += `${flattenDiagnosticMessageChain(diagnostic.chain, host)}.${newLine}`; - } else { - result += `${diagnostic.messageText}${newLine}`; - } - return result; -} - export function formatDiagnostics( - diags: Diagnostics, host: ts.FormatDiagnosticsHost = defaultFormatHost): string { + diags: ReadonlyArray, + host: ts.FormatDiagnosticsHost = defaultFormatHost): string { if (diags && diags.length) { return diags - .map(diagnostic => { - if (api.isTsDiagnostic(diagnostic)) { - return replaceTsWithNgInErrors( - ts.formatDiagnosticsWithColorAndContext([diagnostic], host)); - } else { - return formatDiagnostic(diagnostic, host); - } - }) + .map( + diagnostic => replaceTsWithNgInErrors( + ts.formatDiagnosticsWithColorAndContext([diagnostic], host))) .join(''); } else { return ''; @@ -264,12 +196,12 @@ function getExtendedConfigPathWorker( } export interface PerformCompilationResult { - diagnostics: Diagnostics; + diagnostics: ReadonlyArray; program?: api.Program; emitResult?: ts.EmitResult; } -export function exitCodeFromResult(diags: Diagnostics|undefined): number { +export function exitCodeFromResult(diags: ReadonlyArray|undefined): number { if (!diags) return 0; if (diags.every((diag) => diag.category !== ts.DiagnosticCategory.Error)) { // If we have a result and didn't get any errors, we succeeded. @@ -298,14 +230,14 @@ export function performCompilation({ oldProgram?: api.Program, emitCallback?: api.TsEmitCallback, mergeEmitResultsCallback?: api.TsMergeEmitResultsCallback, - gatherDiagnostics?: (program: api.Program) => Diagnostics, + gatherDiagnostics?: (program: api.Program) => ReadonlyArray, customTransformers?: api.CustomTransformers, emitFlags?: api.EmitFlags, modifiedResourceFiles?: Set| null, }): PerformCompilationResult { let program: api.Program|undefined; let emitResult: ts.EmitResult|undefined; - let allDiagnostics: Array = []; + let allDiagnostics: Array = []; try { if (!host) { host = ng.createCompilerHost({options}); @@ -344,15 +276,21 @@ export function performCompilation({ program = undefined; code = api.UNKNOWN_ERROR_CODE; } - allDiagnostics.push( - {category: ts.DiagnosticCategory.Error, messageText: errMsg, code, source: api.SOURCE}); + allDiagnostics.push({ + category: ts.DiagnosticCategory.Error, + messageText: errMsg, + code, + file: undefined, + start: undefined, + length: undefined, + }); return {diagnostics: allDiagnostics, program}; } } -export function defaultGatherDiagnostics(program: api.Program): Diagnostics { - const allDiagnostics: Array = []; +export function defaultGatherDiagnostics(program: api.Program): ReadonlyArray { + const allDiagnostics: Array = []; - function checkDiagnostics(diags: Diagnostics|undefined) { + function checkDiagnostics(diags: ReadonlyArray|undefined) { if (diags) { allDiagnostics.push(...diags); return !hasErrors(diags); @@ -367,7 +305,7 @@ export function defaultGatherDiagnostics(program: api.Program): Diagnostics { // Check syntactic diagnostics checkOtherDiagnostics = - checkOtherDiagnostics && checkDiagnostics(program.getTsSyntacticDiagnostics() as Diagnostics); + checkOtherDiagnostics && checkDiagnostics(program.getTsSyntacticDiagnostics()); // Check TypeScript semantic and Angular structure diagnostics checkOtherDiagnostics = @@ -377,11 +315,11 @@ export function defaultGatherDiagnostics(program: api.Program): Diagnostics { // Check Angular semantic diagnostics checkOtherDiagnostics = - checkOtherDiagnostics && checkDiagnostics(program.getNgSemanticDiagnostics() as Diagnostics); + checkOtherDiagnostics && checkDiagnostics(program.getNgSemanticDiagnostics()); return allDiagnostics; } -function hasErrors(diags: Diagnostics) { +function hasErrors(diags: ReadonlyArray) { return diags.some(d => d.category === ts.DiagnosticCategory.Error); } diff --git a/packages/compiler-cli/src/perform_watch.ts b/packages/compiler-cli/src/perform_watch.ts index b2c20afe191..766bff1e27c 100644 --- a/packages/compiler-cli/src/perform_watch.ts +++ b/packages/compiler-cli/src/perform_watch.ts @@ -10,12 +10,12 @@ import * as chokidar from 'chokidar'; import * as path from 'path'; import ts from 'typescript'; -import {Diagnostics, exitCodeFromResult, ParsedConfiguration, performCompilation, PerformCompilationResult, readConfiguration} from './perform_compile'; +import {exitCodeFromResult, ParsedConfiguration, performCompilation, PerformCompilationResult, readConfiguration} from './perform_compile'; import * as api from './transformers/api'; import {createCompilerHost} from './transformers/entry_points'; import {createMessageDiagnostic} from './transformers/util'; -function totalCompilationTimeDiagnostic(timeInMillis: number): api.Diagnostic { +function totalCompilationTimeDiagnostic(timeInMillis: number): ts.Diagnostic { let duration: string; if (timeInMillis > 1000) { duration = `${(timeInMillis / 1000).toPrecision(2)}s`; @@ -27,6 +27,9 @@ function totalCompilationTimeDiagnostic(timeInMillis: number): api.Diagnostic { messageText: `Total time: ${duration}`, code: api.DEFAULT_ERROR_CODE, source: api.SOURCE, + file: undefined, + start: undefined, + length: undefined, }; } @@ -37,7 +40,7 @@ export enum FileChangeEvent { } export interface PerformWatchHost { - reportDiagnostics(diagnostics: Diagnostics): void; + reportDiagnostics(diagnostics: ReadonlyArray): void; readConfiguration(): ParsedConfiguration; createCompilerHost(options: api.CompilerOptions): api.CompilerHost; createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback|undefined; @@ -49,7 +52,7 @@ export interface PerformWatchHost { } export function createPerformWatchHost( - configFileName: string, reportDiagnostics: (diagnostics: Diagnostics) => void, + configFileName: string, reportDiagnostics: (diagnostics: ReadonlyArray) => void, existingOptions?: ts.CompilerOptions, createEmitCallback?: (options: api.CompilerOptions) => api.TsEmitCallback | undefined): PerformWatchHost { @@ -64,7 +67,10 @@ export function createPerformWatchHost( category: ts.DiagnosticCategory.Error, messageText: 'Invalid configuration option. baseDir not specified', source: api.SOURCE, - code: api.DEFAULT_ERROR_CODE + code: api.DEFAULT_ERROR_CODE, + file: undefined, + start: undefined, + length: undefined, }]); return {close: () => {}}; } @@ -112,8 +118,11 @@ interface QueuedCompilationInfo { /** * The logic in this function is adapted from `tsc.ts` from TypeScript. */ -export function performWatchCompilation(host: PerformWatchHost): - {close: () => void, ready: (cb: () => void) => void, firstCompileResult: Diagnostics} { +export function performWatchCompilation(host: PerformWatchHost): { + close: () => void, + ready: (cb: () => void) => void, + firstCompileResult: ReadonlyArray +} { let cachedProgram: api.Program|undefined; // Program cached from last compilation let cachedCompilerHost: api.CompilerHost|undefined; // CompilerHost cached from last compilation let cachedOptions: ParsedConfiguration|undefined; // CompilerOptions cached from last compilation @@ -154,7 +163,7 @@ export function performWatchCompilation(host: PerformWatchHost): } // Invoked to perform initial compilation or re-compilation in watch mode - function doCompilation(): Diagnostics { + function doCompilation(): ReadonlyArray { if (!cachedOptions) { cachedOptions = host.readConfiguration(); } diff --git a/packages/compiler-cli/src/transformers/api.ts b/packages/compiler-cli/src/transformers/api.ts index 0cda16b7ef3..3d07af881b3 100644 --- a/packages/compiler-cli/src/transformers/api.ts +++ b/packages/compiler-cli/src/transformers/api.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {GeneratedFile, ParseSourceSpan, Position} from '@angular/compiler'; import ts from 'typescript'; import {ExtendedTsCompilerHost, NgCompilerOptions} from '../ngtsc/core/api'; @@ -15,30 +14,10 @@ export const DEFAULT_ERROR_CODE = 100; export const UNKNOWN_ERROR_CODE = 500; export const SOURCE = 'angular' as 'angular'; -export interface DiagnosticMessageChain { - messageText: string; - position?: Position; - next?: DiagnosticMessageChain[]; -} - -export interface Diagnostic { - messageText: string; - span?: ParseSourceSpan; - position?: Position; - chain?: DiagnosticMessageChain; - category: ts.DiagnosticCategory; - code: number; - source: 'angular'; -} - export function isTsDiagnostic(diagnostic: any): diagnostic is ts.Diagnostic { return diagnostic != null && diagnostic.source !== 'angular'; } -export function isNgDiagnostic(diagnostic: any): diagnostic is Diagnostic { - return diagnostic != null && diagnostic.source === 'angular'; -} - export interface CompilerOptions extends NgCompilerOptions, ts.CompilerOptions { // NOTE: These comments and aio/content/guides/aot-compiler.md should be kept in sync. @@ -190,12 +169,6 @@ export interface TsMergeEmitResultsCallback { (results: ts.EmitResult[]): ts.EmitResult; } -export interface LibrarySummary { - fileName: string; - text: string; - sourceFile?: ts.SourceFile; -} - export interface LazyRoute { route: string; module: {name: string, filePath: string}; @@ -220,8 +193,7 @@ export interface Program { /** * Retrieve options diagnostics for the Angular options used to create the program. */ - getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken): - ReadonlyArray; + getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken): ReadonlyArray; /** * Retrieve the syntax diagnostics from TypeScript. This is faster than calling @@ -242,7 +214,8 @@ export interface Program { * * Angular structural information is required to produce these diagnostics. */ - getNgStructuralDiagnostics(cancellationToken?: ts.CancellationToken): ReadonlyArray; + getNgStructuralDiagnostics(cancellationToken?: ts.CancellationToken): + ReadonlyArray; /** * Retrieve the semantic diagnostics from TypeScript. This is equivalent to calling @@ -257,7 +230,7 @@ export interface Program { * Angular structural information is required to produce these diagnostics. */ getNgSemanticDiagnostics(fileName?: string, cancellationToken?: ts.CancellationToken): - ReadonlyArray; + ReadonlyArray; /** * Load Angular structural information asynchronously. If this method is not called then the @@ -286,18 +259,6 @@ export interface Program { mergeEmitResultsCallback?: TsMergeEmitResultsCallback }): ts.EmitResult; - /** - * Returns the .d.ts / .ngsummary.json / .ngfactory.d.ts files of libraries that have been emitted - * in this program or previous programs with paths that emulate the fact that these libraries - * have been compiled before with no outDir. - */ - getLibrarySummaries(): Map; - - /** - * @internal - */ - getEmittedGeneratedFiles(): Map; - /** * @internal */ diff --git a/packages/compiler-cli/src/transformers/entry_points.ts b/packages/compiler-cli/src/transformers/entry_points.ts index e3aebde1817..7e6dc95f5da 100644 --- a/packages/compiler-cli/src/transformers/entry_points.ts +++ b/packages/compiler-cli/src/transformers/entry_points.ts @@ -6,9 +6,5 @@ * found in the LICENSE file at https://angular.io/license */ -import ts from 'typescript'; - -import {CompilerHost, CompilerOptions, Program} from './api'; - export {createCompilerHost} from './compiler_host'; export {createProgram} from './program'; diff --git a/packages/compiler-cli/src/transformers/program.ts b/packages/compiler-cli/src/transformers/program.ts index 8c546a81c77..b4c838c4519 100644 --- a/packages/compiler-cli/src/transformers/program.ts +++ b/packages/compiler-cli/src/transformers/program.ts @@ -11,22 +11,11 @@ import {NgtscProgram} from '../ngtsc/program'; import {CompilerHost, CompilerOptions, Program} from './api'; -/** Error message to show when attempting to build View Engine. */ -const VE_DISABLED_MESSAGE = ` -This compilation is using the View Engine compiler which is no longer supported by the Angular team -and is being removed. Please upgrade to the Ivy compiler by switching to \`NgtscProgram\`. See -https://angular.io/guide/ivy for more information. -`.trim().split('\n').join(' '); - export function createProgram({rootNames, options, host, oldProgram}: { rootNames: ReadonlyArray, options: CompilerOptions, host: CompilerHost, oldProgram?: Program }): Program { - if (options.enableIvy !== false) { - return new NgtscProgram(rootNames, options, host, oldProgram as NgtscProgram | undefined); - } else { - throw new Error(VE_DISABLED_MESSAGE); - } + return new NgtscProgram(rootNames, options, host, oldProgram as NgtscProgram | undefined); } diff --git a/packages/compiler-cli/src/transformers/util.ts b/packages/compiler-cli/src/transformers/util.ts index 977250b60fc..1a471695c61 100644 --- a/packages/compiler-cli/src/transformers/util.ts +++ b/packages/compiler-cli/src/transformers/util.ts @@ -8,7 +8,7 @@ import ts from 'typescript'; -import {DEFAULT_ERROR_CODE, Diagnostic, SOURCE} from './api'; +import {CompilerOptions, DEFAULT_ERROR_CODE, SOURCE} from './api'; export const GENERATED_FILES = /(.*?)\.(ngfactory|shim\.ngstyle|ngstyle|ngsummary)\.(js|d\.ts|ts)$/; @@ -16,7 +16,7 @@ export function error(msg: string): never { throw new Error(`Internal error: ${msg}`); } -export function createMessageDiagnostic(messageText: string): ts.Diagnostic&Diagnostic { +export function createMessageDiagnostic(messageText: string): ts.Diagnostic { return { file: undefined, start: undefined, @@ -29,29 +29,10 @@ export function createMessageDiagnostic(messageText: string): ts.Diagnostic&Diag } /** - * Converts a ng.Diagnostic into a ts.Diagnostic. - * This looses some information, and also uses an incomplete object as `file`. + * Strip multiline comment start and end markers from the `commentText` string. * - * I.e. only use this where the API allows only a ts.Diagnostic. + * This will also strip the JSDOC comment start marker (`/**`). */ -export function ngToTsDiagnostic(ng: Diagnostic): ts.Diagnostic { - let file: ts.SourceFile|undefined; - let start: number|undefined; - let length: number|undefined; - if (ng.span) { - // Note: We can't use a real ts.SourceFile, - // but we can at least mirror the properties `fileName` and `text`, which - // are mostly used for error reporting. - file = {fileName: ng.span.start.file.url, text: ng.span.start.file.content} as ts.SourceFile; - start = ng.span.start.offset; - length = ng.span.end.offset - start; - } - return { - file, - messageText: ng.messageText, - category: ng.category, - code: ng.code, - start, - length, - }; +export function stripComment(commentText: string): string { + return commentText.replace(/^\/\*\*?/, '').replace(/\*\/$/, '').trim(); } diff --git a/packages/compiler-cli/test/compliance/test_helpers/compile_test.ts b/packages/compiler-cli/test/compliance/test_helpers/compile_test.ts index ff201d70762..177f1cb9b79 100644 --- a/packages/compiler-cli/test/compliance/test_helpers/compile_test.ts +++ b/packages/compiler-cli/test/compliance/test_helpers/compile_test.ts @@ -10,7 +10,7 @@ import ts from 'typescript'; import {AbsoluteFsPath, FileSystem, PathManipulation, ReadonlyFileSystem} from '../../../src/ngtsc/file_system'; import {initMockFileSystem} from '../../../src/ngtsc/file_system/testing'; import {loadStandardTestFiles, loadTestDirectory, NgtscTestCompilerHost} from '../../../src/ngtsc/testing'; -import {Diagnostics, performCompilation} from '../../../src/perform_compile'; +import {performCompilation} from '../../../src/perform_compile'; import {CompilerOptions} from '../../../src/transformers/api'; import {ConfigOptions} from './get_compliance_tests'; @@ -150,7 +150,7 @@ function monkeyPatchReadFile(fs: ReadonlyFileSystem): void { * * @param diagnostics The diagnostics to parse. */ -function parseDiagnostics(diagnostics: Diagnostics): string[] { +function parseDiagnostics(diagnostics: readonly ts.Diagnostic[]): string[] { return diagnostics.map(diagnostic => { const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'); if ('file' in diagnostic && diagnostic.file !== undefined && diagnostic.start !== undefined) { diff --git a/packages/compiler-cli/test/ngc_spec.ts b/packages/compiler-cli/test/ngc_spec.ts deleted file mode 100644 index f985679a6d0..00000000000 --- a/packages/compiler-cli/test/ngc_spec.ts +++ /dev/null @@ -1,2438 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 fs from 'fs'; -import * as path from 'path'; -import * as tsickle from 'tsickle'; -import ts from 'typescript'; - -import {main, mainDiagnosticsForTest, readCommandLineAndConfiguration, watchMode} from '../src/main'; - -import {setup, stripAnsi} from './test_support'; - -describe('ngc transformer command-line', () => { - let basePath: string; - let outDir: string; - let write: (fileName: string, content: string) => void; - let errorSpy: jasmine.Spy&((s: string) => void); - - function shouldExist(fileName: string) { - if (!fs.existsSync(path.resolve(outDir, fileName))) { - throw new Error(`Expected ${fileName} to be emitted (outDir: ${outDir})`); - } - } - - function shouldNotExist(fileName: string) { - if (fs.existsSync(path.resolve(outDir, fileName))) { - throw new Error(`Did not expect ${fileName} to be emitted (outDir: ${outDir})`); - } - } - - function writeConfig(tsconfig: string = '{"extends": "./tsconfig-base.json"}') { - write('tsconfig.json', tsconfig); - } - - beforeEach(() => { - errorSpy = jasmine.createSpy('consoleError').and.callFake(console.error); - const support = setup(); - basePath = support.basePath; - outDir = path.join(basePath, 'built'); - process.chdir(basePath); - write = (fileName: string, content: string) => { - support.write(fileName, content); - }; - - write('tsconfig-base.json', `{ - "compilerOptions": { - "experimentalDecorators": true, - "skipLibCheck": true, - "noImplicitAny": true, - "types": [], - "outDir": "built", - "rootDir": ".", - "baseUrl": ".", - "declaration": true, - "target": "es5", - "newLine": "lf", - "module": "es2015", - "moduleResolution": "node", - "lib": ["es6", "dom"], - "typeRoots": ["node_modules/@types"] - }, - "angularCompilerOptions": { - "enableIvy": false - } - }`); - }); - - it('should compile without errors', () => { - writeConfig(); - write('test.ts', 'export const A = 1;'); - - const exitCode = main(['-p', basePath], errorSpy); - expect(errorSpy).not.toHaveBeenCalled(); - expect(exitCode).toBe(0); - }); - - it('should respect the "newLine" compiler option when printing diagnostics', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "compilerOptions": { - "newLine": "CRLF", - } - }`); - write('test.ts', 'export NOT_VALID = true;'); - - // Stub the error spy because we don't want to call through and print the - // expected error diagnostic. - errorSpy.and.stub(); - - const exitCode = main(['-p', basePath], errorSpy); - const errorText = stripAnsi(errorSpy.calls.mostRecent().args[0]); - expect(errorText).toContain( - `test.ts:1:1 - error TS1128: Declaration or statement expected.\r\n`); - expect(exitCode).toBe(1); - }); - - describe('decorator metadata', () => { - it('should add metadata as decorators if "annotationsAs" is set to "decorators"', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "compilerOptions": { - "emitDecoratorMetadata": true - }, - "angularCompilerOptions": { - "annotationsAs": "decorators" - }, - "files": ["mymodule.ts"] - }`); - write('aclass.ts', `export class AClass {}`); - write('mymodule.ts', ` - import {NgModule} from '@angular/core'; - import {AClass} from './aclass'; - - @NgModule({declarations: []}) - export class MyModule { - constructor(importedClass: AClass) {} - } - `); - - const exitCode = main(['-p', basePath], errorSpy); - expect(exitCode).toEqual(0); - - const mymodulejs = path.resolve(outDir, 'mymodule.js'); - const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); - expect(mymoduleSource).toContain('MyModule = __decorate(['); - expect(mymoduleSource).toContain(`import { AClass } from './aclass';`); - expect(mymoduleSource).toContain(`__metadata("design:paramtypes", [AClass])`); - expect(mymoduleSource).not.toContain('MyModule.ctorParameters'); - expect(mymoduleSource).not.toContain('MyModule.decorators'); - }); - - it('should add metadata for Angular-decorated classes as static fields', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "files": ["mymodule.ts"] - }`); - write('aclass.ts', `export class AClass {}`); - write('mymodule.ts', ` - import {NgModule} from '@angular/core'; - import {AClass} from './aclass'; - - @NgModule({declarations: []}) - export class MyModule { - constructor(importedClass: AClass) {} - } - `); - - const exitCode = main(['-p', basePath], errorSpy); - expect(exitCode).toEqual(0); - - const mymodulejs = path.resolve(outDir, 'mymodule.js'); - const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); - expect(mymoduleSource).not.toContain('__decorate'); - expect(mymoduleSource).toContain('args: [{ declarations: [] },] }'); - expect(mymoduleSource).not.toContain(`__metadata`); - expect(mymoduleSource).toContain(`import { AClass } from './aclass';`); - expect(mymoduleSource).toContain(`{ type: AClass }`); - }); - - it('should not downlevel decorators for classes with custom decorators', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "files": ["mymodule.ts"] - }`); - write('aclass.ts', `export class AClass {}`); - write('decorator.ts', ` - export function CustomDecorator(metadata: any) { - return (...args: any[]) => {} - } - `); - write('mymodule.ts', ` - import {AClass} from './aclass'; - import {CustomDecorator} from './decorator'; - - @CustomDecorator({declarations: []}) - export class MyModule { - constructor(importedClass: AClass) {} - } - `); - - const exitCode = main(['-p', basePath], errorSpy); - expect(exitCode).toEqual(0); - - const mymodulejs = path.resolve(outDir, 'mymodule.js'); - const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); - expect(mymoduleSource).toContain('__decorate'); - expect(mymoduleSource).toContain('({ declarations: [] })'); - expect(mymoduleSource).not.toContain('AClass'); - expect(mymoduleSource).not.toContain('.ctorParameters ='); - expect(mymoduleSource).not.toContain('.decorators = '); - }); - }); - - describe('errors', () => { - beforeEach(() => { - errorSpy.and.stub(); - }); - - it('should not print the stack trace if user input file does not exist', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "files": ["test.ts"] - }`); - - const exitCode = main(['-p', basePath], errorSpy); - const errorText = stripAnsi(errorSpy.calls.mostRecent().args[0]); - expect(errorText).toContain( - `error TS6053: File '` + path.posix.join(basePath, 'test.ts') + `' not found.` + - '\n'); - expect(exitCode).toEqual(1); - }); - - it('should not print the stack trace if user input file is malformed', () => { - writeConfig(); - write('test.ts', 'foo;'); - - const exitCode = main(['-p', basePath], errorSpy); - const errorText = stripAnsi(errorSpy.calls.mostRecent().args[0]); - expect(errorText).toContain( - `test.ts:1:1 - error TS2304: Cannot find name 'foo'.` + - '\n'); - expect(exitCode).toEqual(1); - }); - - it('should not print the stack trace if cannot find the imported module', () => { - writeConfig(); - write('test.ts', `import {MyClass} from './not-exist-deps';`); - - const exitCode = main(['-p', basePath], errorSpy); - const errorText = stripAnsi(errorSpy.calls.mostRecent().args[0]); - expect(errorText).toContain( - `test.ts:1:23 - error TS2307: Cannot find module './not-exist-deps' or its corresponding type declarations.` + - '\n'); - expect(exitCode).toEqual(1); - }); - - it('should not print the stack trace if cannot import', () => { - writeConfig(); - write('empty-deps.ts', 'export const A = 1;'); - write('test.ts', `import {MyClass} from './empty-deps';`); - - const exitCode = main(['-p', basePath], errorSpy); - const errorText = stripAnsi(errorSpy.calls.mostRecent().args[0]); - expect(errorText).toContain( - `test.ts:1:9 - error TS2305: Module '"./empty-deps"' has no exported member 'MyClass'.\n`); - expect(exitCode).toEqual(1); - }); - - it('should not print the stack trace if type mismatches', () => { - writeConfig(); - write('empty-deps.ts', 'export const A = "abc";'); - write('test.ts', ` - import {A} from './empty-deps'; - A(); - `); - - const exitCode = main(['-p', basePath], errorSpy); - const errorText = stripAnsi(errorSpy.calls.mostRecent().args[0]); - expect(errorText).toContain( - 'test.ts:3:9 - error TS2349: This expression is not callable.\n' + - ' Type \'String\' has no call signatures.\n'); - expect(exitCode).toEqual(1); - }); - - it('should print the stack trace on compiler internal errors', () => { - write('test.ts', 'export const A = 1;'); - - const exitCode = main(['-p', 'not-exist'], errorSpy); - expect(errorSpy).toHaveBeenCalledTimes(1); - expect(errorSpy.calls.mostRecent().args[0]).toContain('no such file or directory'); - expect(errorSpy.calls.mostRecent().args[0]).toMatch(/at Object\.(fs\.)?lstatSync/); - expect(exitCode).toEqual(2); - }); - - it('should report errors for ngfactory files that are not referenced by root files', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "files": ["mymodule.ts"] - }`); - write('mymodule.ts', ` - import {NgModule, Component} from '@angular/core'; - - @Component({template: '{{unknownProp}}'}) - export class MyComp {} - - @NgModule({declarations: [MyComp]}) - export class MyModule {} - `); - - const exitCode = main(['-p', basePath], errorSpy); - expect(errorSpy).toHaveBeenCalledTimes(1); - expect(errorSpy.calls.mostRecent().args[0]).toContain('mymodule.ts.MyComp.html'); - expect(errorSpy.calls.mostRecent().args[0]) - .toContain(`Property 'unknownProp' does not exist on type 'MyComp'`); - - expect(exitCode).toEqual(1); - }); - - it('should report errors as coming from the html file, not the factory', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "files": ["mymodule.ts"] - }`); - write('my.component.ts', ` - import {Component} from '@angular/core'; - @Component({templateUrl: './my.component.html'}) - export class MyComp {} - `); - write('my.component.html', `

- {{unknownProp}} -

`); - write('mymodule.ts', ` - import {NgModule} from '@angular/core'; - import {MyComp} from './my.component'; - - @NgModule({declarations: [MyComp]}) - export class MyModule {} - `); - - const exitCode = main(['-p', basePath], errorSpy); - expect(errorSpy).toHaveBeenCalledTimes(1); - expect(errorSpy.calls.mostRecent().args[0]).toContain('my.component.html(1,5):'); - expect(errorSpy.calls.mostRecent().args[0]) - .toContain(`Property 'unknownProp' does not exist on type 'MyComp'`); - - expect(exitCode).toEqual(1); - }); - }); - - it('should give a specific error when an Angular Ivy NgModule is imported', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "files": ["mymodule.ts"] - }`); - write('node_modules/test/index.d.ts', ` - export declare class FooModule { - static ɵmod = null; - } - `); - write('mymodule.ts', ` - import {NgModule} from '@angular/core'; - import {FooModule} from 'test'; - - @NgModule({ - imports: [FooModule], - }) - export class TestModule {} - `); - - const exitCode = main(['-p', basePath], errorSpy); - expect(errorSpy).toHaveBeenCalledTimes(1); - const message = errorSpy.calls.mostRecent().args[0]; - - // The error message should mention Ivy specifically. - expect(message).toContain('Angular Ivy'); - }); - - describe('compile ngfactory files', () => { - it('should compile ngfactory files that are not referenced by root files', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "files": ["mymodule.ts"] - }`); - write('mymodule.ts', ` - import {CommonModule} from '@angular/common'; - import {NgModule} from '@angular/core'; - - @NgModule({ - imports: [CommonModule] - }) - export class MyModule {} - `); - - const exitCode = main(['-p', basePath], errorSpy); - expect(exitCode).toEqual(0); - - expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngfactory.js'))).toBe(true); - expect(fs.existsSync( - path.resolve(outDir, 'node_modules', '@angular', 'core', 'core.ngfactory.js'))) - .toBe(true); - }); - - describe('comments', () => { - function compileAndRead(contents: string) { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "files": ["mymodule.ts"], - "angularCompilerOptions": {"allowEmptyCodegenFiles": true} - }`); - write('mymodule.ts', contents); - - const exitCode = main(['-p', basePath], errorSpy); - expect(exitCode).toEqual(0); - - const modPath = path.resolve(outDir, 'mymodule.ngfactory.js'); - expect(fs.existsSync(modPath)).toBe(true); - return fs.readFileSync(modPath, {encoding: 'UTF-8'}); - } - - it('should be added', () => { - const contents = compileAndRead(` - import {CommonModule} from '@angular/common'; - import {NgModule} from '@angular/core'; - - @NgModule({ - imports: [CommonModule] - }) - export class MyModule {} - `); - expect(contents).toContain( - '/**\n * @fileoverview This file was generated by the Angular template compiler. Do not edit.'); - expect(contents).toContain('\n * @suppress {suspiciousCode'); - }); - - it('should be merged with existing fileoverview comments', () => { - const contents = compileAndRead(`/**\n * @fileoverview Hello world.\n */ - - import {CommonModule} from '@angular/common'; - import {NgModule} from '@angular/core'; - - @NgModule({ - imports: [CommonModule] - }) - export class MyModule {} - `); - expect(contents).toContain('\n * @fileoverview Hello world.\n'); - }); - - it('should only pick file comments', () => { - const contents = compileAndRead(` - /** Comment on class. */ - class MyClass { - - } - `); - expect(contents).toContain('@fileoverview'); - expect(contents).not.toContain('Comment on class.'); - }); - - it('should not be merged with @license comments', () => { - const contents = compileAndRead(`/** @license Some license. */ - - import {CommonModule} from '@angular/common'; - import {NgModule} from '@angular/core'; - - @NgModule({ - imports: [CommonModule] - }) - export class MyModule {} - `); - expect(contents).toContain('@fileoverview'); - expect(contents).not.toContain('@license'); - }); - - it('should be included in empty files', () => { - const contents = compileAndRead(`/** My comment. */ - - import {Inject, Injectable, Optional} from '@angular/core'; - - @Injectable() - export class NotAnAngularComponent {} - `); - expect(contents).toContain('My comment'); - }); - }); - - it('should compile with an explicit tsconfig reference', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "files": ["mymodule.ts"] - }`); - write('mymodule.ts', ` - import {CommonModule} from '@angular/common'; - import {NgModule} from '@angular/core'; - - @NgModule({ - imports: [CommonModule] - }) - export class MyModule {} - `); - - const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy); - expect(exitCode).toEqual(0); - expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngfactory.js'))).toBe(true); - expect(fs.existsSync( - path.resolve(outDir, 'node_modules', '@angular', 'core', 'core.ngfactory.js'))) - .toBe(true); - }); - - describe(`emit generated files depending on the source file`, () => { - const modules = ['comp', 'directive', 'module']; - beforeEach(() => { - write('src/comp.ts', ` - import {Component, ViewEncapsulation} from '@angular/core'; - - @Component({ - selector: 'comp-a', - template: 'A', - styleUrls: ['plain.css'], - encapsulation: ViewEncapsulation.None - }) - export class CompA { - } - - @Component({ - selector: 'comp-b', - template: 'B', - styleUrls: ['emulated.css'] - }) - export class CompB { - }`); - write('src/plain.css', 'div {}'); - write('src/emulated.css', 'div {}'); - write('src/directive.ts', ` - import {Directive, Input} from '@angular/core'; - - @Directive({ - selector: '[someDir]', - host: {'[title]': 'someProp'}, - }) - export class SomeDirective { - @Input() someProp: string; - }`); - write('src/module.ts', ` - import {NgModule} from '@angular/core'; - - import {CompA, CompB} from './comp'; - import {SomeDirective} from './directive'; - - @NgModule({ - declarations: [ - CompA, CompB, - SomeDirective, - ], - exports: [ - CompA, CompB, - SomeDirective, - ], - }) - export class SomeModule { - }`); - }); - - function expectJsDtsMetadataJsonToExist() { - modules.forEach(moduleName => { - shouldExist(moduleName + '.js'); - shouldExist(moduleName + '.d.ts'); - shouldExist(moduleName + '.metadata.json'); - }); - } - - function expectAllGeneratedFilesToExist(enableSummariesForJit = true) { - modules.forEach(moduleName => { - if (/module|comp/.test(moduleName)) { - shouldExist(moduleName + '.ngfactory.js'); - shouldExist(moduleName + '.ngfactory.d.ts'); - } else { - shouldNotExist(moduleName + '.ngfactory.js'); - shouldNotExist(moduleName + '.ngfactory.d.ts'); - } - if (enableSummariesForJit) { - shouldExist(moduleName + '.ngsummary.js'); - shouldExist(moduleName + '.ngsummary.d.ts'); - } else { - shouldNotExist(moduleName + '.ngsummary.js'); - shouldNotExist(moduleName + '.ngsummary.d.ts'); - } - shouldExist(moduleName + '.ngsummary.json'); - shouldNotExist(moduleName + '.ngfactory.metadata.json'); - shouldNotExist(moduleName + '.ngsummary.metadata.json'); - }); - shouldExist('plain.css.ngstyle.js'); - shouldExist('plain.css.ngstyle.d.ts'); - shouldExist('emulated.css.shim.ngstyle.js'); - shouldExist('emulated.css.shim.ngstyle.d.ts'); - } - - it('should emit generated files from sources with summariesForJit', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "angularCompilerOptions": { - "enableSummariesForJit": true - }, - "include": ["src/**/*.ts"] - }`); - const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy); - expect(exitCode).toEqual(0); - outDir = path.resolve(basePath, 'built', 'src'); - expectJsDtsMetadataJsonToExist(); - expectAllGeneratedFilesToExist(true); - }); - - it('should not emit generated files from sources without summariesForJit', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "angularCompilerOptions": { - "enableSummariesForJit": false - }, - "include": ["src/**/*.ts"] - }`); - const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy); - expect(exitCode).toEqual(0); - outDir = path.resolve(basePath, 'built', 'src'); - expectJsDtsMetadataJsonToExist(); - expectAllGeneratedFilesToExist(false); - }); - - it('should emit generated files from libraries', () => { - // first only generate .d.ts / .js / .metadata.json files - writeConfig(`{ - "extends": "./tsconfig-base.json", - "angularCompilerOptions": { - "skipTemplateCodegen": true - }, - "compilerOptions": { - "outDir": "lib" - }, - "include": ["src/**/*.ts"] - }`); - let exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy); - expect(exitCode).toEqual(0); - outDir = path.resolve(basePath, 'lib', 'src'); - modules.forEach(moduleName => { - shouldExist(moduleName + '.js'); - shouldExist(moduleName + '.d.ts'); - shouldExist(moduleName + '.metadata.json'); - shouldNotExist(moduleName + '.ngfactory.js'); - shouldNotExist(moduleName + '.ngfactory.d.ts'); - shouldNotExist(moduleName + '.ngsummary.js'); - shouldNotExist(moduleName + '.ngsummary.d.ts'); - shouldNotExist(moduleName + '.ngsummary.json'); - shouldNotExist(moduleName + '.ngfactory.metadata.json'); - shouldNotExist(moduleName + '.ngsummary.metadata.json'); - }); - shouldNotExist('src/plain.css.ngstyle.js'); - shouldNotExist('src/plain.css.ngstyle.d.ts'); - shouldNotExist('src/emulated.css.shim.ngstyle.js'); - shouldNotExist('src/emulated.css.shim.ngstyle.d.ts'); - // Then compile again, using the previous .metadata.json as input. - writeConfig(`{ - "extends": "./tsconfig-base.json", - "angularCompilerOptions": { - "skipTemplateCodegen": false, - "enableSummariesForJit": true - }, - "compilerOptions": { - "outDir": "built" - }, - "include": ["lib/**/*.d.ts"] - }`); - write('lib/src/plain.css', 'div {}'); - write('lib/src/emulated.css', 'div {}'); - exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy); - expect(exitCode).toEqual(0); - outDir = path.resolve(basePath, 'built', 'lib', 'src'); - expectAllGeneratedFilesToExist(); - }); - }); - - describe('closure', () => { - it('should not run tsickle by default', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "files": ["mymodule.ts"], - }`); - write('mymodule.ts', ` - import {NgModule, Component} from '@angular/core'; - - @Component({template: ''}) - export class MyComp {} - - @NgModule({declarations: [MyComp]}) - export class MyModule {} - `); - - const exitCode = - main(['-p', basePath], errorSpy, undefined, undefined, undefined, undefined, tsickle); - expect(exitCode).toEqual(0); - - const mymodulejs = path.resolve(outDir, 'mymodule.js'); - const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); - expect(mymoduleSource).not.toContain('@fileoverview added by tsickle'); - }); - - it('should add closure annotations', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "angularCompilerOptions": { - "annotateForClosureCompiler": true - }, - "files": ["mymodule.ts"] - }`); - write('mymodule.ts', ` - import {NgModule, Component, Injectable} from '@angular/core'; - - @Injectable() - export class InjectedClass {} - - @Component({template: ''}) - export class MyComp { - constructor(injected: InjectedClass) {} - fn(p: any) {} - } - - @NgModule({declarations: [MyComp]}) - export class MyModule {} - `); - - const exitCode = - main(['-p', basePath], errorSpy, undefined, undefined, undefined, undefined, tsickle); - expect(exitCode).toEqual(0); - - const mymodulejs = path.resolve(outDir, 'mymodule.js'); - const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); - expect(mymoduleSource).toContain('@fileoverview added by tsickle'); - expect(mymoduleSource).toContain('@param {?} p'); - expect(mymoduleSource).toContain('@nocollapse'); - }); - }); - - it('should not rewrite imports when annotating with closure', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "compilerOptions": { - "paths": { - "submodule": ["./src/submodule/public_api.ts"] - } - }, - "angularCompilerOptions": { - "annotateForClosureCompiler": true - }, - "files": ["mymodule.ts"] - }`); - write('src/test.txt', ' '); - write('src/submodule/public_api.ts', ` - export const A = 1; - `); - write('mymodule.ts', ` - import {NgModule, Component} from '@angular/core'; - import {A} from 'submodule'; - - @Component({template: ''}) - export class MyComp { - fn(p: any) { return A; } - } - - @NgModule({declarations: [MyComp]}) - export class MyModule {} - `); - - const exitCode = - main(['-p', basePath], errorSpy, undefined, undefined, undefined, undefined, tsickle); - expect(exitCode).toEqual(0); - const mymodulejs = path.resolve(outDir, 'mymodule.js'); - const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); - expect(mymoduleSource).toContain(`import { A } from 'submodule'`); - }); - - describe('expression lowering', () => { - beforeEach(() => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "files": ["mymodule.ts"] - }`); - }); - - function compile(): number { - errorSpy.calls.reset(); - const result = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy); - expect(errorSpy).not.toHaveBeenCalled(); - return result; - } - - it('should be able to lower a lambda expression in a provider', () => { - write('mymodule.ts', ` - import {CommonModule} from '@angular/common'; - import {NgModule} from '@angular/core'; - - class Foo {} - - @NgModule({ - imports: [CommonModule], - providers: [{provide: 'someToken', useFactory: () => new Foo()}] - }) - export class MyModule {} - `); - expect(compile()).toEqual(0); - - const mymodulejs = path.resolve(outDir, 'mymodule.js'); - const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); - expect(mymoduleSource).toContain('var ɵ0 = function () { return new Foo(); }'); - expect(mymoduleSource).toContain('export { ɵ0'); - - const mymodulefactory = path.resolve(outDir, 'mymodule.ngfactory.js'); - const mymodulefactorySource = fs.readFileSync(mymodulefactory, 'utf8'); - expect(mymodulefactorySource).toContain('"someToken", i1.ɵ0'); - }); - - it('should be able to lower a function expression in a provider', () => { - write('mymodule.ts', ` - import {CommonModule} from '@angular/common'; - import {NgModule} from '@angular/core'; - - class Foo {} - - @NgModule({ - imports: [CommonModule], - providers: [{provide: 'someToken', useFactory: function() {return new Foo();}}] - }) - export class MyModule {} - `); - expect(compile()).toEqual(0); - - const mymodulejs = path.resolve(outDir, 'mymodule.js'); - const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); - expect(mymoduleSource).toContain('var ɵ0 = function () { return new Foo(); }'); - expect(mymoduleSource).toContain('export { ɵ0'); - - const mymodulefactory = path.resolve(outDir, 'mymodule.ngfactory.js'); - const mymodulefactorySource = fs.readFileSync(mymodulefactory, 'utf8'); - expect(mymodulefactorySource).toContain('"someToken", i1.ɵ0'); - }); - - it('should able to lower multiple expressions', () => { - write('mymodule.ts', ` - import {CommonModule} from '@angular/common'; - import {NgModule} from '@angular/core'; - - class Foo {} - - @NgModule({ - imports: [CommonModule], - providers: [ - {provide: 'someToken', useFactory: () => new Foo()}, - {provide: 'someToken', useFactory: () => new Foo()}, - {provide: 'someToken', useFactory: () => new Foo()}, - {provide: 'someToken', useFactory: () => new Foo()} - ] - }) - export class MyModule {} - `); - expect(compile()).toEqual(0); - const mymodulejs = path.resolve(outDir, 'mymodule.js'); - const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); - expect(mymoduleSource).toContain('ɵ0 = function () { return new Foo(); }'); - expect(mymoduleSource).toContain('ɵ1 = function () { return new Foo(); }'); - expect(mymoduleSource).toContain('ɵ2 = function () { return new Foo(); }'); - expect(mymoduleSource).toContain('ɵ3 = function () { return new Foo(); }'); - expect(mymoduleSource).toContain('export { ɵ0, ɵ1, ɵ2, ɵ3'); - }); - - it('should be able to lower an indirect expression', () => { - write('mymodule.ts', ` - import {CommonModule} from '@angular/common'; - import {NgModule} from '@angular/core'; - - class Foo {} - - const factory = () => new Foo(); - - @NgModule({ - imports: [CommonModule], - providers: [{provide: 'someToken', useFactory: factory}] - }) - export class MyModule {} - `); - expect(compile()).toEqual(0, 'Compile failed'); - - const mymodulejs = path.resolve(outDir, 'mymodule.js'); - const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); - expect(mymoduleSource).toContain('var factory = function () { return new Foo(); }'); - expect(mymoduleSource).toContain('var ɵ0 = factory;'); - expect(mymoduleSource).toContain('export { ɵ0 };'); - }); - - it('should not lower a lambda that is already exported', () => { - write('mymodule.ts', ` - import {CommonModule} from '@angular/common'; - import {NgModule} from '@angular/core'; - - export class Foo {} - - export const factory = () => new Foo(); - - @NgModule({ - imports: [CommonModule], - providers: [{provide: 'someToken', useFactory: factory}] - }) - export class MyModule {} - `); - expect(compile()).toEqual(0); - - const mymodulejs = path.resolve(outDir, 'mymodule.js'); - const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); - expect(mymoduleSource).not.toContain('ɵ0'); - }); - - it('should lower an NgModule id', () => { - write('mymodule.ts', ` - import {NgModule} from '@angular/core'; - - @NgModule({ - id: (() => 'test')(), - }) - export class MyModule {} - `); - expect(compile()).toEqual(0); - - const mymodulejs = path.resolve(outDir, 'mymodule.js'); - const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); - expect(mymoduleSource).toContain('id: ɵ0'); - expect(mymoduleSource).toMatch(/ɵ0 = .*'test'/); - }); - - it('should lower loadChildren', () => { - write('mymodule.ts', ` - import {Component, NgModule} from '@angular/core'; - import {RouterModule} from '@angular/router'; - - export function foo(): any { - console.log('side-effect'); - return 'test'; - } - - @Component({ - selector: 'route', - template: 'route', - }) - export class Route {} - - @NgModule({ - declarations: [Route], - imports: [ - RouterModule.forRoot([ - {path: '', pathMatch: 'full', component: Route, loadChildren: foo()} - ]), - ] - }) - export class MyModule {} - `); - expect(compile()).toEqual(0); - - const mymodulejs = path.resolve(outDir, 'mymodule.js'); - const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); - expect(mymoduleSource).toContain('loadChildren: ɵ0'); - expect(mymoduleSource).toMatch(/ɵ0 = .*foo\(\)/); - }); - - it('should lower loadChildren in an exported variable expression', () => { - write('mymodule.ts', ` - import {Component, NgModule} from '@angular/core'; - import {RouterModule} from '@angular/router'; - - export function foo(): any { - console.log('side-effect'); - return 'test'; - } - - @Component({ - selector: 'route', - template: 'route', - }) - export class Route {} - - export const routes = [ - {path: '', pathMatch: 'full', component: Route, loadChildren: foo()} - ]; - - @NgModule({ - declarations: [Route], - imports: [ - RouterModule.forRoot(routes), - ] - }) - export class MyModule {} - `); - expect(compile()).toEqual(0); - - const mymodulejs = path.resolve(outDir, 'mymodule.js'); - const mymoduleSource = fs.readFileSync(mymodulejs, 'utf8'); - expect(mymoduleSource).toContain('loadChildren: ɵ0'); - expect(mymoduleSource).toMatch(/ɵ0 = .*foo\(\)/); - }); - - it('should be able to lower supported expressions', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "files": ["module.ts"] - }`); - write('module.ts', ` - import {NgModule, InjectionToken} from '@angular/core'; - import {AppComponent} from './app'; - - export interface Info { - route: string; - data: string; - } - - export const T1 = new InjectionToken('t1'); - export const T2 = new InjectionToken('t2'); - export const T3 = new InjectionToken('t3'); - export const T4 = new InjectionToken('t4'); - - enum SomeEnum { - OK, - Cancel - } - - function calculateString() { - return 'someValue'; - } - - const routeLikeData = [{ - route: '/home', - data: calculateString() - }]; - - @NgModule({ - declarations: [AppComponent], - providers: [ - { provide: T1, useValue: calculateString() }, - { provide: T2, useFactory: () => 'someValue' }, - { provide: T3, useValue: SomeEnum.OK }, - { provide: T4, useValue: routeLikeData } - ] - }) - export class MyModule {} - `); - write('app.ts', ` - import {Component, Inject} from '@angular/core'; - import * as m from './module'; - - @Component({ - selector: 'my-app', - template: '' - }) - export class AppComponent { - constructor( - @Inject(m.T1) private t1: string, - @Inject(m.T2) private t2: string, - @Inject(m.T3) private t3: number, - @Inject(m.T4) private t4: m.Info[], - ) {} - } - `); - - expect(main(['-p', basePath], errorSpy)).toBe(0); - shouldExist('module.js'); - }); - - it('should allow to use lowering with export *', () => { - write('mymodule.ts', ` - import {NgModule} from '@angular/core'; - - export * from './util'; - - // Note: the lambda will be lowered into an exported expression - @NgModule({providers: [{provide: 'aToken', useValue: () => 2}]}) - export class MyModule {} - `); - write('util.ts', ` - // Note: The lambda will be lowered into an exported expression - const x = () => 2; - - export const y = x; - `); - - expect(compile()).toEqual(0); - - const mymoduleSource = fs.readFileSync(path.resolve(outDir, 'mymodule.js'), 'utf8'); - expect(mymoduleSource).toContain('ɵ0'); - - const utilSource = fs.readFileSync(path.resolve(outDir, 'util.js'), 'utf8'); - expect(utilSource).toContain('ɵ0'); - - const mymoduleNgFactoryJs = - fs.readFileSync(path.resolve(outDir, 'mymodule.ngfactory.js'), 'utf8'); - // check that the generated code refers to ɵ0 from mymodule, and not from util! - expect(mymoduleNgFactoryJs).toContain(`import * as i1 from "./mymodule"`); - expect(mymoduleNgFactoryJs).toContain(`"aToken", i1.ɵ0`); - }); - }); - - function writeFlatModule(outFile: string) { - writeConfig(` - { - "extends": "./tsconfig-base.json", - "angularCompilerOptions": { - "flatModuleId": "flat_module", - "flatModuleOutFile": "${outFile}", - "skipTemplateCodegen": true, - "enableResourceInlining": true - }, - "files": ["public-api.ts"] - } - `); - write('public-api.ts', ` - export * from './src/flat.component'; - export * from './src/flat.module';`); - write('src/flat.component.html', '
flat module component
'); - write('src/flat.component.ts', ` - import {Component} from '@angular/core'; - - @Component({ - selector: 'flat-comp', - templateUrl: 'flat.component.html', - }) - export class FlatComponent { - }`); - write('src/flat.module.ts', ` - import {NgModule} from '@angular/core'; - - import {FlatComponent} from './flat.component'; - - @NgModule({ - declarations: [ - FlatComponent, - ], - exports: [ - FlatComponent, - ], - }) - export class FlatModule { - }`); - } - - it('should be able to generate a flat module library', () => { - writeFlatModule('index.js'); - - const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy); - expect(exitCode).toEqual(0); - shouldExist('index.js'); - shouldExist('index.metadata.json'); - }); - - it('should downlevel templates in flat module metadata', () => { - writeFlatModule('index.js'); - - const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy); - expect(exitCode).toEqual(0); - shouldExist('index.js'); - shouldExist('index.metadata.json'); - - const metadataPath = path.resolve(outDir, 'index.metadata.json'); - const metadataSource = fs.readFileSync(metadataPath, 'utf8'); - expect(metadataSource).not.toContain('templateUrl'); - expect(metadataSource).toContain('
flat module component
'); - }); - - describe('with tree example', () => { - beforeEach(() => { - writeConfig(); - write('index_aot.ts', ` - import {enableProdMode} from '@angular/core'; - import {platformBrowser} from '@angular/platform-browser'; - - import {AppModuleNgFactory} from './tree.ngfactory'; - - enableProdMode(); - platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);`); - write('tree.ts', ` - import {Component, NgModule} from '@angular/core'; - import {CommonModule} from '@angular/common'; - - @Component({ - selector: 'tree', - inputs: ['data'], - template: - \` {{data.value}} \` - }) - export class TreeComponent { - data: any; - bgColor = 0; - } - - @NgModule({imports: [CommonModule], bootstrap: [TreeComponent], declarations: [TreeComponent]}) - export class AppModule {} - `); - }); - - it('should compile without error', () => { - expect(main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy)).toBe(0); - }); - }); - - describe('with external symbol re-exports enabled', () => { - it('should be able to compile multiple libraries with summaries', () => { - // Note: we need to emit the generated code for the libraries - // into the node_modules, as that is the only way that we - // currently support when using summaries. - // TODO(tbosch): add support for `paths` to our CompilerHost.fileNameToModuleName - // and then use `paths` here instead of writing to node_modules. - - // Angular - write('tsconfig-ng.json', `{ - "extends": "./tsconfig-base.json", - "angularCompilerOptions": { - "generateCodeForLibraries": true, - "enableSummariesForJit": true - }, - "compilerOptions": { - "outDir": "." - }, - "include": ["node_modules/@angular/core/**/*"], - "exclude": [ - "node_modules/@angular/core/test/**", - "node_modules/@angular/core/testing/**" - ] - }`); - - // Lib 1 - write('lib1/tsconfig-lib1.json', `{ - "extends": "../tsconfig-base.json", - "angularCompilerOptions": { - "generateCodeForLibraries": false, - "enableSummariesForJit": true, - "createExternalSymbolFactoryReexports": true - }, - "compilerOptions": { - "rootDir": ".", - "outDir": "../node_modules/lib1_built" - } - }`); - write('lib1/module.ts', ` - import {NgModule} from '@angular/core'; - export function someFactory(): any { return null; } - @NgModule({ - providers: [{provide: 'foo', useFactory: someFactory}] - }) - export class Module {} - `); - write('lib1/class1.ts', `export class Class1 {}`); - - // Lib 2 - write('lib2/tsconfig-lib2.json', `{ - "extends": "../tsconfig-base.json", - "angularCompilerOptions": { - "generateCodeForLibraries": false, - "enableSummariesForJit": true, - "createExternalSymbolFactoryReexports": true - }, - "compilerOptions": { - "rootDir": ".", - "outDir": "../node_modules/lib2_built" - } - }`); - write('lib2/module.ts', ` - export {Module} from 'lib1_built/module'; - `); - write('lib2/class2.ts', ` - import {Class1} from 'lib1_built/class1'; - export class Class2 { - constructor(class1: Class1) {} - } - `); - - // Application - write('app/tsconfig-app.json', `{ - "extends": "../tsconfig-base.json", - "angularCompilerOptions": { - "generateCodeForLibraries": false, - "enableSummariesForJit": true, - "createExternalSymbolFactoryReexports": true - }, - "compilerOptions": { - "rootDir": ".", - "outDir": "../built/app" - } - }`); - write('app/main.ts', ` - import {NgModule, Inject} from '@angular/core'; - import {Module} from 'lib2_built/module'; - @NgModule({ - imports: [Module] - }) - export class AppModule { - constructor(@Inject('foo') public foo: any) {} - } - `); - - expect(main(['-p', path.join(basePath, 'lib1', 'tsconfig-lib1.json')], errorSpy)).toBe(0); - expect(main(['-p', path.join(basePath, 'lib2', 'tsconfig-lib2.json')], errorSpy)).toBe(0); - expect(main(['-p', path.join(basePath, 'app', 'tsconfig-app.json')], errorSpy)).toBe(0); - - // library 1 - // make `shouldExist` / `shouldNotExist` relative to `node_modules` - outDir = path.resolve(basePath, 'node_modules'); - shouldExist('lib1_built/module.js'); - shouldExist('lib1_built/module.ngsummary.json'); - shouldExist('lib1_built/module.ngsummary.js'); - shouldExist('lib1_built/module.ngsummary.d.ts'); - shouldExist('lib1_built/module.ngfactory.js'); - shouldExist('lib1_built/module.ngfactory.d.ts'); - - // library 2 - // make `shouldExist` / `shouldNotExist` relative to `node_modules` - outDir = path.resolve(basePath, 'node_modules'); - shouldExist('lib2_built/module.js'); - shouldExist('lib2_built/module.ngsummary.json'); - shouldExist('lib2_built/module.ngsummary.js'); - shouldExist('lib2_built/module.ngsummary.d.ts'); - shouldExist('lib2_built/module.ngfactory.js'); - shouldExist('lib2_built/module.ngfactory.d.ts'); - - shouldExist('lib2_built/class2.ngsummary.json'); - shouldNotExist('lib2_built/class2.ngsummary.js'); - shouldNotExist('lib2_built/class2.ngsummary.d.ts'); - shouldExist('lib2_built/class2.ngfactory.js'); - shouldExist('lib2_built/class2.ngfactory.d.ts'); - - // app - // make `shouldExist` / `shouldNotExist` relative to `built` - outDir = path.resolve(basePath, 'built'); - shouldExist('app/main.js'); - }); - - it('should create external symbol re-exports', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "angularCompilerOptions": { - "generateCodeForLibraries": false, - "createExternalSymbolFactoryReexports": true - } - }`); - - write('test.ts', ` - import {Injectable, NgZone} from '@angular/core'; - - @Injectable({providedIn: 'root'}) - export class MyService { - constructor(public ngZone: NgZone) {} - } - `); - - expect(main(['-p', basePath], errorSpy)).toBe(0); - - shouldExist('test.js'); - shouldExist('test.metadata.json'); - shouldExist('test.ngsummary.json'); - shouldExist('test.ngfactory.js'); - shouldExist('test.ngfactory.d.ts'); - - const summaryJson = require(path.join(outDir, 'test.ngsummary.json')); - const factoryOutput = fs.readFileSync(path.join(outDir, 'test.ngfactory.js'), 'utf8'); - - expect(summaryJson['symbols'][0].name).toBe('MyService'); - expect(summaryJson['symbols'][1]) - .toEqual(jasmine.objectContaining({name: 'NgZone', importAs: 'NgZone_1'})); - - expect(factoryOutput).toContain(`export { NgZone as NgZone_1 } from "@angular/core";`); - }); - }); - - it('should be able to compile multiple libraries with summaries', () => { - // Lib 1 - write('lib1/tsconfig-lib1.json', `{ - "extends": "../tsconfig-base.json", - "angularCompilerOptions": { - "generateCodeForLibraries": false, - "enableSummariesForJit": true - }, - "compilerOptions": { - "rootDir": ".", - "outDir": "../node_modules/lib1_built" - } - }`); - write('lib1/module.ts', ` - import {NgModule} from '@angular/core'; - - export function someFactory(): any { return null; } - - @NgModule({ - providers: [{provide: 'foo', useFactory: someFactory}] - }) - export class Module {} - `); - write('lib1/class1.ts', `export class Class1 {}`); - - // Lib 2 - write('lib2/tsconfig-lib2.json', `{ - "extends": "../tsconfig-base.json", - "angularCompilerOptions": { - "generateCodeForLibraries": false, - "enableSummariesForJit": true - }, - "compilerOptions": { - "rootDir": ".", - "outDir": "../node_modules/lib2_built" - } - }`); - write('lib2/module.ts', `export {Module} from 'lib1_built/module';`); - write('lib2/class2.ts', ` - import {Class1} from 'lib1_built/class1'; - - export class Class2 { - constructor(class1: Class1) {} - } - `); - - // Application - write('app/tsconfig-app.json', `{ - "extends": "../tsconfig-base.json", - "angularCompilerOptions": { - "generateCodeForLibraries": false, - "enableSummariesForJit": true - }, - "compilerOptions": { - "rootDir": ".", - "outDir": "../built/app" - } - }`); - write('app/main.ts', ` - import {NgModule, Inject} from '@angular/core'; - import {Module} from 'lib2_built/module'; - - @NgModule({ - imports: [Module] - }) - export class AppModule { - constructor(@Inject('foo') public foo: any) {} - } - `); - - expect(main(['-p', path.join(basePath, 'lib1', 'tsconfig-lib1.json')], errorSpy)).toBe(0); - expect(main(['-p', path.join(basePath, 'lib2', 'tsconfig-lib2.json')], errorSpy)).toBe(0); - expect(main(['-p', path.join(basePath, 'app', 'tsconfig-app.json')], errorSpy)).toBe(0); - - // library 1 - // make `shouldExist` / `shouldNotExist` relative to `node_modules` - outDir = path.resolve(basePath, 'node_modules'); - shouldExist('lib1_built/module.js'); - shouldExist('lib1_built/module.ngsummary.json'); - shouldExist('lib1_built/module.ngsummary.js'); - shouldExist('lib1_built/module.ngsummary.d.ts'); - shouldExist('lib1_built/module.ngfactory.js'); - shouldExist('lib1_built/module.ngfactory.d.ts'); - - // library 2 - // make `shouldExist` / `shouldNotExist` relative to `node_modules` - outDir = path.resolve(basePath, 'node_modules'); - shouldExist('lib2_built/module.js'); - - // "module.ts" re-exports an external symbol and will therefore - // have a summary JSON file and its corresponding JIT summary. - shouldExist('lib2_built/module.ngsummary.json'); - shouldExist('lib2_built/module.ngsummary.js'); - shouldExist('lib2_built/module.ngsummary.d.ts'); - // "module.ts" only re-exports an external symbol and the AOT compiler does not - // need to generate anything. Therefore there should be no factory files. - shouldNotExist('lib2_built/module.ngfactory.js'); - shouldNotExist('lib2_built/module.ngfactory.d.ts'); - - shouldExist('lib2_built/class2.ngsummary.json'); - shouldNotExist('lib2_built/class2.ngsummary.js'); - shouldNotExist('lib2_built/class2.ngsummary.d.ts'); - // We don't expect factories here because the "class2.ts" file - // just exports a class that does not produce any AOT code. - shouldNotExist('lib2_built/class2.ngfactory.js'); - shouldNotExist('lib2_built/class2.ngfactory.d.ts'); - - // app - // make `shouldExist` / `shouldNotExist` relative to `built` - outDir = path.resolve(basePath, 'built'); - shouldExist('app/main.js'); - }); - - describe('enableResourceInlining', () => { - it('should inline templateUrl and styleUrl in JS and metadata', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "files": ["mymodule.ts"], - "angularCompilerOptions": { - "enableResourceInlining": true - } - }`); - write('my.component.ts', ` - import {Component} from '@angular/core'; - @Component({ - templateUrl: './my.component.html', - styleUrls: ['./my.component.css'], - }) - export class MyComp {} - `); - write('my.component.html', `

Some template content

`); - write('my.component.css', `h1 {color: blue}`); - write('mymodule.ts', ` - import {NgModule} from '@angular/core'; - import {MyComp} from './my.component'; - - @NgModule({declarations: [MyComp]}) - export class MyModule {} - `); - - const exitCode = main(['-p', basePath]); - expect(exitCode).toEqual(0); - outDir = path.resolve(basePath, 'built'); - const outputJs = fs.readFileSync(path.join(outDir, 'my.component.js'), {encoding: 'utf-8'}); - expect(outputJs).not.toContain('templateUrl'); - expect(outputJs).not.toContain('styleUrls'); - expect(outputJs).toContain('Some template content'); - expect(outputJs).toContain('color: blue'); - - const outputMetadata = - fs.readFileSync(path.join(outDir, 'my.component.metadata.json'), {encoding: 'utf-8'}); - expect(outputMetadata).not.toContain('templateUrl'); - expect(outputMetadata).not.toContain('styleUrls'); - expect(outputMetadata).toContain('Some template content'); - expect(outputMetadata).toContain('color: blue'); - }); - }); - }); - - - describe('expression lowering', () => { - const shouldExist = (fileName: string) => { - if (!fs.existsSync(path.resolve(basePath, fileName))) { - throw new Error(`Expected ${fileName} to be emitted (basePath: ${basePath})`); - } - }; - - it('should be able to lower supported expressions', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "files": ["module.ts"] - }`); - write('module.ts', ` - import {NgModule, InjectionToken} from '@angular/core'; - import {AppComponent} from './app'; - - export interface Info { - route: string; - data: string; - } - - export const T1 = new InjectionToken('t1'); - export const T2 = new InjectionToken('t2'); - export const T3 = new InjectionToken('t3'); - export const T4 = new InjectionToken('t4'); - - enum SomeEnum { - OK, - Cancel - } - - function calculateString() { - return 'someValue'; - } - - const routeLikeData = [{ - route: '/home', - data: calculateString() - }]; - - @NgModule({ - declarations: [AppComponent], - providers: [ - { provide: T1, useValue: calculateString() }, - { provide: T2, useFactory: () => 'someValue' }, - { provide: T3, useValue: SomeEnum.OK }, - { provide: T4, useValue: routeLikeData } - ] - }) - export class MyModule {} - `); - write('app.ts', ` - import {Component, Inject} from '@angular/core'; - import * as m from './module'; - - @Component({ - selector: 'my-app', - template: '' - }) - export class AppComponent { - constructor( - @Inject(m.T1) private t1: string, - @Inject(m.T2) private t2: string, - @Inject(m.T3) private t3: number, - @Inject(m.T4) private t4: m.Info[], - ) {} - } - `); - - expect(main(['-p', basePath], s => {})).toBe(0); - shouldExist('built/module.js'); - }); - }); - - describe('watch mode', () => { - let timer: (() => void)|undefined = undefined; - let results: ((message: string) => void)|undefined = undefined; - let originalTimeout: number; - - function trigger() { - const delay = 1000; - setTimeout(() => { - const t = timer; - timer = undefined; - if (!t) { - fail('Unexpected state. Timer was not set.'); - } else { - t(); - } - }, delay); - } - - function whenResults(): Promise { - return new Promise(resolve => { - results = message => { - resolve(message); - results = undefined; - }; - }); - } - - function errorSpy(message: string): void { - if (results) results(message); - } - - beforeEach(() => { - originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; - const timerToken = 100; - // TODO: @JiaLiPassion, need to wait @types/jasmine to handle optional method case - // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/43486 - spyOn(ts.sys as any, 'setTimeout').and.callFake((callback: () => void) => { - timer = callback; - return timerToken; - }); - // TODO: @JiaLiPassion, need to wait @types/jasmine to handle optional method case - // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/43486 - spyOn(ts.sys as any, 'clearTimeout').and.callFake((token: number) => { - if (token == timerToken) { - timer = undefined; - } - }); - - write('greet.html', `

Hello {{name}}!

`); - write('greet.css', `p.greeting { color: #eee }`); - write('greet.ts', ` - import {Component, Input} from '@angular/core'; - - @Component({ - selector: 'greet', - templateUrl: 'greet.html', - styleUrls: ['greet.css'] - }) - export class Greet { - @Input() - name: string; - } - `); - - write('app.ts', ` - import {Component} from '@angular/core' - - @Component({ - selector: 'my-app', - template: \` -
- -
- \`, - }) - export class App { - name:string; - constructor() { - this.name = \`Angular!\` - } - }`); - - write('module.ts', ` - import {NgModule} from '@angular/core'; - import {Greet} from './greet'; - import {App} from './app'; - - @NgModule({ - declarations: [Greet, App] - }) - export class MyModule {} - `); - }); - - afterEach(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; - }); - - function writeAppConfig(location: string) { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "compilerOptions": { - "outDir": "${location}" - } - }`); - } - - function expectRecompile(cb: () => void) { - return (done: DoneFn) => { - writeAppConfig('dist'); - const config = readCommandLineAndConfiguration(['-p', basePath]); - const compile = watchMode(config.project, config.options, errorSpy); - - return new Promise(resolve => { - compile.ready(() => { - cb(); - - // Allow the watch callbacks to occur and trigger the timer. - trigger(); - - // Expect the file to trigger a result. - whenResults().then(message => { - expect(message).toMatch(/File change detected/); - compile.close(); - done(); - resolve(); - }); - }); - }); - }; - } - - it('should recompile when config file changes', expectRecompile(() => writeAppConfig('dist2'))); - - it('should recompile when a ts file changes', expectRecompile(() => { - write('greet.ts', ` - import {Component, Input} from '@angular/core'; - - @Component({ - selector: 'greet', - templateUrl: 'greet.html', - styleUrls: ['greet.css'], - }) - export class Greet { - @Input() - name: string; - age: number; - } - `); - })); - - it('should recompile when the html file changes', expectRecompile(() => { - write('greet.html', '

Hello {{name}} again!

'); - })); - - it('should recompile when the css file changes', expectRecompile(() => { - write('greet.css', `p.greeting { color: blue }`); - })); - }); - - describe('regressions', () => { - //#20479 - it('should not generate an invalid metadata file', () => { - write('src/tsconfig.json', `{ - "extends": "../tsconfig-base.json", - "files": ["lib.ts"], - "angularCompilerOptions": { - "skipTemplateCodegen": true - } - }`); - write('src/lib.ts', ` - export namespace A{ - export class C1 { - } - export interface I1{ - } - }`); - expect(main(['-p', path.join(basePath, 'src/tsconfig.json')])).toBe(0); - shouldNotExist('src/lib.metadata.json'); - }); - - //#19544 - it('should recognize @NgModule() directive with a redundant @Injectable()', () => { - write('src/tsconfig.json', `{ - "extends": "../tsconfig-base.json", - "compilerOptions": { - "outDir": "../dist", - "rootDir": ".", - "rootDirs": [ - ".", - "../dist" - ] - }, - "files": ["test-module.ts"] - }`); - write('src/test.component.ts', ` - import {Component} from '@angular/core'; - - @Component({ - template: '

hello

', - }) - export class TestComponent {} - `); - write('src/test-module.ts', ` - import {Injectable, NgModule} from '@angular/core'; - import {TestComponent} from './test.component'; - - @NgModule({declarations: [TestComponent]}) - @Injectable() - export class TestModule {} - `); - const messages: string[] = []; - const exitCode = - main(['-p', path.join(basePath, 'src/tsconfig.json')], message => messages.push(message)); - expect(exitCode).toBe(0, 'Compile failed unexpectedly.\n ' + messages.join('\n ')); - }); - - // #19765 - it('should not report an error when the resolved .css file is in outside rootDir', () => { - write('src/tsconfig.json', `{ - "extends": "../tsconfig-base.json", - "compilerOptions": { - "outDir": "../dist", - "rootDir": ".", - "rootDirs": [ - ".", - "../dist" - ] - }, - "files": ["test-module.ts"] - }`); - write('src/lib/test.component.ts', ` - import {Component} from '@angular/core'; - - @Component({ - template: '

hello

', - styleUrls: ['./test.component.css'] - }) - export class TestComponent {} - `); - write('dist/dummy.txt', ''); // Force dist to be created - write('dist/lib/test.component.css', ` - p { color: blue } - `); - write('src/test-module.ts', ` - import {NgModule} from '@angular/core'; - import {TestComponent} from './lib/test.component'; - - @NgModule({declarations: [TestComponent]}) - export class TestModule {} - `); - const messages: string[] = []; - const exitCode = - main(['-p', path.join(basePath, 'src/tsconfig.json')], message => messages.push(message)); - expect(exitCode).toBe(0, 'Compile failed unexpectedly.\n ' + messages.join('\n ')); - }); - - it('should emit all structural errors', () => { - write('src/tsconfig.json', `{ - "extends": "../tsconfig-base.json", - "files": ["test-module.ts"] - }`); - write('src/lib/indirect2.ts', ` - declare var f: any; - export const t2 = f\`

hello

\`; - `); - write('src/lib/indirect1.ts', ` - import {t2} from './indirect2'; - export const t1 = t2 + ' '; - `); - write('src/lib/test.component.ts', ` - import {Component} from '@angular/core'; - import {t1} from './indirect1'; - - @Component({ - template: t1 - }) - export class TestComponent {} - `); - write('src/test-module.ts', ` - import {NgModule} from '@angular/core'; - import {TestComponent} from './lib/test.component'; - - @NgModule({declarations: [TestComponent]}) - export class TestModule {} - `); - const messages: string[] = []; - const exitCode = - main(['-p', path.join(basePath, 'src/tsconfig.json')], message => messages.push(message)); - expect(exitCode).toBe(1, 'Compile was expected to fail'); - expect(messages[0]).toContain('Tagged template expressions are not supported in metadata'); - }); - - // Regression: #20076 - it('should report template error messages', () => { - write('src/tsconfig.json', `{ - "extends": "../tsconfig-base.json", - "files": ["test-module.ts"] - }`); - write('src/lib/test.component.ts', ` - import {Component} from '@angular/core'; - - @Component({ - template: '{{thing.?stuff}}' - }) - export class TestComponent { - thing: string; - } - `); - write('src/test-module.ts', ` - import {NgModule} from '@angular/core'; - import {TestComponent} from './lib/test.component'; - - @NgModule({declarations: [TestComponent]}) - export class TestModule {} - `); - const messages: string[] = []; - const exitCode = - main(['-p', path.join(basePath, 'src/tsconfig.json')], message => messages.push(message)); - expect(exitCode).toBe(1, 'Compile was expected to fail'); - expect(messages[0]).toContain('Parser Error: Unexpected token'); - }); - - // Regression test for #19979 - it('should not stack overflow on a recursive module export', () => { - write('src/tsconfig.json', `{ - "extends": "../tsconfig-base.json", - "files": ["test-module.ts"] - }`); - - write('src/test-module.ts', ` - import {Component, NgModule} from '@angular/core'; - - @Component({ - template: 'Hello' - }) - export class MyFaultyComponent {} - - @NgModule({ - exports: [MyFaultyModule], - declarations: [MyFaultyComponent], - providers: [], - }) - export class MyFaultyModule { } - `); - const messages: string[] = []; - expect( - main(['-p', path.join(basePath, 'src/tsconfig.json')], message => messages.push(message))) - .toBe(1, 'Compile was expected to fail'); - expect(messages[0]).toContain(`module 'MyFaultyModule' is exported recursively`); - }); - - // Regression test for #19979 - it('should not stack overflow on a recursive module import', () => { - write('src/tsconfig.json', `{ - "extends": "../tsconfig-base.json", - "files": ["test-module.ts"] - }`); - - write('src/test-module.ts', ` - import {Component, NgModule, forwardRef} from '@angular/core'; - - @Component({ - template: 'Hello' - }) - export class MyFaultyComponent {} - - @NgModule({ - imports: [forwardRef(() => MyFaultyModule)] - }) - export class MyFaultyImport {} - - @NgModule({ - imports: [MyFaultyImport], - declarations: [MyFaultyComponent] - }) - export class MyFaultyModule { } - `); - const messages: string[] = []; - expect( - main(['-p', path.join(basePath, 'src/tsconfig.json')], message => messages.push(message))) - .toBe(1, 'Compile was expected to fail'); - expect(messages[0]).toContain(`is imported recursively by the module 'MyFaultyImport`); - }); - - // Regression test for #21273 - it('should not report errors for unknown property annotations', () => { - write('src/tsconfig.json', `{ - "extends": "../tsconfig-base.json", - "files": ["test-module.ts"] - }`); - - write('src/test-decorator.ts', ` - export function Convert(p: any): any { - // Make sur this doesn't look like a macro function - var r = p; - return r; - } - `); - write('src/test-module.ts', ` - import {Component, Input, NgModule} from '@angular/core'; - import {Convert} from './test-decorator'; - - @Component({template: '{{name}}'}) - export class TestComponent { - @Input() @Convert(convert) name: string; - } - - function convert(n: any) { return n; } - - @NgModule({declarations: [TestComponent]}) - export class TestModule {} - `); - const messages: string[] = []; - expect( - main(['-p', path.join(basePath, 'src/tsconfig.json')], message => messages.push(message))) - .toBe(0, `Compile failed:\n ${messages.join('\n ')}`); - }); - - it('should allow using 2 classes with the same name in declarations with noEmitOnError=true', - () => { - write('src/tsconfig.json', `{ - "extends": "../tsconfig-base.json", - "compilerOptions": { - "noEmitOnError": true - }, - "files": ["test-module.ts"] - }`); - function writeComp(fileName: string) { - write(fileName, ` - import {Component} from '@angular/core'; - - @Component({selector: 'comp', template: ''}) - export class TestComponent {} - `); - } - writeComp('src/comp1.ts'); - writeComp('src/comp2.ts'); - write('src/test-module.ts', ` - import {NgModule} from '@angular/core'; - import {TestComponent as Comp1} from './comp1'; - import {TestComponent as Comp2} from './comp2'; - - @NgModule({ - declarations: [Comp1, Comp2], - }) - export class MyModule {} - `); - expect(main(['-p', path.join(basePath, 'src/tsconfig.json')])).toBe(0); - }); - - it('should not type check a .js files from node_modules with allowJs', () => { - write('src/tsconfig.json', `{ - "extends": "../tsconfig-base.json", - "compilerOptions": { - "noEmitOnError": true, - "allowJs": true, - "declaration": false - }, - "files": ["test-module.ts"] - }`); - write('src/test-module.ts', ` - import {Component, NgModule} from '@angular/core'; - import 'my-library'; - - @Component({ - template: 'hello' - }) - export class HelloCmp {} - - @NgModule({ - declarations: [HelloCmp], - }) - export class MyModule {} - `); - write('src/node_modules/t.txt', ``); - write('src/node_modules/my-library/index.js', ` - export someVar = 1; - export someOtherVar = undefined + 1; - `); - expect(main(['-p', path.join(basePath, 'src/tsconfig.json')])).toBe(0); - }); - }); - - describe('formatted messages', () => { - it('should emit a formatted error message for a structural error', () => { - write('src/tsconfig.json', `{ - "extends": "../tsconfig-base.json", - "files": ["test-module.ts"] - }`); - write('src/lib/indirect2.ts', ` - declare var f: any; - - export const t2 = f\`

hello

\`; - `); - write('src/lib/indirect1.ts', ` - import {t2} from './indirect2'; - export const t1 = t2 + ' '; - `); - write('src/lib/test.component.ts', ` - import {Component} from '@angular/core'; - import {t1} from './indirect1'; - - @Component({ - template: t1, - styleUrls: ['./test.component.css'] - }) - export class TestComponent {} - `); - write('src/test-module.ts', ` - import {NgModule} from '@angular/core'; - import {TestComponent} from './lib/test.component'; - - @NgModule({declarations: [TestComponent]}) - export class TestModule {} - `); - const messages: string[] = []; - const exitCode = - main(['-p', path.join(basePath, 'src/tsconfig.json')], message => messages.push(message)); - expect(exitCode).toBe(1, 'Compile was expected to fail'); - const srcPathWithSep = `lib/`; - expect(messages[0]) - .toEqual(`${ - srcPathWithSep}test.component.ts(6,21): Error during template compile of 'TestComponent' - Tagged template expressions are not supported in metadata in 't1' - 't1' references 't2' at ${srcPathWithSep}indirect1.ts(3,27) - 't2' contains the error at ${srcPathWithSep}indirect2.ts(4,27). -`); - }); - }); - - describe('tree shakeable services', () => { - function compileService(source: string, withTsickle = false): string { - write('service.ts', source); - - const exitCode = main( - ['-p', path.join(basePath, 'tsconfig.json')], errorSpy, undefined, undefined, undefined, - undefined, withTsickle ? tsickle : undefined); - expect(exitCode).toEqual(0); - - const servicePath = path.resolve(outDir, 'service.js'); - return fs.readFileSync(servicePath, 'utf8'); - } - - beforeEach(() => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "files": ["service.ts"] - }`); - write('module.ts', ` - import {NgModule} from '@angular/core'; - - @NgModule({}) - export class Module {} - `); - }); - - describe(`doesn't break existing injectables`, () => { - it('on simple services', () => { - const source = compileService(` - import {Injectable, NgModule} from '@angular/core'; - - @Injectable() - export class Service { - constructor(public param: string) {} - } - - @NgModule({ - providers: [{provide: Service, useValue: new Service('test')}], - }) - export class ServiceModule {} - `); - expect(source).not.toMatch(/ɵprov/); - }); - it('on a service with a base class service', () => { - const source = compileService(` - import {Injectable, NgModule} from '@angular/core'; - - @Injectable() - export class Dep {} - - export class Base { - constructor(private dep: Dep) {} - } - @Injectable() - export class Service extends Base {} - - @NgModule({ - providers: [Service], - }) - export class ServiceModule {} - `); - expect(source).not.toMatch(/ɵprov/); - }); - }); - - it('compiles a basic Injectable definition', () => { - const source = compileService(` - import {Injectable} from '@angular/core'; - import {Module} from './module'; - - @Injectable({ - providedIn: Module, - }) - export class Service {} - `); - expect(source).toMatch(/ɵprov = .+\.ɵɵdefineInjectable\(/); - expect(source).toMatch(/ɵprov.*token: Service/); - expect(source).toMatch(/ɵprov.*providedIn: .+\.Module/); - }); - - it('ɵprov in es5 mode is annotated @nocollapse when closure options are enabled', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "angularCompilerOptions": { - "annotateForClosureCompiler": true - }, - "files": ["service.ts"] - }`); - const input = ` - import {Injectable} from '@angular/core'; - import {Module} from './module'; - - @Injectable({ - providedIn: Module, - }) - export class Service {} - `; - - const source = compileService(input, /* withTsickle */ true); - expect(source).toMatch(/\/\*\* @nocollapse \*\/ Service\.ɵprov =/); - }); - - it('compiles a useValue Injectable definition', () => { - const source = compileService(` - import {Injectable} from '@angular/core'; - import {Module} from './module'; - - export const CONST_SERVICE: Service = null; - - @Injectable({ - providedIn: Module, - useValue: CONST_SERVICE - }) - export class Service {} - `); - expect(source).toMatch(/ɵprov.*return CONST_SERVICE/); - }); - - it('compiles a useExisting Injectable definition', () => { - const source = compileService(` - import {Injectable} from '@angular/core'; - import {Module} from './module'; - - @Injectable() - export class Existing {} - - @Injectable({ - providedIn: Module, - useExisting: Existing, - }) - export class Service {} - `); - expect(source).toMatch(/ɵprov.*return ..\.ɵɵinject\(Existing\)/); - }); - - it('compiles a useFactory Injectable definition with optional dep', () => { - const source = compileService(` - import {Injectable, Optional} from '@angular/core'; - import {Module} from './module'; - - @Injectable() - export class Existing {} - - @Injectable({ - providedIn: Module, - useFactory: (existing: Existing|null) => new Service(existing), - deps: [[new Optional(), Existing]], - }) - export class Service { - constructor(e: Existing|null) {} - } - `); - expect(source).toMatch(/ɵprov.*return ..\(..\.ɵɵinject\(Existing, 8\)/); - }); - - it('compiles a useFactory Injectable definition with skip-self dep', () => { - const source = compileService(` - import {Injectable, SkipSelf} from '@angular/core'; - import {Module} from './module'; - - @Injectable() - export class Existing {} - - @Injectable({ - providedIn: Module, - useFactory: (existing: Existing) => new Service(existing), - deps: [[new SkipSelf(), Existing]], - }) - export class Service { - constructor(e: Existing) {} - } - `); - expect(source).toMatch(/ɵprov.*return ..\(..\.ɵɵinject\(Existing, 4\)/); - }); - - it('compiles a service that depends on a token', () => { - const source = compileService(` - import {Inject, Injectable, InjectionToken} from '@angular/core'; - import {Module} from './module'; - - export const TOKEN = new InjectionToken('desc', {providedIn: Module, factory: () => true}); - - @Injectable({ - providedIn: Module, - }) - export class Service { - constructor(@Inject(TOKEN) value: boolean) {} - } - `); - expect(source).toMatch(/ɵprov = .+\.ɵɵdefineInjectable\(/); - expect(source).toMatch(/ɵprov.*token: Service/); - expect(source).toMatch(/ɵprov.*providedIn: .+\.Module/); - }); - - it('generates exports.* references when outputting commonjs', () => { - writeConfig(`{ - "extends": "./tsconfig-base.json", - "compilerOptions": { - "module": "commonjs" - }, - "files": ["service.ts"] - }`); - const source = compileService(` - import {Inject, Injectable, InjectionToken} from '@angular/core'; - import {Module} from './module'; - - export const TOKEN = new InjectionToken('test token', { - providedIn: 'root', - factory: () => 'this is a test', - }); - - @Injectable({providedIn: 'root'}) - export class Service { - constructor(@Inject(TOKEN) token: any) {} - } - `); - expect(source).toMatch(/new Service\(i0\.ɵɵinject\(exports\.TOKEN\)\);/); - }); - - it('compiles an injectable using `forwardRef` inside `providedIn`', () => { - const source = compileService(` - import {Injectable, forwardRef} from '@angular/core'; - import {Module} from './module'; - - @Injectable({ - providedIn: forwardRef(() => Module), - }) - export class Service {} - `); - - expect(source).toMatch(/ɵprov = .+\.ɵɵdefineInjectable\(/); - expect(source).toMatch(/ɵprov.*token: Service/); - expect(source).toMatch(/ɵprov.*providedIn: .+\.Module/); - }); - }); - - it('libraries should not break strictMetadataEmit', () => { - // first only generate .d.ts / .js / .metadata.json files - writeConfig(`{ - "extends": "./tsconfig-base.json", - "angularCompilerOptions": { - "skipTemplateCodegen": true, - "strictMetadataEmit": true, - "fullTemplateTypeCheck": true - }, - "compilerOptions": { - "outDir": "lib" - }, - "files": ["main.ts", "test.d.ts"] - }`); - write('main.ts', ` - import {Test} from './test'; - export const bar = Test.bar; - `); - write('test.d.ts', ` - declare export class Test { - static bar: string; - } - `); - let exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy); - expect(exitCode).toEqual(0); - }); - - describe('base directives', () => { - it('should allow directives with no selector that are not in NgModules', () => { - // first only generate .d.ts / .js / .metadata.json files - writeConfig(`{ - "extends": "./tsconfig-base.json", - "files": ["main.ts"] - }`); - write('main.ts', ` - import {Directive} from '@angular/core'; - - @Directive({}) - export class BaseDir {} - - @Directive({}) - export abstract class AbstractBaseDir {} - - @Directive() - export abstract class EmptyDir {} - `); - let exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy); - expect(exitCode).toEqual(0); - }); - - it('should be able to use abstract directive in other compilation units', () => { - writeConfig(); - write('lib1/tsconfig.json', JSON.stringify({ - extends: '../tsconfig-base.json', - compilerOptions: {rootDir: '.', outDir: '../node_modules/lib1_built'}, - })); - write('lib1/index.ts', ` - import {Directive} from '@angular/core'; - - @Directive() - export class BaseClass {} - `); - write('index.ts', ` - import {NgModule, Directive} from '@angular/core'; - import {BaseClass} from 'lib1_built'; - - @Directive({selector: 'my-dir'}) - export class MyDirective extends BaseClass {} - - @NgModule({declarations: [MyDirective]}) - export class AppModule {} - `); - - expect(main(['-p', path.join(basePath, 'lib1/tsconfig.json')], errorSpy)).toBe(0); - expect(main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy)).toBe(0); - }); - }); -}); diff --git a/packages/compiler-cli/test/ngtsc/scope_spec.ts b/packages/compiler-cli/test/ngtsc/scope_spec.ts index 4c59e76cf90..65fc6b16364 100644 --- a/packages/compiler-cli/test/ngtsc/scope_spec.ts +++ b/packages/compiler-cli/test/ngtsc/scope_spec.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {Diagnostic} from '@angular/compiler-cli'; import ts from 'typescript'; import {ErrorCode, ngErrorCode} from '../../src/ngtsc/diagnostics'; @@ -388,7 +387,7 @@ runInEachFileSystem(() => { }); function diagnosticToNode( - diagnostic: ts.Diagnostic|Diagnostic|ts.DiagnosticRelatedInformation, + diagnostic: ts.Diagnostic|ts.DiagnosticRelatedInformation, guard: (node: ts.Node) => node is T): T { const diag = diagnostic as ts.Diagnostic | ts.DiagnosticRelatedInformation; if (diag.file === undefined) { diff --git a/packages/compiler-cli/test/perform_watch_spec.ts b/packages/compiler-cli/test/perform_watch_spec.ts index 3db90a6163e..62270e5676b 100644 --- a/packages/compiler-cli/test/perform_watch_spec.ts +++ b/packages/compiler-cli/test/perform_watch_spec.ts @@ -234,11 +234,11 @@ class MockWatchHost { nextTimeoutListenerId = 1; timeoutListeners: {[id: string]: (() => void)} = {}; fileChangeListeners: Array<((event: FileChangeEvent, fileName: string) => void)|null> = []; - diagnostics: ng.Diagnostic[] = []; + diagnostics: ts.Diagnostic[] = []; constructor(public config: ng.ParsedConfiguration) {} - reportDiagnostics(diags: ng.Diagnostics) { - this.diagnostics.push(...(diags as ng.Diagnostic[])); + reportDiagnostics(diags: readonly ts.Diagnostic[]) { + this.diagnostics.push(...diags); } readConfiguration() { return this.config; diff --git a/packages/compiler-cli/test/test_support.ts b/packages/compiler-cli/test/test_support.ts index c26a3eac6dd..e0dff2785bf 100644 --- a/packages/compiler-cli/test/test_support.ts +++ b/packages/compiler-cli/test/test_support.ts @@ -156,7 +156,7 @@ export function setup(): TestSupport { return createTestSupportFor(tmpDirPath); } -export function expectNoDiagnostics(options: ng.CompilerOptions, diags: ng.Diagnostics) { +export function expectNoDiagnostics(options: ng.CompilerOptions, diags: readonly ts.Diagnostic[]) { const errorDiags = diags.filter(d => d.category !== ts.DiagnosticCategory.Message); if (errorDiags.length) { throw new Error(`Expected no diagnostics: ${ng.formatDiagnostics(errorDiags)}`); diff --git a/packages/compiler/src/aot/compiler.ts b/packages/compiler/src/aot/compiler.ts deleted file mode 100644 index 204bc078652..00000000000 --- a/packages/compiler/src/aot/compiler.ts +++ /dev/null @@ -1,880 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {CompileDirectiveMetadata, CompileInjectableMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileShallowModuleMetadata, CompileStylesheetMetadata, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, templateSourceUrl} from '../compile_metadata'; -import {CompilerConfig} from '../config'; -import {ConstantPool, OutputContext} from '../constant_pool'; -import {ViewEncapsulation} from '../core'; -import {MessageBundle} from '../i18n/message_bundle'; -import {createTokenForExternalReference, Identifiers} from '../identifiers'; -import {InjectableCompiler} from '../injectable_compiler'; -import {CompileMetadataResolver} from '../metadata_resolver'; -import {HtmlParser} from '../ml_parser/html_parser'; -import {InterpolationConfig} from '../ml_parser/interpolation_config'; -import {NgModuleCompiler} from '../ng_module_compiler'; -import {OutputEmitter} from '../output/abstract_emitter'; -import * as o from '../output/output_ast'; -import {CompileIdentifierMetadata, identifierName, ParseError, syntaxError} from '../parse_util'; -import {CompiledStylesheet, StyleCompiler} from '../style_compiler'; -import {SummaryResolver} from '../summary_resolver'; -import {TemplateAst} from '../template_parser/template_ast'; -import {TemplateParser} from '../template_parser/template_parser'; -import {newArray, ValueVisitor, visitValue} from '../util'; -import {TypeCheckCompiler} from '../view_compiler/type_check_compiler'; -import {ViewCompiler, ViewCompileResult} from '../view_compiler/view_compiler'; - -import {AotCompilerHost} from './compiler_host'; -import {AotCompilerOptions} from './compiler_options'; -import {GeneratedFile} from './generated_file'; -import {PartialModule} from './partial_module'; -import {StaticReflector} from './static_reflector'; -import {StaticSymbol} from './static_symbol'; -import {StaticSymbolResolver} from './static_symbol_resolver'; -import {createForJitStub, serializeSummaries} from './summary_serializer'; -import {ngfactoryFilePath, normalizeGenFileSuffix, splitTypescriptSuffix, summaryFileName, summaryForJitFileName} from './util'; - -const enum StubEmitFlags { - Basic = 1 << 0, - TypeCheck = 1 << 1, - All = TypeCheck | Basic -} - -export class AotCompiler { - private _templateAstCache = - new Map(); - private _analyzedFiles = new Map(); - private _analyzedFilesForInjectables = new Map(); - - constructor( - private _config: CompilerConfig, private _options: AotCompilerOptions, - private _host: AotCompilerHost, readonly reflector: StaticReflector, - private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser, - private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, - private _typeCheckCompiler: TypeCheckCompiler, private _ngModuleCompiler: NgModuleCompiler, - private _injectableCompiler: InjectableCompiler, private _outputEmitter: OutputEmitter, - private _summaryResolver: SummaryResolver, - private _symbolResolver: StaticSymbolResolver) {} - - clearCache() { - this._metadataResolver.clearCache(); - } - - analyzeModulesSync(rootFiles: string[]): NgAnalyzedModules { - const analyzeResult = analyzeAndValidateNgModules( - rootFiles, this._host, this._symbolResolver, this._metadataResolver); - analyzeResult.ngModules.forEach( - ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata( - ngModule.type.reference, true)); - return analyzeResult; - } - - analyzeModulesAsync(rootFiles: string[]): Promise { - const analyzeResult = analyzeAndValidateNgModules( - rootFiles, this._host, this._symbolResolver, this._metadataResolver); - return Promise - .all(analyzeResult.ngModules.map( - ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata( - ngModule.type.reference, false))) - .then(() => analyzeResult); - } - - private _analyzeFile(fileName: string): NgAnalyzedFile { - let analyzedFile = this._analyzedFiles.get(fileName); - if (!analyzedFile) { - analyzedFile = - analyzeFile(this._host, this._symbolResolver, this._metadataResolver, fileName); - this._analyzedFiles.set(fileName, analyzedFile); - } - return analyzedFile; - } - - private _analyzeFileForInjectables(fileName: string): NgAnalyzedFileWithInjectables { - let analyzedFile = this._analyzedFilesForInjectables.get(fileName); - if (!analyzedFile) { - analyzedFile = analyzeFileForInjectables( - this._host, this._symbolResolver, this._metadataResolver, fileName); - this._analyzedFilesForInjectables.set(fileName, analyzedFile); - } - return analyzedFile; - } - - findGeneratedFileNames(fileName: string): string[] { - const genFileNames: string[] = []; - const file = this._analyzeFile(fileName); - // Make sure we create a .ngfactory if we have a injectable/directive/pipe/NgModule - // or a reference to a non source file. - // Note: This is overestimating the required .ngfactory files as the real calculation is harder. - // Only do this for StubEmitFlags.Basic, as adding a type check block - // does not change this file (as we generate type check blocks based on NgModules). - if (this._options.allowEmptyCodegenFiles || file.directives.length || file.pipes.length || - file.injectables.length || file.ngModules.length || file.exportsNonSourceFiles) { - genFileNames.push(ngfactoryFilePath(file.fileName, true)); - if (this._options.enableSummariesForJit) { - genFileNames.push(summaryForJitFileName(file.fileName, true)); - } - } - const fileSuffix = normalizeGenFileSuffix(splitTypescriptSuffix(file.fileName, true)[1]); - file.directives.forEach((dirSymbol) => { - const compMeta = - this._metadataResolver.getNonNormalizedDirectiveMetadata(dirSymbol)!.metadata; - if (!compMeta.isComponent) { - return; - } - // Note: compMeta is a component and therefore template is non null. - compMeta.template !.styleUrls.forEach((styleUrl) => { - const normalizedUrl = this._host.resourceNameToFileName(styleUrl, file.fileName); - if (!normalizedUrl) { - throw syntaxError(`Couldn't resolve resource ${styleUrl} relative to ${file.fileName}`); - } - const needsShim = (compMeta.template !.encapsulation || - this._config.defaultEncapsulation) === ViewEncapsulation.Emulated; - genFileNames.push(_stylesModuleUrl(normalizedUrl, needsShim, fileSuffix)); - if (this._options.allowEmptyCodegenFiles) { - genFileNames.push(_stylesModuleUrl(normalizedUrl, !needsShim, fileSuffix)); - } - }); - }); - return genFileNames; - } - - emitBasicStub(genFileName: string, originalFileName?: string): GeneratedFile { - const outputCtx = this._createOutputContext(genFileName); - if (genFileName.endsWith('.ngfactory.ts')) { - if (!originalFileName) { - throw new Error( - `Assertion error: require the original file for .ngfactory.ts stubs. File: ${ - genFileName}`); - } - const originalFile = this._analyzeFile(originalFileName); - this._createNgFactoryStub(outputCtx, originalFile, StubEmitFlags.Basic); - } else if (genFileName.endsWith('.ngsummary.ts')) { - if (this._options.enableSummariesForJit) { - if (!originalFileName) { - throw new Error( - `Assertion error: require the original file for .ngsummary.ts stubs. File: ${ - genFileName}`); - } - const originalFile = this._analyzeFile(originalFileName); - _createEmptyStub(outputCtx); - originalFile.ngModules.forEach(ngModule => { - // create exports that user code can reference - createForJitStub(outputCtx, ngModule.type.reference); - }); - } - } else if (genFileName.endsWith('.ngstyle.ts')) { - _createEmptyStub(outputCtx); - } - // Note: for the stubs, we don't need a property srcFileUrl, - // as later on in emitAllImpls we will create the proper GeneratedFiles with the - // correct srcFileUrl. - // This is good as e.g. for .ngstyle.ts files we can't derive - // the url of components based on the genFileUrl. - return this._codegenSourceModule('unknown', outputCtx); - } - - emitTypeCheckStub(genFileName: string, originalFileName: string): GeneratedFile|null { - const originalFile = this._analyzeFile(originalFileName); - const outputCtx = this._createOutputContext(genFileName); - if (genFileName.endsWith('.ngfactory.ts')) { - this._createNgFactoryStub(outputCtx, originalFile, StubEmitFlags.TypeCheck); - } - return outputCtx.statements.length > 0 ? - this._codegenSourceModule(originalFile.fileName, outputCtx) : - null; - } - - loadFilesAsync(fileNames: string[], tsFiles: string[]): Promise< - {analyzedModules: NgAnalyzedModules, analyzedInjectables: NgAnalyzedFileWithInjectables[]}> { - const files = fileNames.map(fileName => this._analyzeFile(fileName)); - const loadingPromises: Promise[] = []; - files.forEach( - file => file.ngModules.forEach( - ngModule => - loadingPromises.push(this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata( - ngModule.type.reference, false)))); - const analyzedInjectables = tsFiles.map(tsFile => this._analyzeFileForInjectables(tsFile)); - return Promise.all(loadingPromises).then(_ => ({ - analyzedModules: mergeAndValidateNgFiles(files), - analyzedInjectables: analyzedInjectables, - })); - } - - loadFilesSync(fileNames: string[], tsFiles: string[]): - {analyzedModules: NgAnalyzedModules, analyzedInjectables: NgAnalyzedFileWithInjectables[]} { - const files = fileNames.map(fileName => this._analyzeFile(fileName)); - files.forEach( - file => file.ngModules.forEach( - ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata( - ngModule.type.reference, true))); - const analyzedInjectables = tsFiles.map(tsFile => this._analyzeFileForInjectables(tsFile)); - return { - analyzedModules: mergeAndValidateNgFiles(files), - analyzedInjectables: analyzedInjectables, - }; - } - - private _createNgFactoryStub( - outputCtx: OutputContext, file: NgAnalyzedFile, emitFlags: StubEmitFlags) { - let componentId = 0; - file.ngModules.forEach((ngModuleMeta, ngModuleIndex) => { - // Note: the code below needs to executed for StubEmitFlags.Basic and StubEmitFlags.TypeCheck, - // so we don't change the .ngfactory file too much when adding the type-check block. - - // create exports that user code can reference - this._ngModuleCompiler.createStub(outputCtx, ngModuleMeta.type.reference); - - // add references to the symbols from the metadata. - // These can be used by the type check block for components, - // and they also cause TypeScript to include these files into the program too, - // which will make them part of the analyzedFiles. - const externalReferences: StaticSymbol[] = [ - // Add references that are available from all the modules and imports. - ...ngModuleMeta.transitiveModule.directives.map(d => d.reference), - ...ngModuleMeta.transitiveModule.pipes.map(d => d.reference), - ...ngModuleMeta.importedModules.map(m => m.type.reference), - ...ngModuleMeta.exportedModules.map(m => m.type.reference), - - // Add references that might be inserted by the template compiler. - ...this._externalIdentifierReferences([Identifiers.TemplateRef, Identifiers.ElementRef]), - ]; - - const externalReferenceVars = new Map(); - externalReferences.forEach((ref, typeIndex) => { - externalReferenceVars.set(ref, `_decl${ngModuleIndex}_${typeIndex}`); - }); - externalReferenceVars.forEach((varName, reference) => { - outputCtx.statements.push( - o.variable(varName) - .set(o.NULL_EXPR.cast(o.DYNAMIC_TYPE)) - .toDeclStmt(o.expressionType(outputCtx.importExpr( - reference, /* typeParams */ null, /* useSummaries */ false)))); - }); - - if (emitFlags & StubEmitFlags.TypeCheck) { - // add the type-check block for all components of the NgModule - ngModuleMeta.declaredDirectives.forEach((dirId) => { - const compMeta = this._metadataResolver.getDirectiveMetadata(dirId.reference); - if (!compMeta.isComponent) { - return; - } - componentId++; - this._createTypeCheckBlock( - outputCtx, `${compMeta.type.reference.name}_Host_${componentId}`, ngModuleMeta, - this._metadataResolver.getHostComponentMetadata(compMeta), [compMeta.type], - externalReferenceVars); - this._createTypeCheckBlock( - outputCtx, `${compMeta.type.reference.name}_${componentId}`, ngModuleMeta, compMeta, - ngModuleMeta.transitiveModule.directives, externalReferenceVars); - }); - } - }); - - if (outputCtx.statements.length === 0) { - _createEmptyStub(outputCtx); - } - } - - private _externalIdentifierReferences(references: o.ExternalReference[]): StaticSymbol[] { - const result: StaticSymbol[] = []; - for (let reference of references) { - const token = createTokenForExternalReference(this.reflector, reference); - if (token.identifier) { - result.push(token.identifier.reference); - } - } - return result; - } - - private _createTypeCheckBlock( - ctx: OutputContext, componentId: string, moduleMeta: CompileNgModuleMetadata, - compMeta: CompileDirectiveMetadata, directives: CompileIdentifierMetadata[], - externalReferenceVars: Map) { - const {template: parsedTemplate, pipes: usedPipes} = - this._parseTemplate(compMeta, moduleMeta, directives); - ctx.statements.push(...this._typeCheckCompiler.compileComponent( - componentId, compMeta, parsedTemplate, usedPipes, externalReferenceVars, ctx)); - } - - emitMessageBundle(analyzeResult: NgAnalyzedModules, locale: string|null): MessageBundle { - const errors: ParseError[] = []; - const htmlParser = new HtmlParser(); - - // TODO(vicb): implicit tags & attributes - const messageBundle = new MessageBundle(htmlParser, [], {}, locale); - - analyzeResult.files.forEach(file => { - const compMetas: CompileDirectiveMetadata[] = []; - file.directives.forEach(directiveType => { - const dirMeta = this._metadataResolver.getDirectiveMetadata(directiveType); - if (dirMeta && dirMeta.isComponent) { - compMetas.push(dirMeta); - } - }); - compMetas.forEach(compMeta => { - const html = compMeta.template !.template !; - // Template URL points to either an HTML or TS file depending on whether - // the file is used with `templateUrl:` or `template:`, respectively. - const templateUrl = compMeta.template !.templateUrl!; - const interpolationConfig = - InterpolationConfig.fromArray(compMeta.template !.interpolation); - errors.push(...messageBundle.updateFromTemplate(html, templateUrl, interpolationConfig)!); - }); - }); - - if (errors.length) { - throw new Error(errors.map(e => e.toString()).join('\n')); - } - - return messageBundle; - } - - emitAllPartialModules2(files: NgAnalyzedFileWithInjectables[]): PartialModule[] { - // Using reduce like this is a select many pattern (where map is a select pattern) - return files.reduce((r, file) => { - r.push(...this._emitPartialModule2(file.fileName, file.injectables)); - return r; - }, []); - } - - private _emitPartialModule2(fileName: string, injectables: CompileInjectableMetadata[]): - PartialModule[] { - const context = this._createOutputContext(fileName); - - injectables.forEach(injectable => this._injectableCompiler.compile(injectable, context)); - - if (context.statements && context.statements.length > 0) { - return [{fileName, statements: [...context.constantPool.statements, ...context.statements]}]; - } - return []; - } - - emitAllImpls(analyzeResult: NgAnalyzedModules): GeneratedFile[] { - const {ngModuleByPipeOrDirective, files} = analyzeResult; - const sourceModules = files.map( - file => this._compileImplFile( - file.fileName, ngModuleByPipeOrDirective, file.directives, file.pipes, file.ngModules, - file.injectables)); - return flatten(sourceModules); - } - - private _compileImplFile( - srcFileUrl: string, ngModuleByPipeOrDirective: Map, - directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: CompileNgModuleMetadata[], - injectables: CompileInjectableMetadata[]): GeneratedFile[] { - const fileSuffix = normalizeGenFileSuffix(splitTypescriptSuffix(srcFileUrl, true)[1]); - const generatedFiles: GeneratedFile[] = []; - - const outputCtx = this._createOutputContext(ngfactoryFilePath(srcFileUrl, true)); - - generatedFiles.push( - ...this._createSummary(srcFileUrl, directives, pipes, ngModules, injectables, outputCtx)); - - // compile all ng modules - ngModules.forEach((ngModuleMeta) => this._compileModule(outputCtx, ngModuleMeta)); - - // compile components - directives.forEach((dirType) => { - const compMeta = this._metadataResolver.getDirectiveMetadata(dirType); - if (!compMeta.isComponent) { - return; - } - const ngModule = ngModuleByPipeOrDirective.get(dirType); - if (!ngModule) { - throw new Error(`Internal Error: cannot determine the module for component ${ - identifierName(compMeta.type)}!`); - } - - // compile styles - const componentStylesheet = this._styleCompiler.compileComponent(outputCtx, compMeta); - // Note: compMeta is a component and therefore template is non null. - compMeta.template !.externalStylesheets.forEach((stylesheetMeta) => { - // Note: fill non shim and shim style files as they might - // be shared by component with and without ViewEncapsulation. - const shim = this._styleCompiler.needsStyleShim(compMeta); - generatedFiles.push( - this._codegenStyles(srcFileUrl, compMeta, stylesheetMeta, shim, fileSuffix)); - if (this._options.allowEmptyCodegenFiles) { - generatedFiles.push( - this._codegenStyles(srcFileUrl, compMeta, stylesheetMeta, !shim, fileSuffix)); - } - }); - - // compile components - const compViewVars = this._compileComponent( - outputCtx, compMeta, ngModule, ngModule.transitiveModule.directives, componentStylesheet, - fileSuffix); - this._compileComponentFactory(outputCtx, compMeta, ngModule, fileSuffix); - }); - if (outputCtx.statements.length > 0 || this._options.allowEmptyCodegenFiles) { - const srcModule = this._codegenSourceModule(srcFileUrl, outputCtx); - generatedFiles.unshift(srcModule); - } - return generatedFiles; - } - - private _createSummary( - srcFileName: string, directives: StaticSymbol[], pipes: StaticSymbol[], - ngModules: CompileNgModuleMetadata[], injectables: CompileInjectableMetadata[], - ngFactoryCtx: OutputContext): GeneratedFile[] { - const symbolSummaries = this._symbolResolver.getSymbolsOf(srcFileName) - .map(symbol => this._symbolResolver.resolveSymbol(symbol)); - const typeData: { - summary: CompileTypeSummary, - metadata: CompileNgModuleMetadata|CompileDirectiveMetadata|CompilePipeMetadata| - CompileTypeMetadata - }[] = - [ - ...ngModules.map( - meta => ({ - summary: this._metadataResolver.getNgModuleSummary(meta.type.reference)!, - metadata: this._metadataResolver.getNgModuleMetadata(meta.type.reference)! - })), - ...directives.map(ref => ({ - summary: this._metadataResolver.getDirectiveSummary(ref)!, - metadata: this._metadataResolver.getDirectiveMetadata(ref)! - })), - ...pipes.map(ref => ({ - summary: this._metadataResolver.getPipeSummary(ref)!, - metadata: this._metadataResolver.getPipeMetadata(ref)! - })), - ...injectables.map( - ref => ({ - summary: this._metadataResolver.getInjectableSummary(ref.symbol)!, - metadata: this._metadataResolver.getInjectableSummary(ref.symbol)!.type - })) - ]; - const forJitOutputCtx = this._options.enableSummariesForJit ? - this._createOutputContext(summaryForJitFileName(srcFileName, true)) : - null; - const {json, exportAs} = serializeSummaries( - srcFileName, forJitOutputCtx, this._summaryResolver, this._symbolResolver, symbolSummaries, - typeData, this._options.createExternalSymbolFactoryReexports); - exportAs.forEach((entry) => { - ngFactoryCtx.statements.push( - o.variable(entry.exportAs).set(ngFactoryCtx.importExpr(entry.symbol)).toDeclStmt(null, [ - o.StmtModifier.Exported - ])); - }); - const summaryJson = new GeneratedFile(srcFileName, summaryFileName(srcFileName), json); - const result = [summaryJson]; - if (forJitOutputCtx) { - result.push(this._codegenSourceModule(srcFileName, forJitOutputCtx)); - } - return result; - } - - private _compileModule(outputCtx: OutputContext, ngModule: CompileNgModuleMetadata): void { - const providers: CompileProviderMetadata[] = []; - - if (this._options.locale) { - const normalizedLocale = this._options.locale.replace(/_/g, '-'); - providers.push({ - token: createTokenForExternalReference(this.reflector, Identifiers.LOCALE_ID), - useValue: normalizedLocale, - }); - } - - if (this._options.i18nFormat) { - providers.push({ - token: createTokenForExternalReference(this.reflector, Identifiers.TRANSLATIONS_FORMAT), - useValue: this._options.i18nFormat - }); - } - - this._ngModuleCompiler.compile(outputCtx, ngModule, providers); - } - - private _compileComponentFactory( - outputCtx: OutputContext, compMeta: CompileDirectiveMetadata, - ngModule: CompileNgModuleMetadata, fileSuffix: string): void { - const hostMeta = this._metadataResolver.getHostComponentMetadata(compMeta); - const hostViewFactoryVar = - this._compileComponent(outputCtx, hostMeta, ngModule, [compMeta.type], null, fileSuffix) - .viewClassVar; - const compFactoryVar = componentFactoryName(compMeta.type.reference); - const inputsExprs: o.LiteralMapEntry[] = []; - for (let propName in compMeta.inputs) { - const templateName = compMeta.inputs[propName]; - // Don't quote so that the key gets minified... - inputsExprs.push(new o.LiteralMapEntry(propName, o.literal(templateName), false)); - } - const outputsExprs: o.LiteralMapEntry[] = []; - for (let propName in compMeta.outputs) { - const templateName = compMeta.outputs[propName]; - // Don't quote so that the key gets minified... - outputsExprs.push(new o.LiteralMapEntry(propName, o.literal(templateName), false)); - } - - outputCtx.statements.push( - o.variable(compFactoryVar) - .set(o.importExpr(Identifiers.createComponentFactory).callFn([ - o.literal(compMeta.selector), outputCtx.importExpr(compMeta.type.reference), - o.variable(hostViewFactoryVar), new o.LiteralMapExpr(inputsExprs), - new o.LiteralMapExpr(outputsExprs), - o.literalArr( - compMeta.template !.ngContentSelectors.map(selector => o.literal(selector))) - ])) - .toDeclStmt( - o.importType( - Identifiers.ComponentFactory, - [o.expressionType(outputCtx.importExpr(compMeta.type.reference))!], - [o.TypeModifier.Const]), - [o.StmtModifier.Final, o.StmtModifier.Exported])); - } - - private _compileComponent( - outputCtx: OutputContext, compMeta: CompileDirectiveMetadata, - ngModule: CompileNgModuleMetadata, directiveIdentifiers: CompileIdentifierMetadata[], - componentStyles: CompiledStylesheet|null, fileSuffix: string): ViewCompileResult { - const {template: parsedTemplate, pipes: usedPipes} = - this._parseTemplate(compMeta, ngModule, directiveIdentifiers); - const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]); - const viewResult = this._viewCompiler.compileComponent( - outputCtx, compMeta, parsedTemplate, stylesExpr, usedPipes); - if (componentStyles) { - _resolveStyleStatements( - this._symbolResolver, componentStyles, this._styleCompiler.needsStyleShim(compMeta), - fileSuffix); - } - return viewResult; - } - - private _parseTemplate( - compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, - directiveIdentifiers: CompileIdentifierMetadata[]): - {template: TemplateAst[], pipes: CompilePipeSummary[]} { - if (this._templateAstCache.has(compMeta.type.reference)) { - return this._templateAstCache.get(compMeta.type.reference)!; - } - const preserveWhitespaces = compMeta!.template !.preserveWhitespaces; - const directives = - directiveIdentifiers.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference)); - const pipes = ngModule.transitiveModule.pipes.map( - pipe => this._metadataResolver.getPipeSummary(pipe.reference)); - const result = this._templateParser.parse( - compMeta, compMeta.template !.htmlAst!, directives, pipes, ngModule.schemas, - templateSourceUrl(ngModule.type, compMeta, compMeta.template !), preserveWhitespaces); - this._templateAstCache.set(compMeta.type.reference, result); - return result; - } - - private _createOutputContext(genFilePath: string): OutputContext { - const importExpr = - (symbol: StaticSymbol, typeParams: o.Type[]|null = null, useSummaries: boolean = true) => { - if (!(symbol instanceof StaticSymbol)) { - throw new Error(`Internal error: unknown identifier ${JSON.stringify(symbol)}`); - } - const arity = this._symbolResolver.getTypeArity(symbol) || 0; - const {filePath, name, members} = - this._symbolResolver.getImportAs(symbol, useSummaries) || symbol; - const importModule = this._fileNameToModuleName(filePath, genFilePath); - - // It should be good enough to compare filePath to genFilePath and if they are equal - // there is a self reference. However, ngfactory files generate to .ts but their - // symbols have .d.ts so a simple compare is insufficient. They should be canonical - // and is tracked by #17705. - const selfReference = this._fileNameToModuleName(genFilePath, genFilePath); - const moduleName = importModule === selfReference ? null : importModule; - - // If we are in a type expression that refers to a generic type then supply - // the required type parameters. If there were not enough type parameters - // supplied, supply any as the type. Outside a type expression the reference - // should not supply type parameters and be treated as a simple value reference - // to the constructor function itself. - const suppliedTypeParams = typeParams || []; - const missingTypeParamsCount = arity - suppliedTypeParams.length; - const allTypeParams = - suppliedTypeParams.concat(newArray(missingTypeParamsCount, o.DYNAMIC_TYPE)); - return members.reduce( - (expr, memberName) => expr.prop(memberName), - o.importExpr( - new o.ExternalReference(moduleName, name, null), allTypeParams)); - }; - - return {statements: [], genFilePath, importExpr, constantPool: new ConstantPool()}; - } - - private _fileNameToModuleName(importedFilePath: string, containingFilePath: string): string { - return this._summaryResolver.getKnownModuleName(importedFilePath) || - this._symbolResolver.getKnownModuleName(importedFilePath) || - this._host.fileNameToModuleName(importedFilePath, containingFilePath); - } - - private _codegenStyles( - srcFileUrl: string, compMeta: CompileDirectiveMetadata, - stylesheetMetadata: CompileStylesheetMetadata, isShimmed: boolean, - fileSuffix: string): GeneratedFile { - const outputCtx = this._createOutputContext( - _stylesModuleUrl(stylesheetMetadata.moduleUrl!, isShimmed, fileSuffix)); - const compiledStylesheet = - this._styleCompiler.compileStyles(outputCtx, compMeta, stylesheetMetadata, isShimmed); - _resolveStyleStatements(this._symbolResolver, compiledStylesheet, isShimmed, fileSuffix); - return this._codegenSourceModule(srcFileUrl, outputCtx); - } - - private _codegenSourceModule(srcFileUrl: string, ctx: OutputContext): GeneratedFile { - return new GeneratedFile(srcFileUrl, ctx.genFilePath, ctx.statements); - } -} - -function _createEmptyStub(outputCtx: OutputContext) { - // Note: We need to produce at least one import statement so that - // TypeScript knows that the file is an es6 module. Otherwise our generated - // exports / imports won't be emitted properly by TypeScript. - outputCtx.statements.push(o.importExpr(Identifiers.ComponentFactory).toStmt()); -} - - -function _resolveStyleStatements( - symbolResolver: StaticSymbolResolver, compileResult: CompiledStylesheet, needsShim: boolean, - fileSuffix: string): void { - compileResult.dependencies.forEach((dep) => { - dep.setValue(symbolResolver.getStaticSymbol( - _stylesModuleUrl(dep.moduleUrl, needsShim, fileSuffix), dep.name)); - }); -} - -function _stylesModuleUrl(stylesheetUrl: string, shim: boolean, suffix: string): string { - return `${stylesheetUrl}${shim ? '.shim' : ''}.ngstyle${suffix}`; -} - -export interface NgAnalyzedModules { - ngModules: CompileNgModuleMetadata[]; - ngModuleByPipeOrDirective: Map; - files: NgAnalyzedFile[]; - symbolsMissingModule?: StaticSymbol[]; -} - -export interface NgAnalyzedFileWithInjectables { - fileName: string; - injectables: CompileInjectableMetadata[]; - shallowModules: CompileShallowModuleMetadata[]; -} - -export interface NgAnalyzedFile { - fileName: string; - directives: StaticSymbol[]; - abstractDirectives: StaticSymbol[]; - pipes: StaticSymbol[]; - ngModules: CompileNgModuleMetadata[]; - injectables: CompileInjectableMetadata[]; - exportsNonSourceFiles: boolean; -} - -export interface NgAnalyzeModulesHost { - isSourceFile(filePath: string): boolean; -} - -export function analyzeNgModules( - fileNames: string[], host: NgAnalyzeModulesHost, staticSymbolResolver: StaticSymbolResolver, - metadataResolver: CompileMetadataResolver): NgAnalyzedModules { - const files = _analyzeFilesIncludingNonProgramFiles( - fileNames, host, staticSymbolResolver, metadataResolver); - return mergeAnalyzedFiles(files); -} - -export function analyzeAndValidateNgModules( - fileNames: string[], host: NgAnalyzeModulesHost, staticSymbolResolver: StaticSymbolResolver, - metadataResolver: CompileMetadataResolver): NgAnalyzedModules { - return validateAnalyzedModules( - analyzeNgModules(fileNames, host, staticSymbolResolver, metadataResolver)); -} - -function validateAnalyzedModules(analyzedModules: NgAnalyzedModules): NgAnalyzedModules { - if (analyzedModules.symbolsMissingModule && analyzedModules.symbolsMissingModule.length) { - const messages = analyzedModules.symbolsMissingModule.map( - s => `Cannot determine the module for class ${s.name} in ${s.filePath}! Add ${ - s.name} to the NgModule to fix it.`); - throw syntaxError(messages.join('\n')); - } - return analyzedModules; -} - -// Analyzes all of the program files, -// including files that are not part of the program -// but are referenced by an NgModule. -function _analyzeFilesIncludingNonProgramFiles( - fileNames: string[], host: NgAnalyzeModulesHost, staticSymbolResolver: StaticSymbolResolver, - metadataResolver: CompileMetadataResolver): NgAnalyzedFile[] { - const seenFiles = new Set(); - const files: NgAnalyzedFile[] = []; - - const visitFile = (fileName: string) => { - if (seenFiles.has(fileName) || !host.isSourceFile(fileName)) { - return false; - } - seenFiles.add(fileName); - const analyzedFile = analyzeFile(host, staticSymbolResolver, metadataResolver, fileName); - files.push(analyzedFile); - analyzedFile.ngModules.forEach(ngModule => { - ngModule.transitiveModule.modules.forEach(modMeta => visitFile(modMeta.reference.filePath)); - }); - }; - fileNames.forEach((fileName) => visitFile(fileName)); - return files; -} - -export function analyzeFile( - host: NgAnalyzeModulesHost, staticSymbolResolver: StaticSymbolResolver, - metadataResolver: CompileMetadataResolver, fileName: string): NgAnalyzedFile { - const abstractDirectives: StaticSymbol[] = []; - const directives: StaticSymbol[] = []; - const pipes: StaticSymbol[] = []; - const injectables: CompileInjectableMetadata[] = []; - const ngModules: CompileNgModuleMetadata[] = []; - const hasDecorators = staticSymbolResolver.hasDecorators(fileName); - let exportsNonSourceFiles = false; - const isDeclarationFile = fileName.endsWith('.d.ts'); - // Don't analyze .d.ts files that have no decorators as a shortcut - // to speed up the analysis. This prevents us from - // resolving the references in these files. - // Note: exportsNonSourceFiles is only needed when compiling with summaries, - // which is not the case when .d.ts files are treated as input files. - if (!isDeclarationFile || hasDecorators) { - staticSymbolResolver.getSymbolsOf(fileName).forEach((symbol) => { - const resolvedSymbol = staticSymbolResolver.resolveSymbol(symbol); - const symbolMeta = resolvedSymbol.metadata; - if (!symbolMeta || symbolMeta.__symbolic === 'error') { - return; - } - let isNgSymbol = false; - if (symbolMeta.__symbolic === 'class') { - if (metadataResolver.isDirective(symbol)) { - isNgSymbol = true; - // This directive either has a selector or doesn't. Selector-less directives get tracked - // in abstractDirectives, not directives. The compiler doesn't deal with selector-less - // directives at all, really, other than to persist their metadata. This is done so that - // apps will have an easier time migrating to Ivy, which requires the selector-less - // annotations to be applied. - if (!metadataResolver.isAbstractDirective(symbol)) { - // The directive is an ordinary directive. - directives.push(symbol); - } else { - // The directive has no selector and is an "abstract" directive, so track it - // accordingly. - abstractDirectives.push(symbol); - } - } else if (metadataResolver.isPipe(symbol)) { - isNgSymbol = true; - pipes.push(symbol); - } else if (metadataResolver.isNgModule(symbol)) { - const ngModule = metadataResolver.getNgModuleMetadata(symbol, false); - if (ngModule) { - isNgSymbol = true; - ngModules.push(ngModule); - } - } else if (metadataResolver.isInjectable(symbol)) { - isNgSymbol = true; - const injectable = metadataResolver.getInjectableMetadata(symbol, null, false); - if (injectable) { - injectables.push(injectable); - } - } - } - if (!isNgSymbol) { - exportsNonSourceFiles = - exportsNonSourceFiles || isValueExportingNonSourceFile(host, symbolMeta); - } - }); - } - return { - fileName, - directives, - abstractDirectives, - pipes, - ngModules, - injectables, - exportsNonSourceFiles, - }; -} - -export function analyzeFileForInjectables( - host: NgAnalyzeModulesHost, staticSymbolResolver: StaticSymbolResolver, - metadataResolver: CompileMetadataResolver, fileName: string): NgAnalyzedFileWithInjectables { - const injectables: CompileInjectableMetadata[] = []; - const shallowModules: CompileShallowModuleMetadata[] = []; - if (staticSymbolResolver.hasDecorators(fileName)) { - staticSymbolResolver.getSymbolsOf(fileName).forEach((symbol) => { - const resolvedSymbol = staticSymbolResolver.resolveSymbol(symbol); - const symbolMeta = resolvedSymbol.metadata; - if (!symbolMeta || symbolMeta.__symbolic === 'error') { - return; - } - if (symbolMeta.__symbolic === 'class') { - if (metadataResolver.isInjectable(symbol)) { - const injectable = metadataResolver.getInjectableMetadata(symbol, null, false); - if (injectable) { - injectables.push(injectable); - } - } else if (metadataResolver.isNgModule(symbol)) { - const module = metadataResolver.getShallowModuleMetadata(symbol); - if (module) { - shallowModules.push(module); - } - } - } - }); - } - return {fileName, injectables, shallowModules}; -} - -function isValueExportingNonSourceFile(host: NgAnalyzeModulesHost, metadata: any): boolean { - let exportsNonSourceFiles = false; - - class Visitor implements ValueVisitor { - visitArray(arr: any[], context: any): any { - arr.forEach(v => visitValue(v, this, context)); - } - visitStringMap(map: {[key: string]: any}, context: any): any { - Object.keys(map).forEach((key) => visitValue(map[key], this, context)); - } - visitPrimitive(value: any, context: any): any {} - visitOther(value: any, context: any): any { - if (value instanceof StaticSymbol && !host.isSourceFile(value.filePath)) { - exportsNonSourceFiles = true; - } - } - } - - visitValue(metadata, new Visitor(), null); - return exportsNonSourceFiles; -} - -export function mergeAnalyzedFiles(analyzedFiles: NgAnalyzedFile[]): NgAnalyzedModules { - const allNgModules: CompileNgModuleMetadata[] = []; - const ngModuleByPipeOrDirective = new Map(); - const allPipesAndDirectives = new Set(); - - analyzedFiles.forEach(af => { - af.ngModules.forEach(ngModule => { - allNgModules.push(ngModule); - ngModule.declaredDirectives.forEach( - d => ngModuleByPipeOrDirective.set(d.reference, ngModule)); - ngModule.declaredPipes.forEach(p => ngModuleByPipeOrDirective.set(p.reference, ngModule)); - }); - af.directives.forEach(d => allPipesAndDirectives.add(d)); - af.pipes.forEach(p => allPipesAndDirectives.add(p)); - }); - - const symbolsMissingModule: StaticSymbol[] = []; - allPipesAndDirectives.forEach(ref => { - if (!ngModuleByPipeOrDirective.has(ref)) { - symbolsMissingModule.push(ref); - } - }); - return { - ngModules: allNgModules, - ngModuleByPipeOrDirective, - symbolsMissingModule, - files: analyzedFiles - }; -} - -function mergeAndValidateNgFiles(files: NgAnalyzedFile[]): NgAnalyzedModules { - return validateAnalyzedModules(mergeAnalyzedFiles(files)); -} diff --git a/packages/compiler/src/aot/compiler_factory.ts b/packages/compiler/src/aot/compiler_factory.ts deleted file mode 100644 index 7743555b169..00000000000 --- a/packages/compiler/src/aot/compiler_factory.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {CompilerConfig} from '../config'; -import {ViewEncapsulation} from '../core'; -import {DirectiveNormalizer} from '../directive_normalizer'; -import {DirectiveResolver} from '../directive_resolver'; -import {Lexer} from '../expression_parser/lexer'; -import {Parser} from '../expression_parser/parser'; -import {I18NHtmlParser} from '../i18n/i18n_html_parser'; -import {InjectableCompiler} from '../injectable_compiler'; -import {CompileMetadataResolver} from '../metadata_resolver'; -import {HtmlParser} from '../ml_parser/html_parser'; -import {NgModuleCompiler} from '../ng_module_compiler'; -import {NgModuleResolver} from '../ng_module_resolver'; -import {TypeScriptEmitter} from '../output/ts_emitter'; -import {syntaxError} from '../parse_util'; -import {PipeResolver} from '../pipe_resolver'; -import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry'; -import {StyleCompiler} from '../style_compiler'; -import {TemplateParser} from '../template_parser/template_parser'; -import {UrlResolver} from '../url_resolver'; -import {TypeCheckCompiler} from '../view_compiler/type_check_compiler'; -import {ViewCompiler} from '../view_compiler/view_compiler'; - -import {AotCompiler} from './compiler'; -import {AotCompilerHost} from './compiler_host'; -import {AotCompilerOptions} from './compiler_options'; -import {StaticReflector} from './static_reflector'; -import {StaticSymbolCache} from './static_symbol'; -import {StaticSymbolResolver} from './static_symbol_resolver'; -import {AotSummaryResolver} from './summary_resolver'; - -export function createAotUrlResolver( - host: {resourceNameToFileName(resourceName: string, containingFileName: string): string|null;}): - UrlResolver { - return { - resolve: (basePath: string, url: string) => { - const filePath = host.resourceNameToFileName(url, basePath); - if (!filePath) { - throw syntaxError(`Couldn't resolve resource ${url} from ${basePath}`); - } - return filePath; - } - }; -} - -/** - * Creates a new AotCompiler based on options and a host. - */ -export function createAotCompiler( - compilerHost: AotCompilerHost, options: AotCompilerOptions, - errorCollector?: (error: any, type?: any) => - void): {compiler: AotCompiler, reflector: StaticReflector} { - let translations: string = options.translations || ''; - - const urlResolver = createAotUrlResolver(compilerHost); - const symbolCache = new StaticSymbolCache(); - const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache); - const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver); - const staticReflector = - new StaticReflector(summaryResolver, symbolResolver, [], [], errorCollector); - let htmlParser: I18NHtmlParser; - if (!!options.enableIvy) { - // Ivy handles i18n at the compiler level so we must use a regular parser - htmlParser = new HtmlParser() as I18NHtmlParser; - } else { - htmlParser = new I18NHtmlParser( - new HtmlParser(), translations, options.i18nFormat, options.missingTranslation, console); - } - const config = new CompilerConfig({ - defaultEncapsulation: ViewEncapsulation.Emulated, - useJit: false, - missingTranslation: options.missingTranslation, - preserveWhitespaces: options.preserveWhitespaces, - strictInjectionParameters: options.strictInjectionParameters, - }); - const normalizer = new DirectiveNormalizer( - {get: (url: string) => compilerHost.loadResource(url)}, urlResolver, htmlParser, config); - const expressionParser = new Parser(new Lexer()); - const elementSchemaRegistry = new DomElementSchemaRegistry(); - const tmplParser = new TemplateParser( - config, staticReflector, expressionParser, elementSchemaRegistry, htmlParser, console, []); - const resolver = new CompileMetadataResolver( - config, htmlParser, new NgModuleResolver(staticReflector), - new DirectiveResolver(staticReflector), new PipeResolver(staticReflector), summaryResolver, - elementSchemaRegistry, normalizer, console, symbolCache, staticReflector, errorCollector); - // TODO(vicb): do not pass options.i18nFormat here - const viewCompiler = new ViewCompiler(staticReflector); - const typeCheckCompiler = new TypeCheckCompiler(options, staticReflector); - const compiler = new AotCompiler( - config, options, compilerHost, staticReflector, resolver, tmplParser, - new StyleCompiler(urlResolver), viewCompiler, typeCheckCompiler, - new NgModuleCompiler(staticReflector), - new InjectableCompiler(staticReflector, !!options.enableIvy), new TypeScriptEmitter(), - summaryResolver, symbolResolver); - return {compiler, reflector: staticReflector}; -} diff --git a/packages/compiler/src/aot/compiler_host.ts b/packages/compiler/src/aot/compiler_host.ts deleted file mode 100644 index 82c4bb6e65a..00000000000 --- a/packages/compiler/src/aot/compiler_host.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {StaticSymbolResolverHost} from './static_symbol_resolver'; -import {AotSummaryResolverHost} from './summary_resolver'; - -/** - * The host of the AotCompiler disconnects the implementation from TypeScript / other language - * services and from underlying file systems. - */ -export interface AotCompilerHost extends StaticSymbolResolverHost, AotSummaryResolverHost { - /** - * Converts a file path to a module name that can be used as an `import. - * I.e. `path/to/importedFile.ts` should be imported by `path/to/containingFile.ts`. - * - * See ImportResolver. - */ - fileNameToModuleName(importedFilePath: string, containingFilePath: string): string; - /** - * Converts a path that refers to a resource into an absolute filePath - * that can be later on used for loading the resource via `loadResource. - */ - resourceNameToFileName(resourceName: string, containingFileName: string): string|null; - /** - * Loads a resource (e.g. html / css) - */ - loadResource(path: string): Promise|string; -} diff --git a/packages/compiler/src/aot/compiler_options.ts b/packages/compiler/src/aot/compiler_options.ts deleted file mode 100644 index 72bcf639662..00000000000 --- a/packages/compiler/src/aot/compiler_options.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {MissingTranslationStrategy} from '../core'; - -export interface AotCompilerOptions { - locale?: string; - i18nFormat?: string; - i18nUseExternalIds?: boolean; - translations?: string; - missingTranslation?: MissingTranslationStrategy; - enableSummariesForJit?: boolean; - preserveWhitespaces?: boolean; - fullTemplateTypeCheck?: boolean; - allowEmptyCodegenFiles?: boolean; - strictInjectionParameters?: boolean; - enableIvy?: boolean|'ngtsc'; - createExternalSymbolFactoryReexports?: boolean; -} diff --git a/packages/compiler/src/aot/formatted_error.ts b/packages/compiler/src/aot/formatted_error.ts deleted file mode 100644 index 7008329a08b..00000000000 --- a/packages/compiler/src/aot/formatted_error.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {syntaxError} from '../parse_util'; - -export interface Position { - fileName: string; - line: number; - column: number; -} - -export interface FormattedMessageChain { - message: string; - position?: Position; - next?: FormattedMessageChain[]; -} - -export type FormattedError = Error&{ - chain: FormattedMessageChain; - position?: Position; -}; - -const FORMATTED_MESSAGE = 'ngFormattedMessage'; - -function indentStr(level: number): string { - if (level <= 0) return ''; - if (level < 6) return ['', ' ', ' ', ' ', ' ', ' '][level]; - const half = indentStr(Math.floor(level / 2)); - return half + half + (level % 2 === 1 ? ' ' : ''); -} - -function formatChain(chain: FormattedMessageChain|undefined, indent: number = 0): string { - if (!chain) return ''; - const position = chain.position ? - `${chain.position.fileName}(${chain.position.line + 1},${chain.position.column + 1})` : - ''; - const prefix = position && indent === 0 ? `${position}: ` : ''; - const postfix = position && indent !== 0 ? ` at ${position}` : ''; - let message = `${prefix}${chain.message}${postfix}`; - - if (chain.next) { - for (const kid of chain.next) { - message += '\n' + formatChain(kid, indent + 2); - } - } - - return `${indentStr(indent)}${message}`; -} - -export function formattedError(chain: FormattedMessageChain): FormattedError { - const message = formatChain(chain) + '.'; - const error = syntaxError(message) as FormattedError; - (error as any)[FORMATTED_MESSAGE] = true; - error.chain = chain; - error.position = chain.position; - return error; -} - -export function isFormattedError(error: Error): error is FormattedError { - return !!(error as any)[FORMATTED_MESSAGE]; -} diff --git a/packages/compiler/src/aot/generated_file.ts b/packages/compiler/src/aot/generated_file.ts deleted file mode 100644 index 2a69a65ef3a..00000000000 --- a/packages/compiler/src/aot/generated_file.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {areAllEquivalent, Statement} from '../output/output_ast'; -import {TypeScriptEmitter} from '../output/ts_emitter'; - -export class GeneratedFile { - public source: string|null; - public stmts: Statement[]|null; - - constructor( - public srcFileUrl: string, public genFileUrl: string, sourceOrStmts: string|Statement[]) { - if (typeof sourceOrStmts === 'string') { - this.source = sourceOrStmts; - this.stmts = null; - } else { - this.source = null; - this.stmts = sourceOrStmts; - } - } - - isEquivalent(other: GeneratedFile): boolean { - if (this.genFileUrl !== other.genFileUrl) { - return false; - } - if (this.source) { - return this.source === other.source; - } - if (other.stmts == null) { - return false; - } - // Note: the constructor guarantees that if this.source is not filled, - // then this.stmts is. - return areAllEquivalent(this.stmts!, other.stmts!); - } -} - -export function toTypeScript(file: GeneratedFile, preamble: string = ''): string { - if (!file.stmts) { - throw new Error(`Illegal state: No stmts present on GeneratedFile ${file.genFileUrl}`); - } - return new TypeScriptEmitter().emitStatements(file.genFileUrl, file.stmts, preamble); -} diff --git a/packages/compiler/src/aot/partial_module.ts b/packages/compiler/src/aot/partial_module.ts deleted file mode 100644 index 105af12e2a8..00000000000 --- a/packages/compiler/src/aot/partial_module.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 o from '../output/output_ast'; - -export interface PartialModule { - fileName: string; - statements: o.Statement[]; -} diff --git a/packages/compiler/src/aot/static_reflector.ts b/packages/compiler/src/aot/static_reflector.ts deleted file mode 100644 index e2e7b1e4f18..00000000000 --- a/packages/compiler/src/aot/static_reflector.ts +++ /dev/null @@ -1,1090 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {CompileSummaryKind} from '../compile_metadata'; -import {CompileReflector} from '../compile_reflector'; -import {createAttribute, createComponent, createContentChild, createContentChildren, createDirective, createHost, createHostBinding, createHostListener, createInject, createInjectable, createInput, createNgModule, createOptional, createOutput, createPipe, createSelf, createSkipSelf, createViewChild, createViewChildren, MetadataFactory} from '../core'; -import * as o from '../output/output_ast'; -import {syntaxError} from '../parse_util'; -import {SummaryResolver} from '../summary_resolver'; - -import {formattedError, FormattedMessageChain} from './formatted_error'; -import {StaticSymbol} from './static_symbol'; -import {StaticSymbolResolver} from './static_symbol_resolver'; - -const ANGULAR_CORE = '@angular/core'; -const ANGULAR_ROUTER = '@angular/router'; - -const HIDDEN_KEY = /^\$.*\$$/; - -const IGNORE = { - __symbolic: 'ignore' -}; - -const USE_VALUE = 'useValue'; -const PROVIDE = 'provide'; -const REFERENCE_SET = new Set([USE_VALUE, 'useFactory', 'data', 'id', 'loadChildren']); -const TYPEGUARD_POSTFIX = 'TypeGuard'; -const USE_IF = 'UseIf'; - -function shouldIgnore(value: any): boolean { - return value && value.__symbolic == 'ignore'; -} - -/** - * A static reflector implements enough of the Reflector API that is necessary to compile - * templates statically. - */ -export class StaticReflector implements CompileReflector { - private annotationCache = new Map(); - private shallowAnnotationCache = new Map(); - private propertyCache = new Map(); - private parameterCache = new Map(); - private methodCache = new Map(); - private staticCache = new Map(); - private conversionMap = new Map any>(); - private resolvedExternalReferences = new Map(); - // TODO(issue/24571): remove '!'. - private injectionToken!: StaticSymbol; - // TODO(issue/24571): remove '!'. - private opaqueToken!: StaticSymbol; - // TODO(issue/24571): remove '!'. - ROUTES!: StaticSymbol; - // TODO(issue/24571): remove '!'. - private ANALYZE_FOR_ENTRY_COMPONENTS!: StaticSymbol; - private annotationForParentClassWithSummaryKind = - new Map[]>(); - - constructor( - private summaryResolver: SummaryResolver, - private symbolResolver: StaticSymbolResolver, - knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [], - knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = [], - private errorRecorder?: (error: any, fileName?: string) => void) { - this.initializeConversionMap(); - knownMetadataClasses.forEach( - (kc) => this._registerDecoratorOrConstructor( - this.getStaticSymbol(kc.filePath, kc.name), kc.ctor)); - knownMetadataFunctions.forEach( - (kf) => this._registerFunction(this.getStaticSymbol(kf.filePath, kf.name), kf.fn)); - this.annotationForParentClassWithSummaryKind.set( - CompileSummaryKind.Directive, [createDirective, createComponent]); - this.annotationForParentClassWithSummaryKind.set(CompileSummaryKind.Pipe, [createPipe]); - this.annotationForParentClassWithSummaryKind.set(CompileSummaryKind.NgModule, [createNgModule]); - this.annotationForParentClassWithSummaryKind.set( - CompileSummaryKind.Injectable, - [createInjectable, createPipe, createDirective, createComponent, createNgModule]); - } - - componentModuleUrl(typeOrFunc: StaticSymbol): string { - const staticSymbol = this.findSymbolDeclaration(typeOrFunc); - return this.symbolResolver.getResourcePath(staticSymbol); - } - - /** - * Invalidate the specified `symbols` on program change. - * @param symbols - */ - invalidateSymbols(symbols: StaticSymbol[]) { - for (const symbol of symbols) { - this.annotationCache.delete(symbol); - this.shallowAnnotationCache.delete(symbol); - this.propertyCache.delete(symbol); - this.parameterCache.delete(symbol); - this.methodCache.delete(symbol); - this.staticCache.delete(symbol); - this.conversionMap.delete(symbol); - } - } - - resolveExternalReference(ref: o.ExternalReference, containingFile?: string): StaticSymbol { - let key: string|undefined = undefined; - if (!containingFile) { - key = `${ref.moduleName}:${ref.name}`; - const declarationSymbol = this.resolvedExternalReferences.get(key); - if (declarationSymbol) return declarationSymbol; - } - const refSymbol = - this.symbolResolver.getSymbolByModule(ref.moduleName!, ref.name!, containingFile); - const declarationSymbol = this.findSymbolDeclaration(refSymbol); - if (!containingFile) { - this.symbolResolver.recordModuleNameForFileName(refSymbol.filePath, ref.moduleName!); - this.symbolResolver.recordImportAs(declarationSymbol, refSymbol); - } - if (key) { - this.resolvedExternalReferences.set(key, declarationSymbol); - } - return declarationSymbol; - } - - findDeclaration(moduleUrl: string, name: string, containingFile?: string): StaticSymbol { - return this.findSymbolDeclaration( - this.symbolResolver.getSymbolByModule(moduleUrl, name, containingFile)); - } - - tryFindDeclaration(moduleUrl: string, name: string, containingFile?: string): StaticSymbol { - return this.symbolResolver.ignoreErrorsFor( - () => this.findDeclaration(moduleUrl, name, containingFile)); - } - - findSymbolDeclaration(symbol: StaticSymbol): StaticSymbol { - const resolvedSymbol = this.symbolResolver.resolveSymbol(symbol); - if (resolvedSymbol) { - let resolvedMetadata = resolvedSymbol.metadata; - if (resolvedMetadata && resolvedMetadata.__symbolic === 'resolved') { - resolvedMetadata = resolvedMetadata.symbol; - } - if (resolvedMetadata instanceof StaticSymbol) { - return this.findSymbolDeclaration(resolvedSymbol.metadata); - } - } - return symbol; - } - - public tryAnnotations(type: StaticSymbol): any[] { - const originalRecorder = this.errorRecorder; - this.errorRecorder = (error: any, fileName?: string) => {}; - try { - return this.annotations(type); - } finally { - this.errorRecorder = originalRecorder; - } - } - - public annotations(type: StaticSymbol): any[] { - return this._annotations( - type, (type: StaticSymbol, decorators: any) => this.simplify(type, decorators), - this.annotationCache); - } - - public shallowAnnotations(type: StaticSymbol): any[] { - return this._annotations( - type, (type: StaticSymbol, decorators: any) => this.simplify(type, decorators, true), - this.shallowAnnotationCache); - } - - private _annotations( - type: StaticSymbol, simplify: (type: StaticSymbol, decorators: any) => any, - annotationCache: Map): any[] { - let annotations = annotationCache.get(type); - if (!annotations) { - annotations = []; - const classMetadata = this.getTypeMetadata(type); - const parentType = this.findParentType(type, classMetadata); - if (parentType) { - const parentAnnotations = this.annotations(parentType); - annotations.push(...parentAnnotations); - } - let ownAnnotations: any[] = []; - if (classMetadata['decorators']) { - ownAnnotations = simplify(type, classMetadata['decorators']); - if (ownAnnotations) { - annotations.push(...ownAnnotations); - } - } - if (parentType && !this.summaryResolver.isLibraryFile(type.filePath) && - this.summaryResolver.isLibraryFile(parentType.filePath)) { - const summary = this.summaryResolver.resolveSummary(parentType); - if (summary && summary.type) { - const requiredAnnotationTypes = - this.annotationForParentClassWithSummaryKind.get(summary.type.summaryKind!)!; - const typeHasRequiredAnnotation = requiredAnnotationTypes.some( - (requiredType) => ownAnnotations.some(ann => requiredType.isTypeOf(ann))); - if (!typeHasRequiredAnnotation) { - this.reportError( - formatMetadataError( - metadataError( - `Class ${type.name} in ${type.filePath} extends from a ${ - CompileSummaryKind[summary.type.summaryKind! - ]} in another compilation unit without duplicating the decorator`, - /* summary */ undefined, - `Please add a ${ - requiredAnnotationTypes.map((type) => type.ngMetadataName) - .join(' or ')} decorator to the class`), - type), - type); - } - } - } - annotationCache.set(type, annotations.filter(ann => !!ann)); - } - return annotations; - } - - public propMetadata(type: StaticSymbol): {[key: string]: any[]} { - let propMetadata = this.propertyCache.get(type); - if (!propMetadata) { - const classMetadata = this.getTypeMetadata(type); - propMetadata = {}; - const parentType = this.findParentType(type, classMetadata); - if (parentType) { - const parentPropMetadata = this.propMetadata(parentType); - Object.keys(parentPropMetadata).forEach((parentProp) => { - propMetadata![parentProp] = parentPropMetadata[parentProp]; - }); - } - - const members = classMetadata['members'] || {}; - Object.keys(members).forEach((propName) => { - const propData = members[propName]; - const prop = (propData) - .find(a => a['__symbolic'] == 'property' || a['__symbolic'] == 'method'); - const decorators: any[] = []; - // hasOwnProperty() is used here to make sure we do not look up methods - // on `Object.prototype`. - if (propMetadata?.hasOwnProperty(propName)) { - decorators.push(...propMetadata![propName]); - } - propMetadata![propName] = decorators; - if (prop && prop['decorators']) { - decorators.push(...this.simplify(type, prop['decorators'])); - } - }); - this.propertyCache.set(type, propMetadata); - } - return propMetadata; - } - - public parameters(type: StaticSymbol): any[] { - if (!(type instanceof StaticSymbol)) { - this.reportError( - new Error(`parameters received ${JSON.stringify(type)} which is not a StaticSymbol`), - type); - return []; - } - try { - let parameters = this.parameterCache.get(type); - if (!parameters) { - const classMetadata = this.getTypeMetadata(type); - const parentType = this.findParentType(type, classMetadata); - const members = classMetadata ? classMetadata['members'] : null; - const ctorData = members ? members['__ctor__'] : null; - if (ctorData) { - const ctor = (ctorData).find(a => a['__symbolic'] == 'constructor'); - const rawParameterTypes = ctor['parameters'] || []; - const parameterDecorators = this.simplify(type, ctor['parameterDecorators'] || []); - parameters = []; - rawParameterTypes.forEach((rawParamType, index) => { - const nestedResult: any[] = []; - const paramType = this.trySimplify(type, rawParamType); - if (paramType) nestedResult.push(paramType); - const decorators = parameterDecorators ? parameterDecorators[index] : null; - if (decorators) { - nestedResult.push(...decorators); - } - parameters!.push(nestedResult); - }); - } else if (parentType) { - parameters = this.parameters(parentType); - } - if (!parameters) { - parameters = []; - } - this.parameterCache.set(type, parameters); - } - return parameters; - } catch (e) { - console.error(`Failed on type ${JSON.stringify(type)} with error ${e}`); - throw e; - } - } - - private _methodNames(type: any): {[key: string]: boolean} { - let methodNames = this.methodCache.get(type); - if (!methodNames) { - const classMetadata = this.getTypeMetadata(type); - methodNames = {}; - const parentType = this.findParentType(type, classMetadata); - if (parentType) { - const parentMethodNames = this._methodNames(parentType); - Object.keys(parentMethodNames).forEach((parentProp) => { - methodNames![parentProp] = parentMethodNames[parentProp]; - }); - } - - const members = classMetadata['members'] || {}; - Object.keys(members).forEach((propName) => { - const propData = members[propName]; - const isMethod = (propData).some(a => a['__symbolic'] == 'method'); - methodNames![propName] = methodNames![propName] || isMethod; - }); - this.methodCache.set(type, methodNames); - } - return methodNames; - } - - private _staticMembers(type: StaticSymbol): string[] { - let staticMembers = this.staticCache.get(type); - if (!staticMembers) { - const classMetadata = this.getTypeMetadata(type); - const staticMemberData = classMetadata['statics'] || {}; - staticMembers = Object.keys(staticMemberData); - this.staticCache.set(type, staticMembers); - } - return staticMembers; - } - - - private findParentType(type: StaticSymbol, classMetadata: any): StaticSymbol|undefined { - const parentType = this.trySimplify(type, classMetadata['extends']); - if (parentType instanceof StaticSymbol) { - return parentType; - } - } - - hasLifecycleHook(type: any, lcProperty: string): boolean { - if (!(type instanceof StaticSymbol)) { - this.reportError( - new Error( - `hasLifecycleHook received ${JSON.stringify(type)} which is not a StaticSymbol`), - type); - } - try { - return !!this._methodNames(type)[lcProperty]; - } catch (e) { - console.error(`Failed on type ${JSON.stringify(type)} with error ${e}`); - throw e; - } - } - - guards(type: any): {[key: string]: StaticSymbol} { - if (!(type instanceof StaticSymbol)) { - this.reportError( - new Error(`guards received ${JSON.stringify(type)} which is not a StaticSymbol`), type); - return {}; - } - const staticMembers = this._staticMembers(type); - const result: {[key: string]: StaticSymbol} = {}; - for (let name of staticMembers) { - if (name.endsWith(TYPEGUARD_POSTFIX)) { - let property = name.substr(0, name.length - TYPEGUARD_POSTFIX.length); - let value: any; - if (property.endsWith(USE_IF)) { - property = name.substr(0, property.length - USE_IF.length); - value = USE_IF; - } else { - value = this.getStaticSymbol(type.filePath, type.name, [name]); - } - result[property] = value; - } - } - return result; - } - - private _registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void { - this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => new ctor(...args)); - } - - private _registerFunction(type: StaticSymbol, fn: any): void { - this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => fn.apply(undefined, args)); - } - - private initializeConversionMap(): void { - this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'Injectable'), createInjectable); - this.injectionToken = this.findDeclaration(ANGULAR_CORE, 'InjectionToken'); - this.opaqueToken = this.findDeclaration(ANGULAR_CORE, 'OpaqueToken'); - this.ROUTES = this.tryFindDeclaration(ANGULAR_ROUTER, 'ROUTES'); - this.ANALYZE_FOR_ENTRY_COMPONENTS = - this.findDeclaration(ANGULAR_CORE, 'ANALYZE_FOR_ENTRY_COMPONENTS'); - - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Host'), createHost); - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Self'), createSelf); - this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'SkipSelf'), createSkipSelf); - this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'Inject'), createInject); - this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'Optional'), createOptional); - this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'Attribute'), createAttribute); - this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'ContentChild'), createContentChild); - this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'ContentChildren'), createContentChildren); - this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'ViewChild'), createViewChild); - this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'ViewChildren'), createViewChildren); - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Input'), createInput); - this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'Output'), createOutput); - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Pipe'), createPipe); - this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'HostBinding'), createHostBinding); - this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'HostListener'), createHostListener); - this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'Directive'), createDirective); - this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'Component'), createComponent); - this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'NgModule'), createNgModule); - - // Note: Some metadata classes can be used directly with Provider.deps. - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Host'), createHost); - this._registerDecoratorOrConstructor(this.findDeclaration(ANGULAR_CORE, 'Self'), createSelf); - this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'SkipSelf'), createSkipSelf); - this._registerDecoratorOrConstructor( - this.findDeclaration(ANGULAR_CORE, 'Optional'), createOptional); - } - - /** - * 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.symbolResolver.getStaticSymbol(declarationFile, name, members); - } - - /** - * Simplify but discard any errors - */ - private trySimplify(context: StaticSymbol, value: any): any { - const originalRecorder = this.errorRecorder; - this.errorRecorder = (error: any, fileName?: string) => {}; - const result = this.simplify(context, value); - this.errorRecorder = originalRecorder; - return result; - } - - /** @internal */ - public simplify(context: StaticSymbol, value: any, lazy: boolean = false): any { - const self = this; - let scope = BindingScope.empty; - const calling = new Map(); - const rootContext = context; - - function simplifyInContext( - context: StaticSymbol, value: any, depth: number, references: number): any { - function resolveReferenceValue(staticSymbol: StaticSymbol): any { - const resolvedSymbol = self.symbolResolver.resolveSymbol(staticSymbol); - return resolvedSymbol ? resolvedSymbol.metadata : null; - } - - function simplifyEagerly(value: any): any { - return simplifyInContext(context, value, depth, 0); - } - - function simplifyLazily(value: any): any { - return simplifyInContext(context, value, depth, references + 1); - } - - function simplifyNested(nestedContext: StaticSymbol, value: any): any { - if (nestedContext === context) { - // If the context hasn't changed let the exception propagate unmodified. - return simplifyInContext(nestedContext, value, depth + 1, references); - } - try { - return simplifyInContext(nestedContext, value, depth + 1, references); - } catch (e) { - if (isMetadataError(e)) { - // Propagate the message text up but add a message to the chain that explains how we got - // here. - // e.chain implies e.symbol - const summaryMsg = e.chain ? 'references \'' + e.symbol!.name + '\'' : errorSummary(e); - const summary = `'${nestedContext.name}' ${summaryMsg}`; - const chain = {message: summary, position: e.position, next: e.chain}; - // TODO(chuckj): retrieve the position information indirectly from the collectors node - // map if the metadata is from a .ts file. - self.error( - { - message: e.message, - advise: e.advise, - context: e.context, - chain, - symbol: nestedContext - }, - context); - } else { - // It is probably an internal error. - throw e; - } - } - } - - function simplifyCall( - functionSymbol: StaticSymbol, targetFunction: any, args: any[], targetExpression: any) { - if (targetFunction && targetFunction['__symbolic'] == 'function') { - if (calling.get(functionSymbol)) { - self.error( - { - message: 'Recursion is not supported', - summary: `called '${functionSymbol.name}' recursively`, - value: targetFunction - }, - functionSymbol); - } - try { - const value = targetFunction['value']; - if (value && (depth != 0 || value.__symbolic != 'error')) { - const parameters: string[] = targetFunction['parameters']; - const defaults: any[] = targetFunction.defaults; - args = args.map(arg => simplifyNested(context, arg)) - .map(arg => shouldIgnore(arg) ? undefined : arg); - if (defaults && defaults.length > args.length) { - args.push(...defaults.slice(args.length).map((value: any) => simplify(value))); - } - calling.set(functionSymbol, true); - const functionScope = BindingScope.build(); - for (let i = 0; i < parameters.length; i++) { - functionScope.define(parameters[i], args[i]); - } - const oldScope = scope; - let result: any; - try { - scope = functionScope.done(); - result = simplifyNested(functionSymbol, value); - } finally { - scope = oldScope; - } - return result; - } - } finally { - calling.delete(functionSymbol); - } - } - - if (depth === 0) { - // If depth is 0 we are evaluating the top level expression that is describing element - // decorator. In this case, it is a decorator we don't understand, such as a custom - // non-angular decorator, and we should just ignore it. - return IGNORE; - } - let position: Position|undefined = undefined; - if (targetExpression && targetExpression.__symbolic == 'resolved') { - const line = targetExpression.line; - const character = targetExpression.character; - const fileName = targetExpression.fileName; - if (fileName != null && line != null && character != null) { - position = {fileName, line, column: character}; - } - } - self.error( - { - message: FUNCTION_CALL_NOT_SUPPORTED, - context: functionSymbol, - value: targetFunction, - position - }, - context); - } - - function simplify(expression: any): any { - if (isPrimitive(expression)) { - return expression; - } - if (Array.isArray(expression)) { - const result: any[] = []; - for (const item of (expression)) { - // Check for a spread expression - if (item && item.__symbolic === 'spread') { - // We call with references as 0 because we require the actual value and cannot - // tolerate a reference here. - const spreadArray = simplifyEagerly(item.expression); - if (Array.isArray(spreadArray)) { - for (const spreadItem of spreadArray) { - result.push(spreadItem); - } - continue; - } - } - const value = simplify(item); - if (shouldIgnore(value)) { - continue; - } - result.push(value); - } - return result; - } - if (expression instanceof StaticSymbol) { - // Stop simplification at builtin symbols or if we are in a reference context and - // the symbol doesn't have members. - if (expression === self.injectionToken || self.conversionMap.has(expression) || - (references > 0 && !expression.members.length)) { - return expression; - } else { - const staticSymbol = expression; - const declarationValue = resolveReferenceValue(staticSymbol); - if (declarationValue != null) { - return simplifyNested(staticSymbol, declarationValue); - } else { - return staticSymbol; - } - } - } - if (expression) { - if (expression['__symbolic']) { - let staticSymbol: StaticSymbol; - switch (expression['__symbolic']) { - case 'binop': - let left = simplify(expression['left']); - if (shouldIgnore(left)) return left; - let right = simplify(expression['right']); - if (shouldIgnore(right)) return right; - switch (expression['operator']) { - case '&&': - return left && right; - case '||': - return left || right; - case '|': - return left | right; - case '^': - return left ^ right; - case '&': - return left & right; - case '==': - return left == right; - case '!=': - return left != right; - case '===': - return left === right; - case '!==': - return left !== right; - case '<': - return left < right; - case '>': - return left > right; - case '<=': - return left <= right; - case '>=': - return left >= right; - case '<<': - return left << right; - case '>>': - return left >> right; - case '+': - return left + right; - case '-': - return left - right; - case '*': - return left * right; - case '/': - return left / right; - case '%': - return left % right; - case '??': - return left ?? right; - } - return null; - case 'if': - let condition = simplify(expression['condition']); - return condition ? simplify(expression['thenExpression']) : - simplify(expression['elseExpression']); - case 'pre': - let operand = simplify(expression['operand']); - if (shouldIgnore(operand)) return operand; - switch (expression['operator']) { - case '+': - return operand; - case '-': - return -operand; - case '!': - return !operand; - case '~': - return ~operand; - } - return null; - case 'index': - let indexTarget = simplifyEagerly(expression['expression']); - let index = simplifyEagerly(expression['index']); - if (indexTarget && isPrimitive(index)) return indexTarget[index]; - return null; - case 'select': - const member = expression['member']; - let selectContext = context; - let selectTarget = simplify(expression['expression']); - if (selectTarget instanceof StaticSymbol) { - const members = selectTarget.members.concat(member); - selectContext = - self.getStaticSymbol(selectTarget.filePath, selectTarget.name, members); - const declarationValue = resolveReferenceValue(selectContext); - if (declarationValue != null) { - return simplifyNested(selectContext, declarationValue); - } else { - return selectContext; - } - } - if (selectTarget && isPrimitive(member)) - return simplifyNested(selectContext, selectTarget[member]); - return null; - case 'reference': - // Note: This only has to deal with variable references, as symbol references have - // been converted into 'resolved' - // in the StaticSymbolResolver. - const name: string = expression['name']; - const localValue = scope.resolve(name); - if (localValue != BindingScope.missing) { - return localValue; - } - break; - case 'resolved': - try { - return simplify(expression.symbol); - } catch (e) { - // If an error is reported evaluating the symbol record the position of the - // reference in the error so it can - // be reported in the error message generated from the exception. - if (isMetadataError(e) && expression.fileName != null && - expression.line != null && expression.character != null) { - e.position = { - fileName: expression.fileName, - line: expression.line, - column: expression.character - }; - } - throw e; - } - case 'class': - return context; - case 'function': - return context; - case 'new': - case 'call': - // Determine if the function is a built-in conversion - staticSymbol = simplifyInContext( - context, expression['expression'], depth + 1, /* references */ 0); - if (staticSymbol instanceof StaticSymbol) { - if (staticSymbol === self.injectionToken || staticSymbol === self.opaqueToken) { - // if somebody calls new InjectionToken, don't create an InjectionToken, - // but rather return the symbol to which the InjectionToken is assigned to. - - // OpaqueToken is supported too as it is required by the language service to - // support v4 and prior versions of Angular. - return context; - } - const argExpressions: any[] = expression['arguments'] || []; - let converter = self.conversionMap.get(staticSymbol); - if (converter) { - const args = argExpressions.map(arg => simplifyNested(context, arg)) - .map(arg => shouldIgnore(arg) ? undefined : arg); - return converter(context, args); - } else { - // Determine if the function is one we can simplify. - const targetFunction = resolveReferenceValue(staticSymbol); - return simplifyCall( - staticSymbol, targetFunction, argExpressions, expression['expression']); - } - } - return IGNORE; - case 'error': - let message = expression.message; - if (expression['line'] != null) { - self.error( - { - message, - context: expression.context, - value: expression, - position: { - fileName: expression['fileName'], - line: expression['line'], - column: expression['character'] - } - }, - context); - } else { - self.error({message, context: expression.context}, context); - } - return IGNORE; - case 'ignore': - return expression; - } - return null; - } - return mapStringMap(expression, (value, name) => { - if (REFERENCE_SET.has(name)) { - if (name === USE_VALUE && PROVIDE in expression) { - // If this is a provider expression, check for special tokens that need the value - // during analysis. - const provide = simplify(expression.provide); - if (provide === self.ROUTES || provide == self.ANALYZE_FOR_ENTRY_COMPONENTS) { - return simplify(value); - } - } - return simplifyLazily(value); - } - return simplify(value); - }); - } - return IGNORE; - } - - return simplify(value); - } - - let result: any; - try { - result = simplifyInContext(context, value, 0, lazy ? 1 : 0); - } catch (e) { - if (this.errorRecorder) { - this.reportError(e, context); - } else { - throw formatMetadataError(e, context); - } - } - if (shouldIgnore(result)) { - return undefined; - } - return result; - } - - private getTypeMetadata(type: StaticSymbol): {[key: string]: any} { - const resolvedSymbol = this.symbolResolver.resolveSymbol(type); - return resolvedSymbol && resolvedSymbol.metadata ? resolvedSymbol.metadata : - {__symbolic: 'class'}; - } - - private reportError(error: Error, context: StaticSymbol, path?: string) { - if (this.errorRecorder) { - this.errorRecorder( - formatMetadataError(error, context), (context && context.filePath) || path); - } else { - throw error; - } - } - - private error( - {message, summary, advise, position, context, value, symbol, chain}: { - message: string, - summary?: string, - advise?: string, - position?: Position, - context?: any, - value?: any, - symbol?: StaticSymbol, - chain?: MetadataMessageChain - }, - reportingContext: StaticSymbol) { - this.reportError( - metadataError(message, summary, advise, position, symbol, context, chain), - reportingContext); - } -} - -interface Position { - fileName: string; - line: number; - column: number; -} - -interface MetadataMessageChain { - message: string; - summary?: string; - position?: Position; - context?: any; - symbol?: StaticSymbol; - next?: MetadataMessageChain; -} - -type MetadataError = Error&{ - position?: Position; - advise?: string; - summary?: string; - context?: any; - symbol?: StaticSymbol; - chain?: MetadataMessageChain; -}; - -const METADATA_ERROR = 'ngMetadataError'; - -function metadataError( - message: string, summary?: string, advise?: string, position?: Position, symbol?: StaticSymbol, - context?: any, chain?: MetadataMessageChain): MetadataError { - const error = syntaxError(message) as MetadataError; - (error as any)[METADATA_ERROR] = true; - if (advise) error.advise = advise; - if (position) error.position = position; - if (summary) error.summary = summary; - if (context) error.context = context; - if (chain) error.chain = chain; - if (symbol) error.symbol = symbol; - return error; -} - -function isMetadataError(error: Error): error is MetadataError { - return !!(error as any)[METADATA_ERROR]; -} - -const REFERENCE_TO_NONEXPORTED_CLASS = 'Reference to non-exported class'; -const VARIABLE_NOT_INITIALIZED = 'Variable not initialized'; -const DESTRUCTURE_NOT_SUPPORTED = 'Destructuring not supported'; -const COULD_NOT_RESOLVE_TYPE = 'Could not resolve type'; -const FUNCTION_CALL_NOT_SUPPORTED = 'Function call not supported'; -const REFERENCE_TO_LOCAL_SYMBOL = 'Reference to a local symbol'; -const LAMBDA_NOT_SUPPORTED = 'Lambda not supported'; - -function expandedMessage(message: string, context: any): string { - switch (message) { - case REFERENCE_TO_NONEXPORTED_CLASS: - if (context && context.className) { - return `References to a non-exported class are not supported in decorators but ${ - context.className} was referenced.`; - } - break; - case VARIABLE_NOT_INITIALIZED: - return 'Only initialized variables and constants can be referenced in decorators because the value of this variable is needed by the template compiler'; - case DESTRUCTURE_NOT_SUPPORTED: - return 'Referencing an exported destructured variable or constant is not supported in decorators and this value is needed by the template compiler'; - case COULD_NOT_RESOLVE_TYPE: - if (context && context.typeName) { - return `Could not resolve type ${context.typeName}`; - } - break; - case FUNCTION_CALL_NOT_SUPPORTED: - if (context && context.name) { - return `Function calls are not supported in decorators but '${context.name}' was called`; - } - return 'Function calls are not supported in decorators'; - case REFERENCE_TO_LOCAL_SYMBOL: - if (context && context.name) { - return `Reference to a local (non-exported) symbols are not supported in decorators but '${ - context.name}' was referenced`; - } - break; - case LAMBDA_NOT_SUPPORTED: - return `Function expressions are not supported in decorators`; - } - return message; -} - -function messageAdvise(message: string, context: any): string|undefined { - switch (message) { - case REFERENCE_TO_NONEXPORTED_CLASS: - if (context && context.className) { - return `Consider exporting '${context.className}'`; - } - break; - case DESTRUCTURE_NOT_SUPPORTED: - return 'Consider simplifying to avoid destructuring'; - case REFERENCE_TO_LOCAL_SYMBOL: - if (context && context.name) { - return `Consider exporting '${context.name}'`; - } - break; - case LAMBDA_NOT_SUPPORTED: - return `Consider changing the function expression into an exported function`; - } - return undefined; -} - -function errorSummary(error: MetadataError): string { - if (error.summary) { - return error.summary; - } - switch (error.message) { - case REFERENCE_TO_NONEXPORTED_CLASS: - if (error.context && error.context.className) { - return `references non-exported class ${error.context.className}`; - } - break; - case VARIABLE_NOT_INITIALIZED: - return 'is not initialized'; - case DESTRUCTURE_NOT_SUPPORTED: - return 'is a destructured variable'; - case COULD_NOT_RESOLVE_TYPE: - return 'could not be resolved'; - case FUNCTION_CALL_NOT_SUPPORTED: - if (error.context && error.context.name) { - return `calls '${error.context.name}'`; - } - return `calls a function`; - case REFERENCE_TO_LOCAL_SYMBOL: - if (error.context && error.context.name) { - return `references local variable ${error.context.name}`; - } - return `references a local variable`; - } - return 'contains the error'; -} - -function mapStringMap(input: {[key: string]: any}, transform: (value: any, key: string) => any): - {[key: string]: any} { - if (!input) return {}; - const result: {[key: string]: any} = {}; - Object.keys(input).forEach((key) => { - const value = transform(input[key], key); - if (!shouldIgnore(value)) { - if (HIDDEN_KEY.test(key)) { - Object.defineProperty(result, key, {enumerable: false, configurable: true, value: value}); - } else { - result[key] = value; - } - } - }); - return result; -} - -function isPrimitive(o: any): boolean { - return o === null || (typeof o !== 'function' && typeof o !== 'object'); -} - -interface BindingScopeBuilder { - define(name: string, value: any): BindingScopeBuilder; - done(): BindingScope; -} - -abstract class BindingScope { - abstract resolve(name: string): any; - public static missing = {}; - public static empty: BindingScope = {resolve: name => BindingScope.missing}; - - public static build(): BindingScopeBuilder { - const current = new Map(); - return { - define: function(name, value) { - current.set(name, value); - return this; - }, - done: function() { - return current.size > 0 ? new PopulatedScope(current) : BindingScope.empty; - } - }; - } -} - -class PopulatedScope extends BindingScope { - constructor(private bindings: Map) { - super(); - } - - override resolve(name: string): any { - return this.bindings.has(name) ? this.bindings.get(name) : BindingScope.missing; - } -} - -function formatMetadataMessageChain( - chain: MetadataMessageChain, advise: string|undefined): FormattedMessageChain { - const expanded = expandedMessage(chain.message, chain.context); - const nesting = chain.symbol ? ` in '${chain.symbol.name}'` : ''; - const message = `${expanded}${nesting}`; - const position = chain.position; - const next: FormattedMessageChain|undefined = chain.next ? - formatMetadataMessageChain(chain.next, advise) : - advise ? {message: advise} : undefined; - return {message, position, next: next ? [next] : undefined}; -} - -function formatMetadataError(e: Error, context: StaticSymbol): Error { - if (isMetadataError(e)) { - // Produce a formatted version of the and leaving enough information in the original error - // to recover the formatting information to eventually produce a diagnostic error message. - const position = e.position; - const chain: MetadataMessageChain = { - message: `Error during template compile of '${context.name}'`, - position: position, - next: {message: e.message, next: e.chain, context: e.context, symbol: e.symbol} - }; - const advise = e.advise || messageAdvise(e.message, e.context); - return formattedError(formatMetadataMessageChain(chain, advise)); - } - return e; -} diff --git a/packages/compiler/src/aot/static_symbol.ts b/packages/compiler/src/aot/static_symbol.ts deleted file mode 100644 index 8444b03a35d..00000000000 --- a/packages/compiler/src/aot/static_symbol.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 - */ - -/** - * A token representing the a reference to a static type. - * - * This token is unique for a filePath and name and can be used as a hash table key. - */ -export class StaticSymbol { - constructor(public filePath: string, public name: string, public members: string[]) {} - - assertNoMembers() { - if (this.members.length) { - throw new Error( - `Illegal state: symbol without members expected, but got ${JSON.stringify(this)}.`); - } - } -} - -/** - * A cache of static symbol used by the StaticReflector to return the same symbol for the - * same symbol values. - */ -export class StaticSymbolCache { - private cache = new Map(); - - get(declarationFile: string, name: string, members?: string[]): StaticSymbol { - members = members || []; - const memberSuffix = members.length ? `.${members.join('.')}` : ''; - const key = `"${declarationFile}".${name}${memberSuffix}`; - let result = this.cache.get(key); - if (!result) { - result = new StaticSymbol(declarationFile, name, members); - this.cache.set(key, result); - } - return result; - } -} diff --git a/packages/compiler/src/aot/static_symbol_resolver.ts b/packages/compiler/src/aot/static_symbol_resolver.ts deleted file mode 100644 index 4389e6c293b..00000000000 --- a/packages/compiler/src/aot/static_symbol_resolver.ts +++ /dev/null @@ -1,548 +0,0 @@ -/** - * @license - * Copyright Google LLC 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'; -import {isGeneratedFile, stripSummaryForJitFileSuffix, stripSummaryForJitNameSuffix, summaryForJitFileName, summaryForJitName} from './util'; - -const TS = /^(?!.*\.d\.ts$).*\.ts$/; - -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 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}[]|undefined; - - /** - * 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; - - /** - * Get a file suitable for display to the user that should be relative to the project directory - * or the current directory. - */ - getOutputName(filePath: string): string; -} - -const SUPPORTED_SCHEMA_VERSION = 4; - -/** - * This class is responsible for loading metadata per symbol, - * and normalizing references between symbols. - * - * Internally, it only uses symbols without members, - * and deduces the values for symbols with members based - * on these symbols. - */ -export class StaticSymbolResolver { - private metadataCache = new Map(); - // Note: this will only contain StaticSymbols without members! - private resolvedSymbols = new Map(); - // Note: this will only contain StaticSymbols without members! - private importAs = new Map(); - private symbolResourcePaths = new Map(); - private symbolFromFile = new Map(); - private knownFileNameToModuleNames = new Map(); - - constructor( - private host: StaticSymbolResolverHost, private staticSymbolCache: StaticSymbolCache, - private summaryResolver: SummaryResolver, - private errorRecorder?: (error: any, fileName?: string) => void) {} - - resolveSymbol(staticSymbol: StaticSymbol): ResolvedStaticSymbol { - if (staticSymbol.members.length > 0) { - return this._resolveSymbolMembers(staticSymbol)!; - } - // Note: always ask for a summary first, - // as we might have read shallow metadata via a .d.ts file - // for the symbol. - const resultFromSummary = this._resolveSymbolFromSummary(staticSymbol)!; - if (resultFromSummary) { - return resultFromSummary; - } - const resultFromCache = this.resolvedSymbols.get(staticSymbol); - if (resultFromCache) { - return resultFromCache; - } - // 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); - return this.resolvedSymbols.get(staticSymbol)!; - } - - /** - * getImportAs produces a symbol that can be used to import the given symbol. - * The import might be different than the symbol if the symbol is exported from - * a library with a summary; in which case we want to import the symbol from the - * ngfactory re-export instead of directly to avoid introducing a direct dependency - * on an otherwise indirect dependency. - * - * @param staticSymbol the symbol for which to generate a import symbol - */ - getImportAs(staticSymbol: StaticSymbol, useSummaries: boolean = true): StaticSymbol|null { - if (staticSymbol.members.length) { - const baseSymbol = this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name); - const baseImportAs = this.getImportAs(baseSymbol, useSummaries); - return baseImportAs ? - this.getStaticSymbol(baseImportAs.filePath, baseImportAs.name, staticSymbol.members) : - null; - } - const summarizedFileName = stripSummaryForJitFileSuffix(staticSymbol.filePath); - if (summarizedFileName !== staticSymbol.filePath) { - const summarizedName = stripSummaryForJitNameSuffix(staticSymbol.name); - const baseSymbol = - this.getStaticSymbol(summarizedFileName, summarizedName, staticSymbol.members); - const baseImportAs = this.getImportAs(baseSymbol, useSummaries); - return baseImportAs ? this.getStaticSymbol( - summaryForJitFileName(baseImportAs.filePath), - summaryForJitName(baseImportAs.name), baseSymbol.members) : - null; - } - let result = (useSummaries && this.summaryResolver.getImportAs(staticSymbol)) || null; - if (!result) { - result = this.importAs.get(staticSymbol)!; - } - return result; - } - - /** - * getResourcePath produces the path to the original location of the symbol and should - * be used to determine the relative location of resource references recorded in - * symbol metadata. - */ - getResourcePath(staticSymbol: StaticSymbol): string { - return this.symbolResourcePaths.get(staticSymbol) || staticSymbol.filePath; - } - - /** - * getTypeArity returns the number of generic type parameters the given symbol - * has. If the symbol is not a type the result is null. - */ - getTypeArity(staticSymbol: StaticSymbol): number|null { - // If the file is a factory/ngsummary file, don't resolve the symbol as doing so would - // cause the metadata for an factory/ngsummary file to be loaded which doesn't exist. - // All references to generated classes must include the correct arity whenever - // generating code. - if (isGeneratedFile(staticSymbol.filePath)) { - return null; - } - let resolvedSymbol = unwrapResolvedMetadata(this.resolveSymbol(staticSymbol)); - while (resolvedSymbol && resolvedSymbol.metadata instanceof StaticSymbol) { - resolvedSymbol = unwrapResolvedMetadata(this.resolveSymbol(resolvedSymbol.metadata)); - } - return (resolvedSymbol && resolvedSymbol.metadata && resolvedSymbol.metadata.arity) || null; - } - - getKnownModuleName(filePath: string): string|null { - return this.knownFileNameToModuleNames.get(filePath) || null; - } - - recordImportAs(sourceSymbol: StaticSymbol, targetSymbol: StaticSymbol) { - sourceSymbol.assertNoMembers(); - targetSymbol.assertNoMembers(); - this.importAs.set(sourceSymbol, targetSymbol); - } - - recordModuleNameForFileName(fileName: string, moduleName: string) { - this.knownFileNameToModuleNames.set(fileName, moduleName); - } - - /** - * Invalidate all information derived from the given file and return the - * static symbols contained in the file. - * - * @param fileName the file to invalidate - */ - invalidateFile(fileName: string): StaticSymbol[] { - this.metadataCache.delete(fileName); - const symbols = this.symbolFromFile.get(fileName); - if (!symbols) { - return []; - } - this.symbolFromFile.delete(fileName); - for (const symbol of symbols) { - this.resolvedSymbols.delete(symbol); - this.importAs.delete(symbol); - this.symbolResourcePaths.delete(symbol); - } - return symbols; - } - - /** @internal */ - ignoreErrorsFor(cb: () => T) { - const recorder = this.errorRecorder; - this.errorRecorder = () => {}; - try { - return cb(); - } finally { - this.errorRecorder = recorder; - } - } - - private _resolveSymbolMembers(staticSymbol: StaticSymbol): ResolvedStaticSymbol|null { - const members = staticSymbol.members; - const baseResolvedSymbol = - this.resolveSymbol(this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name)); - if (!baseResolvedSymbol) { - return null; - } - let baseMetadata = unwrapResolvedMetadata(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 (let i = 0; i < members.length && value; i++) { - value = value[members[i]]; - } - return new ResolvedStaticSymbol(staticSymbol, value); - } - return null; - } - - private _resolveSymbolFromSummary(staticSymbol: StaticSymbol): ResolvedStaticSymbol|null { - 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. - * @param members a symbol for a static member of the named type - */ - getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol { - return this.staticSymbolCache.get(declarationFile, name, members); - } - - /** - * hasDecorators checks a file's metadata for the presence of decorators without evaluating the - * metadata. - * - * @param filePath the absolute path to examine for decorators. - * @returns true if any class in the file has a decorator. - */ - hasDecorators(filePath: string): boolean { - const metadata = this.getModuleMetadata(filePath); - if (metadata['metadata']) { - return Object.keys(metadata['metadata']).some((metadataKey) => { - const entry = metadata['metadata'][metadataKey]; - return entry && entry.__symbolic === 'class' && entry.decorators; - }); - } - return false; - } - - getSymbolsOf(filePath: string): StaticSymbol[] { - const summarySymbols = this.summaryResolver.getSymbolsOf(filePath); - if (summarySymbols) { - return summarySymbols; - } - // Note: Some users use libraries that were not compiled with ngc, i.e. they don't - // have summaries, only .d.ts files, but `summaryResolver.isLibraryFile` returns true. - this._createSymbolsOf(filePath); - return this.symbolFromFile.get(filePath) || []; - } - - private _createSymbolsOf(filePath: string) { - if (this.symbolFromFile.has(filePath)) { - return; - } - const resolvedSymbols: ResolvedStaticSymbol[] = []; - const metadata = this.getModuleMetadata(filePath); - if (metadata['importAs']) { - // Index bundle indices should use the importAs module name defined - // in the bundle. - this.knownFileNameToModuleNames.set(filePath, metadata['importAs']); - } - // 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; - } - symbolName = unescapeIdentifier(symbolName); - let symName = symbolName; - if (typeof exportSymbol !== 'string') { - symName = unescapeIdentifier(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(this.createExport(sourceSymbol, targetSymbol)); - } - }); - } else { - // Handle the symbols loaded by 'export *' directives. - const resolvedModule = this.resolveModule(moduleExport.from, filePath); - if (resolvedModule && resolvedModule !== filePath) { - const nestedExports = this.getSymbolsOf(resolvedModule); - nestedExports.forEach((targetSymbol) => { - const sourceSymbol = this.getStaticSymbol(filePath, targetSymbol.name); - resolvedSymbols.push(this.createExport(sourceSymbol, targetSymbol)); - }); - } - } - } - } - - // handle the actual metadata. Has to be after the exports - // as there might be collisions in the names, and we want the symbols - // of the current module to win ofter reexports. - if (metadata['metadata']) { - // handle direct declarations of the symbol - const topLevelSymbolNames = - new Set(Object.keys(metadata['metadata']).map(unescapeIdentifier)); - const origins: {[index: string]: string} = metadata['origins'] || {}; - Object.keys(metadata['metadata']).forEach((metadataKey) => { - const symbolMeta = metadata['metadata'][metadataKey]; - const name = unescapeIdentifier(metadataKey); - - const symbol = this.getStaticSymbol(filePath, name); - - const origin = origins.hasOwnProperty(metadataKey) && origins[metadataKey]; - if (origin) { - // If the symbol is from a bundled index, use the declaration location of the - // symbol so relative references (such as './my.html') will be calculated - // correctly. - const originFilePath = this.resolveModule(origin, filePath); - if (!originFilePath) { - this.reportError(new Error(`Couldn't resolve original symbol for ${origin} from ${ - this.host.getOutputName(filePath)}`)); - } else { - this.symbolResourcePaths.set(symbol, originFilePath); - } - } - resolvedSymbols.push( - this.createResolvedSymbol(symbol, filePath, topLevelSymbolNames, symbolMeta)); - }); - } - const uniqueSymbols = new Set(); - for (const resolvedSymbol of resolvedSymbols) { - this.resolvedSymbols.set(resolvedSymbol.symbol, resolvedSymbol); - uniqueSymbols.add(resolvedSymbol.symbol); - } - this.symbolFromFile.set(filePath, Array.from(uniqueSymbols)); - } - - private createResolvedSymbol( - sourceSymbol: StaticSymbol, topLevelPath: string, topLevelSymbolNames: Set, - metadata: any): ResolvedStaticSymbol { - // For classes that don't have Angular summaries / metadata, - // we only keep their arity, but nothing else - // (e.g. their constructor parameters). - // We do this to prevent introducing deep imports - // as we didn't generate .ngfactory.ts files with proper reexports. - const isTsFile = TS.test(sourceSymbol.filePath); - if (this.summaryResolver.isLibraryFile(sourceSymbol.filePath) && !isTsFile && metadata && - metadata['__symbolic'] === 'class') { - const transformedMeta = {__symbolic: 'class', arity: metadata.arity}; - return new ResolvedStaticSymbol(sourceSymbol, transformedMeta); - } - - let _originalFileMemo: string|undefined; - const getOriginalName: () => string = () => { - if (!_originalFileMemo) { - // Guess what the original file name is from the reference. If it has a `.d.ts` extension - // replace it with `.ts`. If it already has `.ts` just leave it in place. If it doesn't have - // .ts or .d.ts, append `.ts'. Also, if it is in `node_modules`, trim the `node_module` - // location as it is not important to finding the file. - _originalFileMemo = - this.host.getOutputName(topLevelPath.replace(/((\.ts)|(\.d\.ts)|)$/, '.ts') - .replace(/^.*node_modules[/\\]/, '')); - } - return _originalFileMemo; - }; - - const self = this; - - class ReferenceTransformer extends ValueTransformer { - override 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'] ? unescapeIdentifier(map['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 ${ - self.host.getMetadataFor(sourceSymbol.filePath)}.`, - line: map['line'], - character: map['character'], - fileName: getOriginalName() - }; - } - return { - __symbolic: 'resolved', - symbol: self.getStaticSymbol(filePath, name), - line: map['line'], - character: map['character'], - fileName: getOriginalName() - }; - } else if (functionParams.indexOf(name) >= 0) { - // reference to a function parameter - return {__symbolic: 'reference', name: name}; - } else { - if (topLevelSymbolNames.has(name)) { - return self.getStaticSymbol(topLevelPath, name); - } - // ambient value - null; - } - } else if (symbolic === 'error') { - return {...map, fileName: getOriginalName()}; - } else { - return super.visitStringMap(map, functionParams); - } - } - } - const transformedMeta = visitValue(metadata, new ReferenceTransformer(), []); - let unwrappedTransformedMeta = unwrapResolvedMetadata(transformedMeta); - if (unwrappedTransformedMeta instanceof StaticSymbol) { - return this.createExport(sourceSymbol, unwrappedTransformedMeta); - } - return new ResolvedStaticSymbol(sourceSymbol, transformedMeta); - } - - private createExport(sourceSymbol: StaticSymbol, targetSymbol: StaticSymbol): - ResolvedStaticSymbol { - sourceSymbol.assertNoMembers(); - targetSymbol.assertNoMembers(); - if (this.summaryResolver.isLibraryFile(sourceSymbol.filePath) && - this.summaryResolver.isLibraryFile(targetSymbol.filePath)) { - // This case is for an ng library importing symbols from a plain ts library - // transitively. - // Note: We rely on the fact that we discover symbols in the direction - // from source files to library files - this.importAs.set(targetSymbol, this.getImportAs(sourceSymbol) || sourceSymbol); - } - return new ResolvedStaticSymbol(sourceSymbol, targetSymbol); - } - - 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 && 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 ${ - this.host.getOutputName(module)}, found version ${ - moduleMetadata['version']}, expected ${SUPPORTED_SCHEMA_VERSION}`; - this.reportError(new Error(errorMessage)); - } - this.metadataCache.set(module, moduleMetadata); - } - return moduleMetadata; - } - - - getSymbolByModule(module: string, symbolName: string, containingFile?: string): StaticSymbol { - const filePath = this.resolveModule(module, containingFile); - if (!filePath) { - this.reportError(new Error(`Could not resolve module ${module}${ - containingFile ? ' relative to ' + this.host.getOutputName(containingFile) : ''}`)); - return this.getStaticSymbol(`ERROR:${module}`, symbolName); - } - return this.getStaticSymbol(filePath, symbolName); - } - - private resolveModule(module: string, containingFile?: string): string|null { - try { - return this.host.moduleNameToFileName(module, containingFile); - } catch (e) { - console.error(`Could not resolve module '${module}' relative to file ${containingFile}`); - this.reportError(e, undefined, containingFile); - } - return null; - } -} - -// Remove extra underscore from escaped identifier. -// See https://github.com/Microsoft/TypeScript/blob/master/src/compiler/utilities.ts -export function unescapeIdentifier(identifier: string): string { - return identifier.startsWith('___') ? identifier.substr(1) : identifier; -} - -export function unwrapResolvedMetadata(metadata: any): any { - if (metadata && metadata.__symbolic === 'resolved') { - return metadata.symbol; - } - return metadata; -} diff --git a/packages/compiler/src/aot/summary_resolver.ts b/packages/compiler/src/aot/summary_resolver.ts deleted file mode 100644 index 960d20ea8b3..00000000000 --- a/packages/compiler/src/aot/summary_resolver.ts +++ /dev/null @@ -1,132 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {Summary, SummaryResolver} from '../summary_resolver'; - -import {StaticSymbol, StaticSymbolCache} from './static_symbol'; -import {deserializeSummaries} from './summary_serializer'; -import {stripGeneratedFileSuffix, summaryFileName} from './util'; - -export interface AotSummaryResolverHost { - /** - * Loads an NgModule/Directive/Pipe summary file - */ - loadSummary(filePath: string): string|null; - - /** - * Returns whether a file is a source file or not. - */ - isSourceFile(sourceFilePath: string): boolean; - /** - * Converts a file name into a representation that should be stored in a summary file. - * This has to include changing the suffix as well. - * E.g. - * `some_file.ts` -> `some_file.d.ts` - * - * @param referringSrcFileName the soure file that refers to fileName - */ - toSummaryFileName(fileName: string, referringSrcFileName: string): string; - - /** - * Converts a fileName that was processed by `toSummaryFileName` back into a real fileName - * given the fileName of the library that is referrig to it. - */ - fromSummaryFileName(fileName: string, referringLibFileName: string): string; -} - -export class AotSummaryResolver implements SummaryResolver { - // Note: this will only contain StaticSymbols without members! - private summaryCache = new Map>(); - private loadedFilePaths = new Map(); - // Note: this will only contain StaticSymbols without members! - private importAs = new Map(); - private knownFileNameToModuleNames = new Map(); - - constructor(private host: AotSummaryResolverHost, private staticSymbolCache: StaticSymbolCache) {} - - isLibraryFile(filePath: string): boolean { - // Note: We need to strip the .ngfactory. file path, - // so this method also works for generated files - // (for which host.isSourceFile will always return false). - return !this.host.isSourceFile(stripGeneratedFileSuffix(filePath)); - } - - toSummaryFileName(filePath: string, referringSrcFileName: string) { - return this.host.toSummaryFileName(filePath, referringSrcFileName); - } - - fromSummaryFileName(fileName: string, referringLibFileName: string) { - return this.host.fromSummaryFileName(fileName, referringLibFileName); - } - - resolveSummary(staticSymbol: StaticSymbol): Summary|null { - const rootSymbol = staticSymbol.members.length ? - this.staticSymbolCache.get(staticSymbol.filePath, staticSymbol.name) : - staticSymbol; - let summary = this.summaryCache.get(rootSymbol); - if (!summary) { - this._loadSummaryFile(staticSymbol.filePath); - summary = this.summaryCache.get(staticSymbol)!; - } - return (rootSymbol === staticSymbol && summary) || null; - } - - getSymbolsOf(filePath: string): StaticSymbol[]|null { - if (this._loadSummaryFile(filePath)) { - return Array.from(this.summaryCache.keys()).filter((symbol) => symbol.filePath === filePath); - } - return null; - } - - getImportAs(staticSymbol: StaticSymbol): StaticSymbol { - staticSymbol.assertNoMembers(); - return this.importAs.get(staticSymbol)!; - } - - /** - * Converts a file path to a module name that can be used as an `import`. - */ - getKnownModuleName(importedFilePath: string): string|null { - return this.knownFileNameToModuleNames.get(importedFilePath) || null; - } - - addSummary(summary: Summary) { - this.summaryCache.set(summary.symbol, summary); - } - - private _loadSummaryFile(filePath: string): boolean { - let hasSummary = this.loadedFilePaths.get(filePath); - if (hasSummary != null) { - return hasSummary; - } - let json: string|null = null; - if (this.isLibraryFile(filePath)) { - const summaryFilePath = summaryFileName(filePath); - try { - json = this.host.loadSummary(summaryFilePath); - } catch (e) { - console.error(`Error loading summary file ${summaryFilePath}`); - throw e; - } - } - hasSummary = json != null; - this.loadedFilePaths.set(filePath, hasSummary); - if (json) { - const {moduleName, summaries, importAs} = - deserializeSummaries(this.staticSymbolCache, this, filePath, json); - summaries.forEach((summary) => this.summaryCache.set(summary.symbol, summary)); - if (moduleName) { - this.knownFileNameToModuleNames.set(filePath, moduleName); - } - importAs.forEach((importAs) => { - this.importAs.set(importAs.symbol, importAs.importAs); - }); - } - return hasSummary; - } -} diff --git a/packages/compiler/src/aot/summary_serializer.ts b/packages/compiler/src/aot/summary_serializer.ts deleted file mode 100644 index 0587276e08d..00000000000 --- a/packages/compiler/src/aot/summary_serializer.ts +++ /dev/null @@ -1,508 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {CompileDirectiveMetadata, CompileDirectiveSummary, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompileProviderMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary} from '../compile_metadata'; -import {OutputContext} from '../constant_pool'; -import * as o from '../output/output_ast'; -import {Summary, SummaryResolver} from '../summary_resolver'; -import {ValueTransformer, ValueVisitor, visitValue} from '../util'; - -import {StaticSymbol, StaticSymbolCache} from './static_symbol'; -import {ResolvedStaticSymbol, StaticSymbolResolver, unwrapResolvedMetadata} from './static_symbol_resolver'; -import {isLoweredSymbol, ngfactoryFilePath, summaryForJitFileName, summaryForJitName} from './util'; - -export function serializeSummaries( - srcFileName: string, forJitCtx: OutputContext|null, - summaryResolver: SummaryResolver, symbolResolver: StaticSymbolResolver, - symbols: ResolvedStaticSymbol[], types: { - summary: CompileTypeSummary, - metadata: CompileNgModuleMetadata|CompileDirectiveMetadata|CompilePipeMetadata| - CompileTypeMetadata - }[], - createExternalSymbolReexports = - false): {json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} { - const toJsonSerializer = new ToJsonSerializer(symbolResolver, summaryResolver, srcFileName); - - // for symbols, we use everything except for the class metadata itself - // (we keep the statics though), as the class metadata is contained in the - // CompileTypeSummary. - symbols.forEach( - (resolvedSymbol) => toJsonSerializer.addSummary( - {symbol: resolvedSymbol.symbol, metadata: resolvedSymbol.metadata})); - - // Add type summaries. - types.forEach(({summary, metadata}) => { - toJsonSerializer.addSummary( - {symbol: summary.type.reference, metadata: undefined, type: summary}); - }); - const {json, exportAs} = toJsonSerializer.serialize(createExternalSymbolReexports); - if (forJitCtx) { - const forJitSerializer = new ForJitSerializer(forJitCtx, symbolResolver, summaryResolver); - types.forEach(({summary, metadata}) => { - forJitSerializer.addSourceType(summary, metadata); - }); - toJsonSerializer.unprocessedSymbolSummariesBySymbol.forEach((summary) => { - if (summaryResolver.isLibraryFile(summary.symbol.filePath) && summary.type) { - forJitSerializer.addLibType(summary.type); - } - }); - forJitSerializer.serialize(exportAs); - } - return {json, exportAs}; -} - -export function deserializeSummaries( - symbolCache: StaticSymbolCache, summaryResolver: SummaryResolver, - libraryFileName: string, json: string): { - moduleName: string|null, - summaries: Summary[], - importAs: {symbol: StaticSymbol, importAs: StaticSymbol}[] -} { - const deserializer = new FromJsonDeserializer(symbolCache, summaryResolver); - return deserializer.deserialize(libraryFileName, json); -} - -export function createForJitStub(outputCtx: OutputContext, reference: StaticSymbol) { - return createSummaryForJitFunction(outputCtx, reference, o.NULL_EXPR); -} - -function createSummaryForJitFunction( - outputCtx: OutputContext, reference: StaticSymbol, value: o.Expression) { - const fnName = summaryForJitName(reference.name); - outputCtx.statements.push( - o.fn([], [new o.ReturnStatement(value)], new o.ArrayType(o.DYNAMIC_TYPE)).toDeclStmt(fnName, [ - o.StmtModifier.Final, o.StmtModifier.Exported - ])); -} - -const enum SerializationFlags { - None = 0, - ResolveValue = 1, -} - -class ToJsonSerializer extends ValueTransformer { - // Note: This only contains symbols without members. - private symbols: StaticSymbol[] = []; - private indexBySymbol = new Map(); - private reexportedBy = new Map(); - // This now contains a `__symbol: number` in the place of - // StaticSymbols, but otherwise has the same shape as the original objects. - private processedSummaryBySymbol = new Map(); - private processedSummaries: any[] = []; - private moduleName: string|null; - - unprocessedSymbolSummariesBySymbol = new Map>(); - - constructor( - private symbolResolver: StaticSymbolResolver, - private summaryResolver: SummaryResolver, private srcFileName: string) { - super(); - this.moduleName = symbolResolver.getKnownModuleName(srcFileName); - } - - addSummary(summary: Summary) { - let unprocessedSummary = this.unprocessedSymbolSummariesBySymbol.get(summary.symbol); - let processedSummary = this.processedSummaryBySymbol.get(summary.symbol); - if (!unprocessedSummary) { - unprocessedSummary = {symbol: summary.symbol, metadata: undefined}; - this.unprocessedSymbolSummariesBySymbol.set(summary.symbol, unprocessedSummary); - processedSummary = {symbol: this.processValue(summary.symbol, SerializationFlags.None)}; - this.processedSummaries.push(processedSummary); - this.processedSummaryBySymbol.set(summary.symbol, processedSummary); - } - if (!unprocessedSummary.metadata && summary.metadata) { - let metadata = summary.metadata || {}; - if (metadata.__symbolic === 'class') { - // For classes, we keep everything except their class decorators. - // We need to keep e.g. the ctor args, method names, method decorators - // so that the class can be extended in another compilation unit. - // We don't keep the class decorators as - // 1) they refer to data - // that should not cause a rebuild of downstream compilation units - // (e.g. inline templates of @Component, or @NgModule.declarations) - // 2) their data is already captured in TypeSummaries, e.g. DirectiveSummary. - const clone: {[key: string]: any} = {}; - Object.keys(metadata).forEach((propName) => { - if (propName !== 'decorators') { - clone[propName] = metadata[propName]; - } - }); - metadata = clone; - } else if (isCall(metadata)) { - if (!isFunctionCall(metadata) && !isMethodCallOnVariable(metadata)) { - // Don't store complex calls as we won't be able to simplify them anyways later on. - metadata = { - __symbolic: 'error', - message: 'Complex function calls are not supported.', - }; - } - } - // Note: We need to keep storing ctor calls for e.g. - // `export const x = new InjectionToken(...)` - unprocessedSummary.metadata = metadata; - processedSummary.metadata = this.processValue(metadata, SerializationFlags.ResolveValue); - if (metadata instanceof StaticSymbol && - this.summaryResolver.isLibraryFile(metadata.filePath)) { - const declarationSymbol = this.symbols[this.indexBySymbol.get(metadata)!]; - if (!isLoweredSymbol(declarationSymbol.name)) { - // Note: symbols that were introduced during codegen in the user file can have a reexport - // if a user used `export *`. However, we can't rely on this as tsickle will change - // `export *` into named exports, using only the information from the typechecker. - // As we introduce the new symbols after typecheck, Tsickle does not know about them, - // and omits them when expanding `export *`. - // So we have to keep reexporting these symbols manually via .ngfactory files. - this.reexportedBy.set(declarationSymbol, summary.symbol); - } - } - } - if (!unprocessedSummary.type && summary.type) { - unprocessedSummary.type = summary.type; - // Note: We don't add the summaries of all referenced symbols as for the ResolvedSymbols, - // as the type summaries already contain the transitive data that they require - // (in a minimal way). - processedSummary.type = this.processValue(summary.type, SerializationFlags.None); - // except for reexported directives / pipes, so we need to store - // their summaries explicitly. - if (summary.type.summaryKind === CompileSummaryKind.NgModule) { - const ngModuleSummary = summary.type; - ngModuleSummary.exportedDirectives.concat(ngModuleSummary.exportedPipes).forEach((id) => { - const symbol: StaticSymbol = id.reference; - if (this.summaryResolver.isLibraryFile(symbol.filePath) && - !this.unprocessedSymbolSummariesBySymbol.has(symbol)) { - const summary = this.summaryResolver.resolveSummary(symbol); - if (summary) { - this.addSummary(summary); - } - } - }); - } - } - } - - /** - * @param createExternalSymbolReexports Whether external static symbols should be re-exported. - * This can be enabled if external symbols should be re-exported by the current module in - * order to avoid dynamically generated module dependencies which can break strict dependency - * enforcements (as in Google3). Read more here: https://github.com/angular/angular/issues/25644 - */ - serialize(createExternalSymbolReexports: boolean): - {json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} { - const exportAs: {symbol: StaticSymbol, exportAs: string}[] = []; - const json = JSON.stringify({ - moduleName: this.moduleName, - summaries: this.processedSummaries, - symbols: this.symbols.map((symbol, index) => { - symbol.assertNoMembers(); - let importAs: string|number = undefined!; - if (this.summaryResolver.isLibraryFile(symbol.filePath)) { - const reexportSymbol = this.reexportedBy.get(symbol); - if (reexportSymbol) { - // In case the given external static symbol is already manually exported by the - // user, we just proxy the external static symbol reference to the manual export. - // This ensures that the AOT compiler imports the external symbol through the - // user export and does not introduce another dependency which is not needed. - importAs = this.indexBySymbol.get(reexportSymbol)!; - } else if (createExternalSymbolReexports) { - // In this case, the given external static symbol is *not* manually exported by - // the user, and we manually create a re-export in the factory file so that we - // don't introduce another module dependency. This is useful when running within - // Bazel so that the AOT compiler does not introduce any module dependencies - // which can break the strict dependency enforcement. (e.g. as in Google3) - // Read more about this here: https://github.com/angular/angular/issues/25644 - const summary = this.unprocessedSymbolSummariesBySymbol.get(symbol); - if (!summary || !summary.metadata || summary.metadata.__symbolic !== 'interface') { - importAs = `${symbol.name}_${index}`; - exportAs.push({symbol, exportAs: importAs}); - } - } - } - return { - __symbol: index, - name: symbol.name, - filePath: this.summaryResolver.toSummaryFileName(symbol.filePath, this.srcFileName), - importAs: importAs - }; - }) - }); - return {json, exportAs}; - } - - private processValue(value: any, flags: SerializationFlags): any { - return visitValue(value, this, flags); - } - - override visitOther(value: any, context: any): any { - if (value instanceof StaticSymbol) { - let baseSymbol = this.symbolResolver.getStaticSymbol(value.filePath, value.name); - const index = this.visitStaticSymbol(baseSymbol, context); - return {__symbol: index, members: value.members}; - } - } - - /** - * Strip line and character numbers from ngsummaries. - * Emitting them causes white spaces changes to retrigger upstream - * recompilations in bazel. - * TODO: find out a way to have line and character numbers in errors without - * excessive recompilation in bazel. - */ - override visitStringMap(map: {[key: string]: any}, context: any): any { - if (map['__symbolic'] === 'resolved') { - return visitValue(map['symbol'], this, context); - } - if (map['__symbolic'] === 'error') { - delete map['line']; - delete map['character']; - } - return super.visitStringMap(map, context); - } - - /** - * Returns null if the options.resolveValue is true, and the summary for the symbol - * resolved to a type or could not be resolved. - */ - private visitStaticSymbol(baseSymbol: StaticSymbol, flags: SerializationFlags): number { - let index: number|undefined|null = this.indexBySymbol.get(baseSymbol); - let summary: Summary|null = null; - if (flags & SerializationFlags.ResolveValue && - this.summaryResolver.isLibraryFile(baseSymbol.filePath)) { - if (this.unprocessedSymbolSummariesBySymbol.has(baseSymbol)) { - // the summary for this symbol was already added - // -> nothing to do. - return index!; - } - summary = this.loadSummary(baseSymbol); - if (summary && summary.metadata instanceof StaticSymbol) { - // The summary is a reexport - index = this.visitStaticSymbol(summary.metadata, flags); - // reset the summary as it is just a reexport, so we don't want to store it. - summary = null; - } - } else if (index != null) { - // Note: == on purpose to compare with undefined! - // No summary and the symbol is already added -> nothing to do. - return index; - } - // Note: == on purpose to compare with undefined! - if (index == null) { - index = this.symbols.length; - this.symbols.push(baseSymbol); - } - this.indexBySymbol.set(baseSymbol, index); - if (summary) { - this.addSummary(summary); - } - return index; - } - - private loadSummary(symbol: StaticSymbol): Summary|null { - let summary = this.summaryResolver.resolveSummary(symbol); - if (!summary) { - // some symbols might originate from a plain typescript library - // that just exported .d.ts and .metadata.json files, i.e. where no summary - // files were created. - const resolvedSymbol = this.symbolResolver.resolveSymbol(symbol); - if (resolvedSymbol) { - summary = {symbol: resolvedSymbol.symbol, metadata: resolvedSymbol.metadata}; - } - } - return summary; - } -} - -class ForJitSerializer { - private data: Array<{ - summary: CompileTypeSummary, - metadata: CompileNgModuleMetadata|CompileDirectiveMetadata|CompilePipeMetadata| - CompileTypeMetadata|null, - isLibrary: boolean - }> = []; - - constructor( - private outputCtx: OutputContext, private symbolResolver: StaticSymbolResolver, - private summaryResolver: SummaryResolver) {} - - addSourceType( - summary: CompileTypeSummary, - metadata: CompileNgModuleMetadata|CompileDirectiveMetadata|CompilePipeMetadata| - CompileTypeMetadata) { - this.data.push({summary, metadata, isLibrary: false}); - } - - addLibType(summary: CompileTypeSummary) { - this.data.push({summary, metadata: null, isLibrary: true}); - } - - serialize(exportAsArr: {symbol: StaticSymbol, exportAs: string}[]): void { - const exportAsBySymbol = new Map(); - for (const {symbol, exportAs} of exportAsArr) { - exportAsBySymbol.set(symbol, exportAs); - } - const ngModuleSymbols = new Set(); - - for (const {summary, metadata, isLibrary} of this.data) { - if (summary.summaryKind === CompileSummaryKind.NgModule) { - // collect the symbols that refer to NgModule classes. - // Note: we can't just rely on `summary.type.summaryKind` to determine this as - // we don't add the summaries of all referenced symbols when we serialize type summaries. - // See serializeSummaries for details. - ngModuleSymbols.add(summary.type.reference); - const modSummary = summary; - for (const mod of modSummary.modules) { - ngModuleSymbols.add(mod.reference); - } - } - if (!isLibrary) { - const fnName = summaryForJitName(summary.type.reference.name); - createSummaryForJitFunction( - this.outputCtx, summary.type.reference, - this.serializeSummaryWithDeps(summary, metadata!)); - } - } - - ngModuleSymbols.forEach((ngModuleSymbol) => { - if (this.summaryResolver.isLibraryFile(ngModuleSymbol.filePath)) { - let exportAs = exportAsBySymbol.get(ngModuleSymbol) || ngModuleSymbol.name; - const jitExportAsName = summaryForJitName(exportAs); - this.outputCtx.statements.push(o.variable(jitExportAsName) - .set(this.serializeSummaryRef(ngModuleSymbol)) - .toDeclStmt(null, [o.StmtModifier.Exported])); - } - }); - } - - private serializeSummaryWithDeps( - summary: CompileTypeSummary, - metadata: CompileNgModuleMetadata|CompileDirectiveMetadata|CompilePipeMetadata| - CompileTypeMetadata): o.Expression { - const expressions: o.Expression[] = [this.serializeSummary(summary)]; - let providers: CompileProviderMetadata[] = []; - if (metadata instanceof CompileNgModuleMetadata) { - expressions.push(... - // For directives / pipes, we only add the declared ones, - // and rely on transitively importing NgModules to get the transitive - // summaries. - metadata.declaredDirectives.concat(metadata.declaredPipes) - .map(type => type.reference) - // For modules, - // we also add the summaries for modules - // from libraries. - // This is ok as we produce reexports for all transitive modules. - .concat(metadata.transitiveModule.modules.map(type => type.reference) - .filter(ref => ref !== metadata.type.reference)) - .map((ref) => this.serializeSummaryRef(ref))); - // Note: We don't use `NgModuleSummary.providers`, as that one is transitive, - // and we already have transitive modules. - providers = metadata.providers; - } else if (summary.summaryKind === CompileSummaryKind.Directive) { - const dirSummary = summary; - providers = dirSummary.providers.concat(dirSummary.viewProviders); - } - // Note: We can't just refer to the `ngsummary.ts` files for `useClass` providers (as we do for - // declaredDirectives / declaredPipes), as we allow - // providers without ctor arguments to skip the `@Injectable` decorator, - // i.e. we didn't generate .ngsummary.ts files for these. - expressions.push( - ...providers.filter(provider => !!provider.useClass).map(provider => this.serializeSummary({ - summaryKind: CompileSummaryKind.Injectable, - type: provider.useClass - } as CompileTypeSummary))); - return o.literalArr(expressions); - } - - private serializeSummaryRef(typeSymbol: StaticSymbol): o.Expression { - const jitImportedSymbol = this.symbolResolver.getStaticSymbol( - summaryForJitFileName(typeSymbol.filePath), summaryForJitName(typeSymbol.name)); - return this.outputCtx.importExpr(jitImportedSymbol); - } - - private serializeSummary(data: {[key: string]: any}): o.Expression { - const outputCtx = this.outputCtx; - - class Transformer implements ValueVisitor { - visitArray(arr: any[], context: any): any { - return o.literalArr(arr.map(entry => visitValue(entry, this, context))); - } - visitStringMap(map: {[key: string]: any}, context: any): any { - return new o.LiteralMapExpr(Object.keys(map).map( - (key) => new o.LiteralMapEntry(key, visitValue(map[key], this, context), false))); - } - visitPrimitive(value: any, context: any): any { - return o.literal(value); - } - visitOther(value: any, context: any): any { - if (value instanceof StaticSymbol) { - return outputCtx.importExpr(value); - } else { - throw new Error(`Illegal State: Encountered value ${value}`); - } - } - } - - return visitValue(data, new Transformer(), null); - } -} - -class FromJsonDeserializer extends ValueTransformer { - // TODO(issue/24571): remove '!'. - private symbols!: StaticSymbol[]; - - constructor( - private symbolCache: StaticSymbolCache, - private summaryResolver: SummaryResolver) { - super(); - } - - deserialize(libraryFileName: string, json: string): { - moduleName: string|null, - summaries: Summary[], - importAs: {symbol: StaticSymbol, importAs: StaticSymbol}[] - } { - const data = JSON.parse(json) as {moduleName: string | null, summaries: any[], symbols: any[]}; - const allImportAs: {symbol: StaticSymbol, importAs: StaticSymbol}[] = []; - this.symbols = data.symbols.map( - (serializedSymbol) => this.symbolCache.get( - this.summaryResolver.fromSummaryFileName(serializedSymbol.filePath, libraryFileName), - serializedSymbol.name)); - data.symbols.forEach((serializedSymbol, index) => { - const symbol = this.symbols[index]; - const importAs = serializedSymbol.importAs; - if (typeof importAs === 'number') { - allImportAs.push({symbol, importAs: this.symbols[importAs]}); - } else if (typeof importAs === 'string') { - allImportAs.push( - {symbol, importAs: this.symbolCache.get(ngfactoryFilePath(libraryFileName), importAs)}); - } - }); - const summaries = visitValue(data.summaries, this, null) as Summary[]; - return {moduleName: data.moduleName, summaries, importAs: allImportAs}; - } - - override visitStringMap(map: {[key: string]: any}, context: any): any { - if ('__symbol' in map) { - const baseSymbol = this.symbols[map['__symbol']]; - const members = map['members']; - return members.length ? this.symbolCache.get(baseSymbol.filePath, baseSymbol.name, members) : - baseSymbol; - } else { - return super.visitStringMap(map, context); - } - } -} - -function isCall(metadata: any): boolean { - return metadata && metadata.__symbolic === 'call'; -} - -function isFunctionCall(metadata: any): boolean { - return isCall(metadata) && unwrapResolvedMetadata(metadata.expression) instanceof StaticSymbol; -} - -function isMethodCallOnVariable(metadata: any): boolean { - return isCall(metadata) && metadata.expression && metadata.expression.__symbolic === 'select' && - unwrapResolvedMetadata(metadata.expression.expression) instanceof StaticSymbol; -} diff --git a/packages/compiler/src/aot/util.ts b/packages/compiler/src/aot/util.ts deleted file mode 100644 index bd5b48888b1..00000000000 --- a/packages/compiler/src/aot/util.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 - */ - -const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; -const GENERATED_FILE = /\.ngfactory\.|\.ngsummary\./; -const JIT_SUMMARY_FILE = /\.ngsummary\./; -const JIT_SUMMARY_NAME = /NgSummary$/; - -export function ngfactoryFilePath(filePath: string, forceSourceFile = false): string { - const urlWithSuffix = splitTypescriptSuffix(filePath, forceSourceFile); - return `${urlWithSuffix[0]}.ngfactory${normalizeGenFileSuffix(urlWithSuffix[1])}`; -} - -export function stripGeneratedFileSuffix(filePath: string): string { - return filePath.replace(GENERATED_FILE, '.'); -} - -export function isGeneratedFile(filePath: string): boolean { - return GENERATED_FILE.test(filePath); -} - -export function splitTypescriptSuffix(path: string, forceSourceFile = false): string[] { - if (path.endsWith('.d.ts')) { - return [path.slice(0, -5), forceSourceFile ? '.ts' : '.d.ts']; - } - - const lastDot = path.lastIndexOf('.'); - - if (lastDot !== -1) { - return [path.substring(0, lastDot), path.substring(lastDot)]; - } - - return [path, '']; -} - -export function normalizeGenFileSuffix(srcFileSuffix: string): string { - return srcFileSuffix === '.tsx' ? '.ts' : srcFileSuffix; -} - -export function summaryFileName(fileName: string): string { - const fileNameWithoutSuffix = fileName.replace(STRIP_SRC_FILE_SUFFIXES, ''); - return `${fileNameWithoutSuffix}.ngsummary.json`; -} - -export function summaryForJitFileName(fileName: string, forceSourceFile = false): string { - const urlWithSuffix = splitTypescriptSuffix(stripGeneratedFileSuffix(fileName), forceSourceFile); - return `${urlWithSuffix[0]}.ngsummary${urlWithSuffix[1]}`; -} - -export function stripSummaryForJitFileSuffix(filePath: string): string { - return filePath.replace(JIT_SUMMARY_FILE, '.'); -} - -export function summaryForJitName(symbolName: string): string { - return `${symbolName}NgSummary`; -} - -export function stripSummaryForJitNameSuffix(symbolName: string): string { - return symbolName.replace(JIT_SUMMARY_NAME, ''); -} - -const LOWERED_SYMBOL = /\u0275\d+/; - -export function isLoweredSymbol(name: string) { - return LOWERED_SYMBOL.test(name); -} - -export function createLoweredSymbol(id: number): string { - return `\u0275${id}`; -} diff --git a/packages/compiler/src/compile_metadata.ts b/packages/compiler/src/compile_metadata.ts deleted file mode 100644 index f24046ab4dc..00000000000 --- a/packages/compiler/src/compile_metadata.ts +++ /dev/null @@ -1,755 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {StaticSymbol} from './aot/static_symbol'; -import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from './core'; -import {LifecycleHooks} from './lifecycle_reflector'; -import {ParseTreeResult as HtmlParseTreeResult} from './ml_parser/parser'; -import {CompileIdentifierMetadata, identifierName, sanitizeIdentifier} from './parse_util'; -import {splitAtColon} from './util'; - -// group 0: "[prop] or (event) or @trigger" -// group 1: "prop" from "[prop]" -// group 2: "event" from "(event)" -// group 3: "@trigger" from "@trigger" -const HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/; - -export function viewClassName(compType: any, embeddedTemplateIndex: number): string { - return `View_${identifierName({reference: compType})}_${embeddedTemplateIndex}`; -} - -export function rendererTypeName(compType: any): string { - return `RenderType_${identifierName({reference: compType})}`; -} - -export function hostViewClassName(compType: any): string { - return `HostView_${identifierName({reference: compType})}`; -} - -export function componentFactoryName(compType: any): string { - return `${identifierName({reference: compType})}NgFactory`; -} - -export interface ProxyClass { - setDelegate(delegate: any): void; -} - -export enum CompileSummaryKind { - Pipe, - Directive, - NgModule, - Injectable -} - -/** - * A CompileSummary is the data needed to use a directive / pipe / module - * in other modules / components. However, this data is not enough to compile - * the directive / module itself. - */ -export interface CompileTypeSummary { - summaryKind: CompileSummaryKind|null; - type: CompileTypeMetadata; -} - -export interface CompileDiDependencyMetadata { - isAttribute?: boolean; - isSelf?: boolean; - isHost?: boolean; - isSkipSelf?: boolean; - isOptional?: boolean; - isValue?: boolean; - token?: CompileTokenMetadata; - value?: any; -} - -export interface CompileProviderMetadata { - token: CompileTokenMetadata; - useClass?: CompileTypeMetadata; - useValue?: any; - useExisting?: CompileTokenMetadata; - useFactory?: CompileFactoryMetadata; - deps?: CompileDiDependencyMetadata[]; - multi?: boolean; -} - -export interface CompileFactoryMetadata extends CompileIdentifierMetadata { - diDeps: CompileDiDependencyMetadata[]; - reference: any; -} - -export function tokenName(token: CompileTokenMetadata) { - return token.value != null ? sanitizeIdentifier(token.value) : identifierName(token.identifier); -} - -export function tokenReference(token: CompileTokenMetadata) { - if (token.identifier != null) { - return token.identifier.reference; - } else { - return token.value; - } -} - -export interface CompileTokenMetadata { - value?: any; - identifier?: CompileIdentifierMetadata|CompileTypeMetadata; -} - -export interface CompileInjectableMetadata { - symbol: StaticSymbol; - type: CompileTypeMetadata; - - providedIn?: StaticSymbol; - - useValue?: any; - useClass?: StaticSymbol; - useExisting?: StaticSymbol; - useFactory?: StaticSymbol; - deps?: any[]; -} - -/** - * Metadata regarding compilation of a type. - */ -export interface CompileTypeMetadata extends CompileIdentifierMetadata { - diDeps: CompileDiDependencyMetadata[]; - lifecycleHooks: LifecycleHooks[]; - reference: any; -} - -export interface CompileQueryMetadata { - selectors: Array; - descendants: boolean; - first: boolean; - propertyName: string; - read: CompileTokenMetadata; - static?: boolean; - emitDistinctChangesOnly?: boolean; -} - -/** - * Metadata about a stylesheet - */ -export class CompileStylesheetMetadata { - moduleUrl: string|null; - styles: string[]; - styleUrls: string[]; - constructor( - {moduleUrl, styles, styleUrls}: - {moduleUrl?: string, styles?: string[], styleUrls?: string[]} = {}) { - this.moduleUrl = moduleUrl || null; - this.styles = _normalizeArray(styles); - this.styleUrls = _normalizeArray(styleUrls); - } -} - -/** - * Summary Metadata regarding compilation of a template. - */ -export interface CompileTemplateSummary { - ngContentSelectors: string[]; - encapsulation: ViewEncapsulation|null; - styles: string[]; - animations: any[]|null; -} - -/** - * Metadata regarding compilation of a template. - */ -export class CompileTemplateMetadata { - encapsulation: ViewEncapsulation|null; - template: string|null; - templateUrl: string|null; - htmlAst: HtmlParseTreeResult|null; - isInline: boolean; - styles: string[]; - styleUrls: string[]; - externalStylesheets: CompileStylesheetMetadata[]; - animations: any[]; - ngContentSelectors: string[]; - interpolation: [string, string]|null; - preserveWhitespaces: boolean; - constructor({ - encapsulation, - template, - templateUrl, - htmlAst, - styles, - styleUrls, - externalStylesheets, - animations, - ngContentSelectors, - interpolation, - isInline, - preserveWhitespaces - }: { - encapsulation: ViewEncapsulation|null, - template: string|null, - templateUrl: string|null, - htmlAst: HtmlParseTreeResult|null, - styles: string[], - styleUrls: string[], - externalStylesheets: CompileStylesheetMetadata[], - ngContentSelectors: string[], - animations: any[], - interpolation: [string, string]|null, - isInline: boolean, - preserveWhitespaces: boolean - }) { - this.encapsulation = encapsulation; - this.template = template; - this.templateUrl = templateUrl; - this.htmlAst = htmlAst; - this.styles = _normalizeArray(styles); - this.styleUrls = _normalizeArray(styleUrls); - this.externalStylesheets = _normalizeArray(externalStylesheets); - this.animations = animations ? flatten(animations) : []; - this.ngContentSelectors = ngContentSelectors || []; - if (interpolation && interpolation.length != 2) { - throw new Error(`'interpolation' should have a start and an end symbol.`); - } - this.interpolation = interpolation; - this.isInline = isInline; - this.preserveWhitespaces = preserveWhitespaces; - } - - toSummary(): CompileTemplateSummary { - return { - ngContentSelectors: this.ngContentSelectors, - encapsulation: this.encapsulation, - styles: this.styles, - animations: this.animations - }; - } -} - -export interface CompileEntryComponentMetadata { - componentType: any; - componentFactory: StaticSymbol|object; -} - -// Note: This should only use interfaces as nested data types -// as we need to be able to serialize this from/to JSON! -export interface CompileDirectiveSummary extends CompileTypeSummary { - type: CompileTypeMetadata; - isComponent: boolean; - selector: string|null; - exportAs: string|null; - inputs: {[key: string]: string}; - outputs: {[key: string]: string}; - hostListeners: {[key: string]: string}; - hostProperties: {[key: string]: string}; - hostAttributes: {[key: string]: string}; - providers: CompileProviderMetadata[]; - viewProviders: CompileProviderMetadata[]; - queries: CompileQueryMetadata[]; - guards: {[key: string]: any}; - viewQueries: CompileQueryMetadata[]; - entryComponents: CompileEntryComponentMetadata[]; - changeDetection: ChangeDetectionStrategy|null; - template: CompileTemplateSummary|null; - componentViewType: StaticSymbol|ProxyClass|null; - rendererType: StaticSymbol|object|null; - componentFactory: StaticSymbol|object|null; -} - -/** - * Metadata regarding compilation of a directive. - */ -export class CompileDirectiveMetadata { - static create({ - isHost, - type, - isComponent, - selector, - exportAs, - changeDetection, - inputs, - outputs, - host, - providers, - viewProviders, - queries, - guards, - viewQueries, - entryComponents, - template, - componentViewType, - rendererType, - componentFactory - }: { - isHost: boolean, - type: CompileTypeMetadata, - isComponent: boolean, - selector: string|null, - exportAs: string|null, - changeDetection: ChangeDetectionStrategy|null, - inputs: string[], - outputs: string[], - host: {[key: string]: string}, - providers: CompileProviderMetadata[], - viewProviders: CompileProviderMetadata[], - queries: CompileQueryMetadata[], - guards: {[key: string]: any}; - viewQueries: CompileQueryMetadata[], - entryComponents: CompileEntryComponentMetadata[], - template: CompileTemplateMetadata, - componentViewType: StaticSymbol|ProxyClass|null, - rendererType: StaticSymbol|object|null, - componentFactory: StaticSymbol|object|null, - }): CompileDirectiveMetadata { - const hostListeners: {[key: string]: string} = {}; - const hostProperties: {[key: string]: string} = {}; - const hostAttributes: {[key: string]: string} = {}; - if (host != null) { - Object.keys(host).forEach(key => { - const value = host[key]; - const matches = key.match(HOST_REG_EXP); - if (matches === null) { - hostAttributes[key] = value; - } else if (matches[1] != null) { - hostProperties[matches[1]] = value; - } else if (matches[2] != null) { - hostListeners[matches[2]] = value; - } - }); - } - const inputsMap: {[key: string]: string} = {}; - if (inputs != null) { - inputs.forEach((bindConfig: string) => { - // canonical syntax: `dirProp: elProp` - // if there is no `:`, use dirProp = elProp - const parts = splitAtColon(bindConfig, [bindConfig, bindConfig]); - inputsMap[parts[0]] = parts[1]; - }); - } - const outputsMap: {[key: string]: string} = {}; - if (outputs != null) { - outputs.forEach((bindConfig: string) => { - // canonical syntax: `dirProp: elProp` - // if there is no `:`, use dirProp = elProp - const parts = splitAtColon(bindConfig, [bindConfig, bindConfig]); - outputsMap[parts[0]] = parts[1]; - }); - } - - return new CompileDirectiveMetadata({ - isHost, - type, - isComponent: !!isComponent, - selector, - exportAs, - changeDetection, - inputs: inputsMap, - outputs: outputsMap, - hostListeners, - hostProperties, - hostAttributes, - providers, - viewProviders, - queries, - guards, - viewQueries, - entryComponents, - template, - componentViewType, - rendererType, - componentFactory, - }); - } - isHost: boolean; - type: CompileTypeMetadata; - isComponent: boolean; - selector: string|null; - exportAs: string|null; - changeDetection: ChangeDetectionStrategy|null; - inputs: {[key: string]: string}; - outputs: {[key: string]: string}; - hostListeners: {[key: string]: string}; - hostProperties: {[key: string]: string}; - hostAttributes: {[key: string]: string}; - providers: CompileProviderMetadata[]; - viewProviders: CompileProviderMetadata[]; - queries: CompileQueryMetadata[]; - guards: {[key: string]: any}; - viewQueries: CompileQueryMetadata[]; - entryComponents: CompileEntryComponentMetadata[]; - - template: CompileTemplateMetadata|null; - - componentViewType: StaticSymbol|ProxyClass|null; - rendererType: StaticSymbol|object|null; - componentFactory: StaticSymbol|object|null; - - constructor({ - isHost, - type, - isComponent, - selector, - exportAs, - changeDetection, - inputs, - outputs, - hostListeners, - hostProperties, - hostAttributes, - providers, - viewProviders, - queries, - guards, - viewQueries, - entryComponents, - template, - componentViewType, - rendererType, - componentFactory - }: { - isHost: boolean, - type: CompileTypeMetadata, - isComponent: boolean, - selector: string|null, - exportAs: string|null, - changeDetection: ChangeDetectionStrategy|null, - inputs: {[key: string]: string}, - outputs: {[key: string]: string}, - hostListeners: {[key: string]: string}, - hostProperties: {[key: string]: string}, - hostAttributes: {[key: string]: string}, - providers: CompileProviderMetadata[], - viewProviders: CompileProviderMetadata[], - queries: CompileQueryMetadata[], - guards: {[key: string]: any}, - viewQueries: CompileQueryMetadata[], - entryComponents: CompileEntryComponentMetadata[], - template: CompileTemplateMetadata|null, - componentViewType: StaticSymbol|ProxyClass|null, - rendererType: StaticSymbol|object|null, - componentFactory: StaticSymbol|object|null, - }) { - this.isHost = !!isHost; - this.type = type; - this.isComponent = isComponent; - this.selector = selector; - this.exportAs = exportAs; - this.changeDetection = changeDetection; - this.inputs = inputs; - this.outputs = outputs; - this.hostListeners = hostListeners; - this.hostProperties = hostProperties; - this.hostAttributes = hostAttributes; - this.providers = _normalizeArray(providers); - this.viewProviders = _normalizeArray(viewProviders); - this.queries = _normalizeArray(queries); - this.guards = guards; - this.viewQueries = _normalizeArray(viewQueries); - this.entryComponents = _normalizeArray(entryComponents); - this.template = template; - - this.componentViewType = componentViewType; - this.rendererType = rendererType; - this.componentFactory = componentFactory; - } - - toSummary(): CompileDirectiveSummary { - return { - summaryKind: CompileSummaryKind.Directive, - type: this.type, - isComponent: this.isComponent, - selector: this.selector, - exportAs: this.exportAs, - inputs: this.inputs, - outputs: this.outputs, - hostListeners: this.hostListeners, - hostProperties: this.hostProperties, - hostAttributes: this.hostAttributes, - providers: this.providers, - viewProviders: this.viewProviders, - queries: this.queries, - guards: this.guards, - viewQueries: this.viewQueries, - entryComponents: this.entryComponents, - changeDetection: this.changeDetection, - template: this.template && this.template.toSummary(), - componentViewType: this.componentViewType, - rendererType: this.rendererType, - componentFactory: this.componentFactory - }; - } -} - -export interface CompilePipeSummary extends CompileTypeSummary { - type: CompileTypeMetadata; - name: string; - pure: boolean; -} - -export class CompilePipeMetadata { - type: CompileTypeMetadata; - name: string; - pure: boolean; - - constructor({type, name, pure}: { - type: CompileTypeMetadata, - name: string, - pure: boolean, - }) { - this.type = type; - this.name = name; - this.pure = !!pure; - } - - toSummary(): CompilePipeSummary { - return { - summaryKind: CompileSummaryKind.Pipe, - type: this.type, - name: this.name, - pure: this.pure - }; - } -} - -// Note: This should only use interfaces as nested data types -// as we need to be able to serialize this from/to JSON! -export interface CompileNgModuleSummary extends CompileTypeSummary { - type: CompileTypeMetadata; - - // Note: This is transitive over the exported modules. - exportedDirectives: CompileIdentifierMetadata[]; - // Note: This is transitive over the exported modules. - exportedPipes: CompileIdentifierMetadata[]; - - // Note: This is transitive. - entryComponents: CompileEntryComponentMetadata[]; - // Note: This is transitive. - providers: {provider: CompileProviderMetadata, module: CompileIdentifierMetadata}[]; - // Note: This is transitive. - modules: CompileTypeMetadata[]; -} - -export class CompileShallowModuleMetadata { - // TODO(issue/24571): remove '!'. - type!: CompileTypeMetadata; - - rawExports: any; - rawImports: any; - rawProviders: any; -} - -/** - * Metadata regarding compilation of a module. - */ -export class CompileNgModuleMetadata { - type: CompileTypeMetadata; - declaredDirectives: CompileIdentifierMetadata[]; - exportedDirectives: CompileIdentifierMetadata[]; - declaredPipes: CompileIdentifierMetadata[]; - - exportedPipes: CompileIdentifierMetadata[]; - entryComponents: CompileEntryComponentMetadata[]; - bootstrapComponents: CompileIdentifierMetadata[]; - providers: CompileProviderMetadata[]; - - importedModules: CompileNgModuleSummary[]; - exportedModules: CompileNgModuleSummary[]; - schemas: SchemaMetadata[]; - id: string|null; - - transitiveModule: TransitiveCompileNgModuleMetadata; - - constructor({ - type, - providers, - declaredDirectives, - exportedDirectives, - declaredPipes, - exportedPipes, - entryComponents, - bootstrapComponents, - importedModules, - exportedModules, - schemas, - transitiveModule, - id - }: { - type: CompileTypeMetadata, - providers: CompileProviderMetadata[], - declaredDirectives: CompileIdentifierMetadata[], - exportedDirectives: CompileIdentifierMetadata[], - declaredPipes: CompileIdentifierMetadata[], - exportedPipes: CompileIdentifierMetadata[], - entryComponents: CompileEntryComponentMetadata[], - bootstrapComponents: CompileIdentifierMetadata[], - importedModules: CompileNgModuleSummary[], - exportedModules: CompileNgModuleSummary[], - transitiveModule: TransitiveCompileNgModuleMetadata, - schemas: SchemaMetadata[], - id: string|null - }) { - this.type = type || null; - this.declaredDirectives = _normalizeArray(declaredDirectives); - this.exportedDirectives = _normalizeArray(exportedDirectives); - this.declaredPipes = _normalizeArray(declaredPipes); - this.exportedPipes = _normalizeArray(exportedPipes); - this.providers = _normalizeArray(providers); - this.entryComponents = _normalizeArray(entryComponents); - this.bootstrapComponents = _normalizeArray(bootstrapComponents); - this.importedModules = _normalizeArray(importedModules); - this.exportedModules = _normalizeArray(exportedModules); - this.schemas = _normalizeArray(schemas); - this.id = id || null; - this.transitiveModule = transitiveModule || null; - } - - toSummary(): CompileNgModuleSummary { - const module = this.transitiveModule!; - return { - summaryKind: CompileSummaryKind.NgModule, - type: this.type, - entryComponents: module.entryComponents, - providers: module.providers, - modules: module.modules, - exportedDirectives: module.exportedDirectives, - exportedPipes: module.exportedPipes - }; - } -} - -export class TransitiveCompileNgModuleMetadata { - directivesSet = new Set(); - directives: CompileIdentifierMetadata[] = []; - exportedDirectivesSet = new Set(); - exportedDirectives: CompileIdentifierMetadata[] = []; - pipesSet = new Set(); - pipes: CompileIdentifierMetadata[] = []; - exportedPipesSet = new Set(); - exportedPipes: CompileIdentifierMetadata[] = []; - modulesSet = new Set(); - modules: CompileTypeMetadata[] = []; - entryComponentsSet = new Set(); - entryComponents: CompileEntryComponentMetadata[] = []; - - providers: {provider: CompileProviderMetadata, module: CompileIdentifierMetadata}[] = []; - - addProvider(provider: CompileProviderMetadata, module: CompileIdentifierMetadata) { - this.providers.push({provider: provider, module: module}); - } - - addDirective(id: CompileIdentifierMetadata) { - if (!this.directivesSet.has(id.reference)) { - this.directivesSet.add(id.reference); - this.directives.push(id); - } - } - addExportedDirective(id: CompileIdentifierMetadata) { - if (!this.exportedDirectivesSet.has(id.reference)) { - this.exportedDirectivesSet.add(id.reference); - this.exportedDirectives.push(id); - } - } - addPipe(id: CompileIdentifierMetadata) { - if (!this.pipesSet.has(id.reference)) { - this.pipesSet.add(id.reference); - this.pipes.push(id); - } - } - addExportedPipe(id: CompileIdentifierMetadata) { - if (!this.exportedPipesSet.has(id.reference)) { - this.exportedPipesSet.add(id.reference); - this.exportedPipes.push(id); - } - } - addModule(id: CompileTypeMetadata) { - if (!this.modulesSet.has(id.reference)) { - this.modulesSet.add(id.reference); - this.modules.push(id); - } - } - addEntryComponent(ec: CompileEntryComponentMetadata) { - if (!this.entryComponentsSet.has(ec.componentType)) { - this.entryComponentsSet.add(ec.componentType); - this.entryComponents.push(ec); - } - } -} - -function _normalizeArray(obj: any[]|undefined|null): any[] { - return obj || []; -} - -export class ProviderMeta { - token: any; - useClass: Type|null; - useValue: any; - useExisting: any; - useFactory: Function|null; - dependencies: Object[]|null; - multi: boolean; - - constructor(token: any, {useClass, useValue, useExisting, useFactory, deps, multi}: { - useClass?: Type, - useValue?: any, - useExisting?: any, - useFactory?: Function|null, - deps?: Object[]|null, - multi?: boolean - }) { - this.token = token; - this.useClass = useClass || null; - this.useValue = useValue; - this.useExisting = useExisting; - this.useFactory = useFactory || null; - this.dependencies = deps || null; - this.multi = !!multi; - } -} - -export function flatten(list: Array): T[] { - return list.reduce((flat: any[], item: T|T[]): T[] => { - const flatItem = Array.isArray(item) ? flatten(item) : item; - return (flat).concat(flatItem); - }, []); -} - -function jitSourceUrl(url: string) { - // Note: We need 3 "/" so that ng shows up as a separate domain - // in the chrome dev tools. - return url.replace(/(\w+:\/\/[\w:-]+)?(\/+)?/, 'ng:///'); -} - -export function templateSourceUrl( - ngModuleType: CompileIdentifierMetadata, compMeta: {type: CompileIdentifierMetadata}, - templateMeta: {isInline: boolean, templateUrl: string|null}) { - let url: string; - if (templateMeta.isInline) { - if (compMeta.type.reference instanceof StaticSymbol) { - // Note: a .ts file might contain multiple components with inline templates, - // so we need to give them unique urls, as these will be used for sourcemaps. - url = `${compMeta.type.reference.filePath}.${compMeta.type.reference.name}.html`; - } else { - url = `${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.html`; - } - } else { - url = templateMeta.templateUrl!; - } - return compMeta.type.reference instanceof StaticSymbol ? url : jitSourceUrl(url); -} - -export function sharedStylesheetJitUrl(meta: CompileStylesheetMetadata, id: number) { - const pathParts = meta.moduleUrl!.split(/\/\\/g); - const baseName = pathParts[pathParts.length - 1]; - return jitSourceUrl(`css/${id}${baseName}.ngstyle.js`); -} - -export function ngModuleJitUrl(moduleMeta: CompileNgModuleMetadata): string { - return jitSourceUrl(`${identifierName(moduleMeta.type)}/module.ngfactory.js`); -} - -export function templateJitUrl( - ngModuleType: CompileIdentifierMetadata, compMeta: CompileDirectiveMetadata): string { - return jitSourceUrl( - `${identifierName(ngModuleType)}/${identifierName(compMeta.type)}.ngfactory.js`); -} diff --git a/packages/compiler/src/compile_reflector.ts b/packages/compiler/src/compile_reflector.ts deleted file mode 100644 index 251b5db7ab6..00000000000 --- a/packages/compiler/src/compile_reflector.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {Component} from './core'; -import * as o from './output/output_ast'; - -/** - * Provides access to reflection data about symbols that the compiler needs. - */ -export abstract class CompileReflector { - abstract parameters(typeOrFunc: /*Type*/ any): any[][]; - abstract annotations(typeOrFunc: /*Type*/ any): any[]; - abstract shallowAnnotations(typeOrFunc: /*Type*/ any): any[]; - abstract tryAnnotations(typeOrFunc: /*Type*/ any): any[]; - abstract propMetadata(typeOrFunc: /*Type*/ any): {[key: string]: any[]}; - abstract hasLifecycleHook(type: any, lcProperty: string): boolean; - abstract guards(typeOrFunc: /* Type */ any): {[key: string]: any}; - abstract componentModuleUrl(type: /*Type*/ any, cmpMetadata: Component): string; - abstract resolveExternalReference(ref: o.ExternalReference): any; -} diff --git a/packages/compiler/src/compiler.ts b/packages/compiler/src/compiler.ts index 01db7147834..cc5f99950c8 100644 --- a/packages/compiler/src/compiler.ts +++ b/packages/compiler/src/compiler.ts @@ -35,40 +35,17 @@ export {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from './core'; export {core}; export * from './version'; -export * from './template_parser/template_ast'; export {CompilerConfig, preserveWhitespacesDefault} from './config'; -export * from './compile_metadata'; -export * from './aot/compiler_factory'; -export * from './aot/compiler'; -export * from './aot/generated_file'; -export * from './aot/compiler_options'; -export * from './aot/compiler_host'; -export * from './aot/formatted_error'; -export * from './aot/partial_module'; -export * from './aot/static_reflector'; -export * from './aot/static_symbol'; -export * from './aot/static_symbol_resolver'; -export * from './aot/summary_resolver'; -export {isLoweredSymbol, createLoweredSymbol} from './aot/util'; export * from './ast_path'; -export * from './summary_resolver'; export {Identifiers} from './identifiers'; -export {JitCompiler} from './jit/compiler'; -export * from './compile_reflector'; -export * from './url_resolver'; export * from './resource_loader'; export {ConstantPool} from './constant_pool'; -export {DirectiveResolver} from './directive_resolver'; -export {PipeResolver} from './pipe_resolver'; -export {NgModuleResolver} from './ng_module_resolver'; export {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './ml_parser/interpolation_config'; export * from './schema/element_schema_registry'; export * from './i18n/index'; -export * from './directive_normalizer'; export * from './expression_parser/ast'; export * from './expression_parser/lexer'; export * from './expression_parser/parser'; -export * from './metadata_resolver'; export * from './ml_parser/ast'; export * from './ml_parser/html_parser'; export * from './ml_parser/html_tags'; @@ -77,17 +54,12 @@ export * from './ml_parser/tags'; export {ParseTreeResult, TreeError} from './ml_parser/parser'; export {LexerRange} from './ml_parser/lexer'; export * from './ml_parser/xml_parser'; -export {NgModuleCompiler} from './ng_module_compiler'; export {ArrayType, AssertNotNull, DYNAMIC_TYPE, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinType, BuiltinTypeName, BuiltinVar, CastExpr, ClassField, ClassMethod, ClassStmt, CommaExpr, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, ExternalReference, literalMap, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, NONE_TYPE, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, TaggedTemplateExpr, TemplateLiteral, TemplateLiteralElement, ThrowStmt, TryCatchStmt, Type, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr, StmtModifier, Statement, STRING_TYPE, TypeofExpr, collectExternalReferences, jsDocComment, leadingComment, LeadingComment, JSDocComment, UnaryOperator, UnaryOperatorExpr, LocalizedString} from './output/output_ast'; export {EmitterVisitorContext} from './output/abstract_emitter'; export {JitEvaluator} from './output/output_jit'; -export * from './output/ts_emitter'; export * from './parse_util'; export * from './schema/dom_element_schema_registry'; export * from './selector'; -export * from './style_compiler'; -export * from './template_parser/template_parser'; -export {ViewCompiler} from './view_compiler/view_compiler'; export {Version} from './util'; export {SourceMap} from './output/source_map'; export * from './injectable_compiler_2'; diff --git a/packages/compiler/src/directive_normalizer.ts b/packages/compiler/src/directive_normalizer.ts deleted file mode 100644 index 3976338d812..00000000000 --- a/packages/compiler/src/directive_normalizer.ts +++ /dev/null @@ -1,329 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, templateSourceUrl} from './compile_metadata'; -import {CompilerConfig, preserveWhitespacesDefault} from './config'; -import {ViewEncapsulation} from './core'; -import * as html from './ml_parser/ast'; -import {HtmlParser} from './ml_parser/html_parser'; -import {InterpolationConfig} from './ml_parser/interpolation_config'; -import {ParseTreeResult as HtmlParseTreeResult} from './ml_parser/parser'; -import {syntaxError} from './parse_util'; -import {ResourceLoader} from './resource_loader'; -import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver'; -import {PreparsedElementType, preparseElement} from './template_parser/template_preparser'; -import {UrlResolver} from './url_resolver'; -import {isDefined, stringify, SyncAsync} from './util'; - -export interface PrenormalizedTemplateMetadata { - ngModuleType: any; - componentType: any; - moduleUrl: string; - template: string|null; - templateUrl: string|null; - styles: string[]; - styleUrls: string[]; - interpolation: [string, string]|null; - encapsulation: ViewEncapsulation|null; - animations: any[]; - preserveWhitespaces: boolean|null; -} - -export class DirectiveNormalizer { - private _resourceLoaderCache = new Map>(); - - constructor( - private _resourceLoader: ResourceLoader, private _urlResolver: UrlResolver, - private _htmlParser: HtmlParser, private _config: CompilerConfig) {} - - clearCache(): void { - this._resourceLoaderCache.clear(); - } - - clearCacheFor(normalizedDirective: CompileDirectiveMetadata): void { - if (!normalizedDirective.isComponent) { - return; - } - const template = normalizedDirective.template !; - this._resourceLoaderCache.delete(template.templateUrl!); - template.externalStylesheets.forEach((stylesheet) => { - this._resourceLoaderCache.delete(stylesheet.moduleUrl!); - }); - } - - private _fetch(url: string): SyncAsync { - let result = this._resourceLoaderCache.get(url); - if (!result) { - result = this._resourceLoader.get(url); - this._resourceLoaderCache.set(url, result); - } - return result; - } - - normalizeTemplate(prenormData: PrenormalizedTemplateMetadata): - SyncAsync { - if (isDefined(prenormData.template)) { - if (isDefined(prenormData.templateUrl)) { - throw syntaxError(`'${ - stringify(prenormData - .componentType)}' component cannot define both template and templateUrl`); - } - if (typeof prenormData.template !== 'string') { - throw syntaxError(`The template specified for component ${ - stringify(prenormData.componentType)} is not a string`); - } - } else if (isDefined(prenormData.templateUrl)) { - if (typeof prenormData.templateUrl !== 'string') { - throw syntaxError(`The templateUrl specified for component ${ - stringify(prenormData.componentType)} is not a string`); - } - } else { - throw syntaxError( - `No template specified for component ${stringify(prenormData.componentType)}`); - } - - if (isDefined(prenormData.preserveWhitespaces) && - typeof prenormData.preserveWhitespaces !== 'boolean') { - throw syntaxError(`The preserveWhitespaces option for component ${ - stringify(prenormData.componentType)} must be a boolean`); - } - - return SyncAsync.then( - this._preParseTemplate(prenormData), - (preparsedTemplate) => this._normalizeTemplateMetadata(prenormData, preparsedTemplate)); - } - - private _preParseTemplate(prenomData: PrenormalizedTemplateMetadata): - SyncAsync { - let template: SyncAsync; - let templateUrl: string; - if (prenomData.template != null) { - template = prenomData.template; - templateUrl = prenomData.moduleUrl; - } else { - templateUrl = this._urlResolver.resolve(prenomData.moduleUrl, prenomData.templateUrl!); - template = this._fetch(templateUrl); - } - return SyncAsync.then( - template, (template) => this._preparseLoadedTemplate(prenomData, template, templateUrl)); - } - - private _preparseLoadedTemplate( - prenormData: PrenormalizedTemplateMetadata, template: string, - templateAbsUrl: string): PreparsedTemplate { - const isInline = !!prenormData.template; - const interpolationConfig = InterpolationConfig.fromArray(prenormData.interpolation!); - const templateUrl = templateSourceUrl( - {reference: prenormData.ngModuleType}, {type: {reference: prenormData.componentType}}, - {isInline, templateUrl: templateAbsUrl}); - const rootNodesAndErrors = this._htmlParser.parse( - template, templateUrl, {tokenizeExpansionForms: true, interpolationConfig}); - if (rootNodesAndErrors.errors.length > 0) { - const errorString = rootNodesAndErrors.errors.join('\n'); - throw syntaxError(`Template parse errors:\n${errorString}`); - } - - const templateMetadataStyles = this._normalizeStylesheet(new CompileStylesheetMetadata( - {styles: prenormData.styles, moduleUrl: prenormData.moduleUrl})); - - const visitor = new TemplatePreparseVisitor(); - html.visitAll(visitor, rootNodesAndErrors.rootNodes); - const templateStyles = this._normalizeStylesheet(new CompileStylesheetMetadata( - {styles: visitor.styles, styleUrls: visitor.styleUrls, moduleUrl: templateAbsUrl})); - - const styles = templateMetadataStyles.styles.concat(templateStyles.styles); - - const inlineStyleUrls = templateMetadataStyles.styleUrls.concat(templateStyles.styleUrls); - const styleUrls = this - ._normalizeStylesheet(new CompileStylesheetMetadata( - {styleUrls: prenormData.styleUrls, moduleUrl: prenormData.moduleUrl})) - .styleUrls; - return { - template, - templateUrl: templateAbsUrl, - isInline, - htmlAst: rootNodesAndErrors, - styles, - inlineStyleUrls, - styleUrls, - ngContentSelectors: visitor.ngContentSelectors, - }; - } - - private _normalizeTemplateMetadata( - prenormData: PrenormalizedTemplateMetadata, - preparsedTemplate: PreparsedTemplate): SyncAsync { - return SyncAsync.then( - this._loadMissingExternalStylesheets( - preparsedTemplate.styleUrls.concat(preparsedTemplate.inlineStyleUrls)), - (externalStylesheets) => this._normalizeLoadedTemplateMetadata( - prenormData, preparsedTemplate, externalStylesheets)); - } - - private _normalizeLoadedTemplateMetadata( - prenormData: PrenormalizedTemplateMetadata, preparsedTemplate: PreparsedTemplate, - stylesheets: Map): CompileTemplateMetadata { - // Algorithm: - // - produce exactly 1 entry per original styleUrl in - // CompileTemplateMetadata.externalStylesheets with all styles inlined - // - inline all styles that are referenced by the template into CompileTemplateMetadata.styles. - // Reason: be able to determine how many stylesheets there are even without loading - // the template nor the stylesheets, so we can create a stub for TypeScript always synchronously - // (as resource loading may be async) - - const styles = [...preparsedTemplate.styles]; - this._inlineStyles(preparsedTemplate.inlineStyleUrls, stylesheets, styles); - const styleUrls = preparsedTemplate.styleUrls; - - const externalStylesheets = styleUrls.map(styleUrl => { - const stylesheet = stylesheets.get(styleUrl)!; - const styles = [...stylesheet.styles]; - this._inlineStyles(stylesheet.styleUrls, stylesheets, styles); - return new CompileStylesheetMetadata({moduleUrl: styleUrl, styles: styles}); - }); - - let encapsulation = prenormData.encapsulation; - if (encapsulation == null) { - encapsulation = this._config.defaultEncapsulation; - } - if (encapsulation === ViewEncapsulation.Emulated && styles.length === 0 && - styleUrls.length === 0) { - encapsulation = ViewEncapsulation.None; - } - return new CompileTemplateMetadata({ - encapsulation, - template: preparsedTemplate.template, - templateUrl: preparsedTemplate.templateUrl, - htmlAst: preparsedTemplate.htmlAst, - styles, - styleUrls, - ngContentSelectors: preparsedTemplate.ngContentSelectors, - animations: prenormData.animations, - interpolation: prenormData.interpolation, - isInline: preparsedTemplate.isInline, - externalStylesheets, - preserveWhitespaces: preserveWhitespacesDefault( - prenormData.preserveWhitespaces, this._config.preserveWhitespaces), - }); - } - - private _inlineStyles( - styleUrls: string[], stylesheets: Map, - targetStyles: string[]) { - styleUrls.forEach(styleUrl => { - const stylesheet = stylesheets.get(styleUrl)!; - stylesheet.styles.forEach(style => targetStyles.push(style)); - this._inlineStyles(stylesheet.styleUrls, stylesheets, targetStyles); - }); - } - - private _loadMissingExternalStylesheets( - styleUrls: string[], - loadedStylesheets: - Map = new Map()): - SyncAsync> { - return SyncAsync.then( - SyncAsync.all(styleUrls.filter((styleUrl) => !loadedStylesheets.has(styleUrl)) - .map( - styleUrl => SyncAsync.then( - this._fetch(styleUrl), - (loadedStyle) => { - const stylesheet = - this._normalizeStylesheet(new CompileStylesheetMetadata( - {styles: [loadedStyle], moduleUrl: styleUrl})); - loadedStylesheets.set(styleUrl, stylesheet); - return this._loadMissingExternalStylesheets( - stylesheet.styleUrls, loadedStylesheets); - }))), - (_) => loadedStylesheets); - } - - private _normalizeStylesheet(stylesheet: CompileStylesheetMetadata): CompileStylesheetMetadata { - const moduleUrl = stylesheet.moduleUrl!; - const allStyleUrls = stylesheet.styleUrls.filter(isStyleUrlResolvable) - .map(url => this._urlResolver.resolve(moduleUrl, url)); - - const allStyles = stylesheet.styles.map(style => { - const styleWithImports = extractStyleUrls(this._urlResolver, moduleUrl, style); - allStyleUrls.push(...styleWithImports.styleUrls); - return styleWithImports.style; - }); - - return new CompileStylesheetMetadata( - {styles: allStyles, styleUrls: allStyleUrls, moduleUrl: moduleUrl}); - } -} - -interface PreparsedTemplate { - template: string; - templateUrl: string; - isInline: boolean; - htmlAst: HtmlParseTreeResult; - styles: string[]; - inlineStyleUrls: string[]; - styleUrls: string[]; - ngContentSelectors: string[]; -} - -class TemplatePreparseVisitor implements html.Visitor { - ngContentSelectors: string[] = []; - styles: string[] = []; - styleUrls: string[] = []; - ngNonBindableStackCount: number = 0; - - visitElement(ast: html.Element, context: any): any { - const preparsedElement = preparseElement(ast); - switch (preparsedElement.type) { - case PreparsedElementType.NG_CONTENT: - if (this.ngNonBindableStackCount === 0) { - this.ngContentSelectors.push(preparsedElement.selectAttr); - } - break; - case PreparsedElementType.STYLE: - let textContent = ''; - ast.children.forEach(child => { - if (child instanceof html.Text) { - textContent += child.value; - } - }); - this.styles.push(textContent); - break; - case PreparsedElementType.STYLESHEET: - this.styleUrls.push(preparsedElement.hrefAttr); - break; - default: - break; - } - if (preparsedElement.nonBindable) { - this.ngNonBindableStackCount++; - } - html.visitAll(this, ast.children); - if (preparsedElement.nonBindable) { - this.ngNonBindableStackCount--; - } - return null; - } - - visitExpansion(ast: html.Expansion, context: any): any { - html.visitAll(this, ast.cases); - } - - visitExpansionCase(ast: html.ExpansionCase, context: any): any { - html.visitAll(this, ast.expression); - } - - visitComment(ast: html.Comment, context: any): any { - return null; - } - visitAttribute(ast: html.Attribute, context: any): any { - return null; - } - visitText(ast: html.Text, context: any): any { - return null; - } -} diff --git a/packages/compiler/src/directive_resolver.ts b/packages/compiler/src/directive_resolver.ts deleted file mode 100644 index a4c8a09a8d5..00000000000 --- a/packages/compiler/src/directive_resolver.ts +++ /dev/null @@ -1,191 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {CompileReflector} from './compile_reflector'; -import {Component, createComponent, createContentChild, createContentChildren, createDirective, createHostBinding, createHostListener, createInput, createOutput, createViewChild, createViewChildren, Directive, Type} from './core'; -import {resolveForwardRef, splitAtColon, stringify} from './util'; - -const QUERY_METADATA_IDENTIFIERS = [ - createViewChild, - createViewChildren, - createContentChild, - createContentChildren, -]; - -/* - * Resolve a `Type` for {@link Directive}. - * - * This interface can be overridden by the application developer to create custom behavior. - * - * See {@link Compiler} - */ -export class DirectiveResolver { - constructor(private _reflector: CompileReflector) {} - - isDirective(type: Type) { - const typeMetadata = this._reflector.annotations(resolveForwardRef(type)); - return typeMetadata && typeMetadata.some(isDirectiveMetadata); - } - - /** - * Return {@link Directive} for a given `Type`. - */ - resolve(type: Type): Directive; - resolve(type: Type, throwIfNotFound: true): Directive; - resolve(type: Type, throwIfNotFound: boolean): Directive|null; - resolve(type: Type, throwIfNotFound = true): Directive|null { - const typeMetadata = this._reflector.annotations(resolveForwardRef(type)); - if (typeMetadata) { - const metadata = findLast(typeMetadata, isDirectiveMetadata); - if (metadata) { - const propertyMetadata = this._reflector.propMetadata(type); - const guards = this._reflector.guards(type); - return this._mergeWithPropertyMetadata(metadata, propertyMetadata, guards, type); - } - } - - if (throwIfNotFound) { - throw new Error(`No Directive annotation found on ${stringify(type)}`); - } - - return null; - } - - private _mergeWithPropertyMetadata( - dm: Directive, propertyMetadata: {[key: string]: any[]}, guards: {[key: string]: any}, - directiveType: Type): Directive { - const inputs: string[] = []; - const outputs: string[] = []; - const host: {[key: string]: string} = {}; - const queries: {[key: string]: any} = {}; - Object.keys(propertyMetadata).forEach((propName: string) => { - const input = findLast(propertyMetadata[propName], (a) => createInput.isTypeOf(a)); - if (input) { - if (input.bindingPropertyName) { - inputs.push(`${propName}: ${input.bindingPropertyName}`); - } else { - inputs.push(propName); - } - } - const output = findLast(propertyMetadata[propName], (a) => createOutput.isTypeOf(a)); - if (output) { - if (output.bindingPropertyName) { - outputs.push(`${propName}: ${output.bindingPropertyName}`); - } else { - outputs.push(propName); - } - } - const hostBindings = propertyMetadata[propName].filter(a => createHostBinding.isTypeOf(a)); - hostBindings.forEach(hostBinding => { - if (hostBinding.hostPropertyName) { - const startWith = hostBinding.hostPropertyName[0]; - if (startWith === '(') { - throw new Error(`@HostBinding can not bind to events. Use @HostListener instead.`); - } else if (startWith === '[') { - throw new Error( - `@HostBinding parameter should be a property name, 'class.', or 'attr.'.`); - } - host[`[${hostBinding.hostPropertyName}]`] = propName; - } else { - host[`[${propName}]`] = propName; - } - }); - const hostListeners = propertyMetadata[propName].filter(a => createHostListener.isTypeOf(a)); - hostListeners.forEach(hostListener => { - const args = hostListener.args || []; - host[`(${hostListener.eventName})`] = `${propName}(${args.join(',')})`; - }); - const query = findLast( - propertyMetadata[propName], (a) => QUERY_METADATA_IDENTIFIERS.some(i => i.isTypeOf(a))); - if (query) { - queries[propName] = query; - } - }); - return this._merge(dm, inputs, outputs, host, queries, guards, directiveType); - } - - private _extractPublicName(def: string) { - return splitAtColon(def, [null!, def])[1].trim(); - } - - private _dedupeBindings(bindings: string[]): string[] { - const names = new Set(); - const publicNames = new Set(); - const reversedResult: string[] = []; - // go last to first to allow later entries to overwrite previous entries - for (let i = bindings.length - 1; i >= 0; i--) { - const binding = bindings[i]; - const name = this._extractPublicName(binding); - publicNames.add(name); - if (!names.has(name)) { - names.add(name); - reversedResult.push(binding); - } - } - return reversedResult.reverse(); - } - - private _merge( - directive: Directive, inputs: string[], outputs: string[], host: {[key: string]: string}, - queries: {[key: string]: any}, guards: {[key: string]: any}, directiveType: Type): Directive { - const mergedInputs = - this._dedupeBindings(directive.inputs ? directive.inputs.concat(inputs) : inputs); - const mergedOutputs = - this._dedupeBindings(directive.outputs ? directive.outputs.concat(outputs) : outputs); - const mergedHost = directive.host ? {...directive.host, ...host} : host; - const mergedQueries = directive.queries ? {...directive.queries, ...queries} : queries; - if (createComponent.isTypeOf(directive)) { - const comp = directive as Component; - return createComponent({ - selector: comp.selector, - inputs: mergedInputs, - outputs: mergedOutputs, - host: mergedHost, - exportAs: comp.exportAs, - moduleId: comp.moduleId, - queries: mergedQueries, - changeDetection: comp.changeDetection, - providers: comp.providers, - viewProviders: comp.viewProviders, - entryComponents: comp.entryComponents, - template: comp.template, - templateUrl: comp.templateUrl, - styles: comp.styles, - styleUrls: comp.styleUrls, - encapsulation: comp.encapsulation, - animations: comp.animations, - interpolation: comp.interpolation, - preserveWhitespaces: directive.preserveWhitespaces, - }); - } else { - return createDirective({ - selector: directive.selector, - inputs: mergedInputs, - outputs: mergedOutputs, - host: mergedHost, - exportAs: directive.exportAs, - queries: mergedQueries, - providers: directive.providers, - guards - }); - } - } -} - -function isDirectiveMetadata(type: any): type is Directive { - return createDirective.isTypeOf(type) || createComponent.isTypeOf(type); -} - -export function findLast(arr: T[], condition: (value: T) => boolean): T|null { - for (let i = arr.length - 1; i >= 0; i--) { - if (condition(arr[i])) { - return arr[i]; - } - } - return null; -} diff --git a/packages/compiler/src/i18n/extractor.ts b/packages/compiler/src/i18n/extractor.ts deleted file mode 100644 index b574db85d0f..00000000000 --- a/packages/compiler/src/i18n/extractor.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 - */ - - -/** - * Extract i18n messages from source code - */ -import {analyzeAndValidateNgModules} from '../aot/compiler'; -import {createAotUrlResolver} from '../aot/compiler_factory'; -import {StaticReflector} from '../aot/static_reflector'; -import {StaticSymbolCache} from '../aot/static_symbol'; -import {StaticSymbolResolver, StaticSymbolResolverHost} from '../aot/static_symbol_resolver'; -import {AotSummaryResolver, AotSummaryResolverHost} from '../aot/summary_resolver'; -import {CompileDirectiveMetadata} from '../compile_metadata'; -import {CompilerConfig} from '../config'; -import {ViewEncapsulation} from '../core'; -import {DirectiveNormalizer} from '../directive_normalizer'; -import {DirectiveResolver} from '../directive_resolver'; -import {CompileMetadataResolver} from '../metadata_resolver'; -import {HtmlParser} from '../ml_parser/html_parser'; -import {InterpolationConfig} from '../ml_parser/interpolation_config'; -import {NgModuleResolver} from '../ng_module_resolver'; -import {ParseError} from '../parse_util'; -import {PipeResolver} from '../pipe_resolver'; -import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry'; - -import {MessageBundle} from './message_bundle'; - - - -/** - * The host of the Extractor disconnects the implementation from TypeScript / other language - * services and from underlying file systems. - */ -export interface ExtractorHost extends StaticSymbolResolverHost, AotSummaryResolverHost { - /** - * Converts a path that refers to a resource into an absolute filePath - * that can be later on used for loading the resource via `loadResource. - */ - resourceNameToFileName(path: string, containingFile: string): string|null; - /** - * Loads a resource (e.g. html / css) - */ - loadResource(path: string): Promise|string; -} - -export class Extractor { - constructor( - public host: ExtractorHost, private staticSymbolResolver: StaticSymbolResolver, - private messageBundle: MessageBundle, private metadataResolver: CompileMetadataResolver) {} - - extract(rootFiles: string[]): Promise { - const {files, ngModules} = analyzeAndValidateNgModules( - rootFiles, this.host, this.staticSymbolResolver, this.metadataResolver); - return Promise - .all(ngModules.map( - ngModule => this.metadataResolver.loadNgModuleDirectiveAndPipeMetadata( - ngModule.type.reference, false))) - .then(() => { - const errors: ParseError[] = []; - - files.forEach(file => { - const compMetas: CompileDirectiveMetadata[] = []; - file.directives.forEach(directiveType => { - const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType); - if (dirMeta && dirMeta.isComponent) { - compMetas.push(dirMeta); - } - }); - compMetas.forEach(compMeta => { - const html = compMeta.template !.template !; - // Template URL points to either an HTML or TS file depending on - // whether the file is used with `templateUrl:` or `template:`, - // respectively. - const templateUrl = compMeta.template !.templateUrl!; - const interpolationConfig = - InterpolationConfig.fromArray(compMeta.template !.interpolation); - errors.push(...this.messageBundle.updateFromTemplate( - html, templateUrl, interpolationConfig)!); - }); - }); - - if (errors.length) { - throw new Error(errors.map(e => e.toString()).join('\n')); - } - - return this.messageBundle; - }); - } - - static create(host: ExtractorHost, locale: string|null): - {extractor: Extractor, staticReflector: StaticReflector} { - const htmlParser = new HtmlParser(); - - const urlResolver = createAotUrlResolver(host); - const symbolCache = new StaticSymbolCache(); - const summaryResolver = new AotSummaryResolver(host, symbolCache); - const staticSymbolResolver = new StaticSymbolResolver(host, symbolCache, summaryResolver); - const staticReflector = new StaticReflector(summaryResolver, staticSymbolResolver); - - const config = - new CompilerConfig({defaultEncapsulation: ViewEncapsulation.Emulated, useJit: false}); - - const normalizer = new DirectiveNormalizer( - {get: (url: string) => host.loadResource(url)}, urlResolver, htmlParser, config); - const elementSchemaRegistry = new DomElementSchemaRegistry(); - const resolver = new CompileMetadataResolver( - config, htmlParser, new NgModuleResolver(staticReflector), - new DirectiveResolver(staticReflector), new PipeResolver(staticReflector), summaryResolver, - elementSchemaRegistry, normalizer, console, symbolCache, staticReflector); - - // TODO(vicb): implicit tags & attributes - const messageBundle = new MessageBundle(htmlParser, [], {}, locale); - - const extractor = new Extractor(host, staticSymbolResolver, messageBundle, resolver); - return {extractor, staticReflector}; - } -} diff --git a/packages/compiler/src/i18n/index.ts b/packages/compiler/src/i18n/index.ts index 8e6eb867306..6af92a7321d 100644 --- a/packages/compiler/src/i18n/index.ts +++ b/packages/compiler/src/i18n/index.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ export {computeMsgId} from './digest'; -export {Extractor, ExtractorHost} from './extractor'; export {I18NHtmlParser} from './i18n_html_parser'; export {MessageBundle} from './message_bundle'; export {Serializer} from './serializers/serializer'; diff --git a/packages/compiler/src/identifiers.ts b/packages/compiler/src/identifiers.ts index 0aca3f0a39a..c7d0209ce8c 100644 --- a/packages/compiler/src/identifiers.ts +++ b/packages/compiler/src/identifiers.ts @@ -6,8 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {CompileTokenMetadata} from './compile_metadata'; -import {CompileReflector} from './compile_reflector'; import * as o from './output/output_ast'; const CORE = '@angular/core'; @@ -95,20 +93,6 @@ export class Identifiers { static EMPTY_ARRAY: o.ExternalReference = {name: 'ɵEMPTY_ARRAY', moduleName: CORE}; static EMPTY_MAP: o.ExternalReference = {name: 'ɵEMPTY_MAP', moduleName: CORE}; static Renderer: o.ExternalReference = {name: 'Renderer', moduleName: CORE}; - static viewDef: o.ExternalReference = {name: 'ɵvid', moduleName: CORE}; - static elementDef: o.ExternalReference = {name: 'ɵeld', moduleName: CORE}; - static anchorDef: o.ExternalReference = {name: 'ɵand', moduleName: CORE}; - static textDef: o.ExternalReference = {name: 'ɵted', moduleName: CORE}; - static directiveDef: o.ExternalReference = {name: 'ɵdid', moduleName: CORE}; - static providerDef: o.ExternalReference = {name: 'ɵprd', moduleName: CORE}; - static queryDef: o.ExternalReference = {name: 'ɵqud', moduleName: CORE}; - static pureArrayDef: o.ExternalReference = {name: 'ɵpad', moduleName: CORE}; - static pureObjectDef: o.ExternalReference = {name: 'ɵpod', moduleName: CORE}; - static purePipeDef: o.ExternalReference = {name: 'ɵppd', moduleName: CORE}; - static pipeDef: o.ExternalReference = {name: 'ɵpid', moduleName: CORE}; - static nodeValue: o.ExternalReference = {name: 'ɵnov', moduleName: CORE}; - static ngContentDef: o.ExternalReference = {name: 'ɵncd', moduleName: CORE}; - static createRendererType2: o.ExternalReference = {name: 'ɵcrt', moduleName: CORE}; // type only static RendererType2: o.ExternalReference = { name: 'RendererType2', @@ -122,12 +106,3 @@ export class Identifiers { }; static createComponentFactory: o.ExternalReference = {name: 'ɵccf', moduleName: CORE}; } - -export function createTokenForReference(reference: any): CompileTokenMetadata { - return {identifier: {reference: reference}}; -} - -export function createTokenForExternalReference( - reflector: CompileReflector, reference: o.ExternalReference): CompileTokenMetadata { - return createTokenForReference(reflector.resolveExternalReference(reference)); -} diff --git a/packages/compiler/src/injectable_compiler.ts b/packages/compiler/src/injectable_compiler.ts deleted file mode 100644 index 8af182b7b33..00000000000 --- a/packages/compiler/src/injectable_compiler.ts +++ /dev/null @@ -1,135 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {StaticSymbol} from './aot/static_symbol'; -import {CompileInjectableMetadata} from './compile_metadata'; -import {CompileReflector} from './compile_reflector'; -import {OutputContext} from './constant_pool'; -import {InjectFlags} from './core'; -import {Identifiers} from './identifiers'; -import * as o from './output/output_ast'; -import {convertValueToOutputAst} from './output/value_util'; -import {identifierName} from './parse_util'; -import {Identifiers as R3} from './render3/r3_identifiers'; - -type MapEntry = { - key: string, - quoted: boolean, - value: o.Expression -}; -type MapLiteral = MapEntry[]; - -function mapEntry(key: string, value: o.Expression): MapEntry { - return {key, value, quoted: false}; -} - -export class InjectableCompiler { - private tokenInjector: StaticSymbol; - constructor(private reflector: CompileReflector, private alwaysGenerateDef: boolean) { - this.tokenInjector = reflector.resolveExternalReference(Identifiers.Injector); - } - - private depsArray(deps: any[], ctx: OutputContext): o.Expression[] { - return deps.map(dep => { - let token = dep; - let args = [token]; - let flags: InjectFlags = InjectFlags.Default; - if (Array.isArray(dep)) { - for (let i = 0; i < dep.length; i++) { - const v = dep[i]; - if (v) { - if (v.ngMetadataName === 'Optional') { - flags |= InjectFlags.Optional; - } else if (v.ngMetadataName === 'SkipSelf') { - flags |= InjectFlags.SkipSelf; - } else if (v.ngMetadataName === 'Self') { - flags |= InjectFlags.Self; - } else if (v.ngMetadataName === 'Inject') { - token = v.token; - } else { - token = v; - } - } - } - } - - let tokenExpr: o.Expression; - if (typeof token === 'string') { - tokenExpr = o.literal(token); - } else if (token === this.tokenInjector) { - tokenExpr = o.importExpr(Identifiers.INJECTOR); - } else { - tokenExpr = ctx.importExpr(token); - } - - if (flags !== InjectFlags.Default) { - args = [tokenExpr, o.literal(flags)]; - } else { - args = [tokenExpr]; - } - return o.importExpr(Identifiers.inject).callFn(args); - }); - } - - factoryFor(injectable: CompileInjectableMetadata, ctx: OutputContext): o.Expression { - let retValue: o.Expression; - if (injectable.useExisting) { - retValue = o.importExpr(Identifiers.inject).callFn([ctx.importExpr(injectable.useExisting)]); - } else if (injectable.useFactory) { - const deps = injectable.deps || []; - if (deps.length > 0) { - retValue = ctx.importExpr(injectable.useFactory).callFn(this.depsArray(deps, ctx)); - } else { - return ctx.importExpr(injectable.useFactory); - } - } else if (injectable.useValue) { - retValue = convertValueToOutputAst(ctx, injectable.useValue); - } else { - const clazz = injectable.useClass || injectable.symbol; - const depArgs = this.depsArray(this.reflector.parameters(clazz), ctx); - retValue = new o.InstantiateExpr(ctx.importExpr(clazz), depArgs); - } - return o.fn( - [], [new o.ReturnStatement(retValue)], undefined, undefined, - injectable.symbol.name + '_Factory'); - } - - injectableDef(injectable: CompileInjectableMetadata, ctx: OutputContext): o.Expression { - let providedIn: o.Expression = o.NULL_EXPR; - if (injectable.providedIn !== undefined) { - if (injectable.providedIn === null) { - providedIn = o.NULL_EXPR; - } else if (typeof injectable.providedIn === 'string') { - providedIn = o.literal(injectable.providedIn); - } else { - providedIn = ctx.importExpr(injectable.providedIn); - } - } - const def: MapLiteral = [ - mapEntry('factory', this.factoryFor(injectable, ctx)), - mapEntry('token', ctx.importExpr(injectable.type.reference)), - mapEntry('providedIn', providedIn), - ]; - return o.importExpr(R3.ɵɵdefineInjectable).callFn([o.literalMap(def)], undefined, true); - } - - compile(injectable: CompileInjectableMetadata, ctx: OutputContext): void { - if (this.alwaysGenerateDef || injectable.providedIn !== undefined) { - const className = identifierName(injectable.type)!; - const clazz = new o.ClassStmt( - className, null, - [ - new o.ClassField( - 'ɵprov', o.INFERRED_TYPE, [o.StmtModifier.Static], - this.injectableDef(injectable, ctx)), - ], - [], new o.ClassMethod(null, [], []), []); - ctx.statements.push(clazz); - } - } -} diff --git a/packages/compiler/src/jit/compiler.ts b/packages/compiler/src/jit/compiler.ts deleted file mode 100644 index ded36fc38fa..00000000000 --- a/packages/compiler/src/jit/compiler.ts +++ /dev/null @@ -1,365 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {CompileDirectiveMetadata, CompileNgModuleMetadata, CompilePipeSummary, CompileProviderMetadata, CompileTypeSummary, ngModuleJitUrl, ProxyClass, sharedStylesheetJitUrl, templateJitUrl, templateSourceUrl} from '../compile_metadata'; -import {CompileReflector} from '../compile_reflector'; -import {CompilerConfig} from '../config'; -import {ConstantPool, OutputContext} from '../constant_pool'; -import {Type} from '../core'; -import {CompileMetadataResolver} from '../metadata_resolver'; -import {NgModuleCompiler} from '../ng_module_compiler'; -import * as ir from '../output/output_ast'; -import {interpretStatements} from '../output/output_interpreter'; -import {JitEvaluator} from '../output/output_jit'; -import {CompileIdentifierMetadata, identifierName} from '../parse_util'; -import {CompiledStylesheet, StyleCompiler} from '../style_compiler'; -import {SummaryResolver} from '../summary_resolver'; -import {TemplateAst} from '../template_parser/template_ast'; -import {TemplateParser} from '../template_parser/template_parser'; -import {Console, stringify, SyncAsync} from '../util'; -import {ViewCompiler} from '../view_compiler/view_compiler'; - -export interface ModuleWithComponentFactories { - ngModuleFactory: object; - componentFactories: object[]; -} - -/** - * An internal module of the Angular compiler that begins with component types, - * extracts templates, and eventually produces a compiled version of the component - * ready for linking into an application. - * - * @security When compiling templates at runtime, you must ensure that the entire template comes - * from a trusted source. Attacker-controlled data introduced by a template could expose your - * application to XSS risks. For more detail, see the [Security Guide](https://g.co/ng/security). - */ -export class JitCompiler { - private _compiledTemplateCache = new Map(); - private _compiledHostTemplateCache = new Map(); - private _compiledDirectiveWrapperCache = new Map(); - private _compiledNgModuleCache = new Map(); - private _sharedStylesheetCount = 0; - private _addedAotSummaries = new Set<() => any[]>(); - - constructor( - private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser, - private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, - private _ngModuleCompiler: NgModuleCompiler, private _summaryResolver: SummaryResolver, - private _reflector: CompileReflector, private _jitEvaluator: JitEvaluator, - private _compilerConfig: CompilerConfig, private _console: Console, - private getExtraNgModuleProviders: (ngModule: any) => CompileProviderMetadata[]) {} - - compileModuleSync(moduleType: Type): object { - return SyncAsync.assertSync(this._compileModuleAndComponents(moduleType, true)); - } - - compileModuleAsync(moduleType: Type): Promise { - return Promise.resolve(this._compileModuleAndComponents(moduleType, false)); - } - - compileModuleAndAllComponentsSync(moduleType: Type): ModuleWithComponentFactories { - return SyncAsync.assertSync(this._compileModuleAndAllComponents(moduleType, true)); - } - - compileModuleAndAllComponentsAsync(moduleType: Type): Promise { - return Promise.resolve(this._compileModuleAndAllComponents(moduleType, false)); - } - - getComponentFactory(component: Type): object { - const summary = this._metadataResolver.getDirectiveSummary(component); - return summary.componentFactory as object; - } - - loadAotSummaries(summaries: () => any[]) { - this.clearCache(); - this._addAotSummaries(summaries); - } - - private _addAotSummaries(fn: () => any[]) { - if (this._addedAotSummaries.has(fn)) { - return; - } - this._addedAotSummaries.add(fn); - const summaries = fn(); - for (let i = 0; i < summaries.length; i++) { - const entry = summaries[i]; - if (typeof entry === 'function') { - this._addAotSummaries(entry); - } else { - const summary = entry as CompileTypeSummary; - this._summaryResolver.addSummary( - {symbol: summary.type.reference, metadata: null, type: summary}); - } - } - } - - hasAotSummary(ref: Type) { - return !!this._summaryResolver.resolveSummary(ref); - } - - private _filterJitIdentifiers(ids: CompileIdentifierMetadata[]): any[] { - return ids.map(mod => mod.reference).filter((ref) => !this.hasAotSummary(ref)); - } - - private _compileModuleAndComponents(moduleType: Type, isSync: boolean): SyncAsync { - return SyncAsync.then(this._loadModules(moduleType, isSync), () => { - this._compileComponents(moduleType, null); - return this._compileModule(moduleType); - }); - } - - private _compileModuleAndAllComponents(moduleType: Type, isSync: boolean): - SyncAsync { - return SyncAsync.then(this._loadModules(moduleType, isSync), () => { - const componentFactories: object[] = []; - this._compileComponents(moduleType, componentFactories); - return { - ngModuleFactory: this._compileModule(moduleType), - componentFactories: componentFactories - }; - }); - } - - private _loadModules(mainModule: any, isSync: boolean): SyncAsync { - const loading: Promise[] = []; - const mainNgModule = this._metadataResolver.getNgModuleMetadata(mainModule)!; - // Note: for runtime compilation, we want to transitively compile all modules, - // so we also need to load the declared directives / pipes for all nested modules. - this._filterJitIdentifiers(mainNgModule.transitiveModule.modules).forEach((nestedNgModule) => { - // getNgModuleMetadata only returns null if the value passed in is not an NgModule - const moduleMeta = this._metadataResolver.getNgModuleMetadata(nestedNgModule)!; - this._filterJitIdentifiers(moduleMeta.declaredDirectives).forEach((ref) => { - const promise = - this._metadataResolver.loadDirectiveMetadata(moduleMeta.type.reference, ref, isSync); - if (promise) { - loading.push(promise); - } - }); - this._filterJitIdentifiers(moduleMeta.declaredPipes) - .forEach((ref) => this._metadataResolver.getOrLoadPipeMetadata(ref)); - }); - return SyncAsync.all(loading); - } - - private _compileModule(moduleType: Type): object { - let ngModuleFactory = this._compiledNgModuleCache.get(moduleType)!; - if (!ngModuleFactory) { - const moduleMeta = this._metadataResolver.getNgModuleMetadata(moduleType)!; - // Always provide a bound Compiler - const extraProviders = this.getExtraNgModuleProviders(moduleMeta.type.reference); - const outputCtx = createOutputContext(); - const compileResult = this._ngModuleCompiler.compile(outputCtx, moduleMeta, extraProviders); - ngModuleFactory = this._interpretOrJit( - ngModuleJitUrl(moduleMeta), outputCtx.statements)[compileResult.ngModuleFactoryVar]; - this._compiledNgModuleCache.set(moduleMeta.type.reference, ngModuleFactory); - } - return ngModuleFactory; - } - - /** - * @internal - */ - _compileComponents(mainModule: Type, allComponentFactories: object[]|null) { - const ngModule = this._metadataResolver.getNgModuleMetadata(mainModule)!; - const moduleByJitDirective = new Map(); - const templates = new Set(); - - const transJitModules = this._filterJitIdentifiers(ngModule.transitiveModule.modules); - transJitModules.forEach((localMod) => { - const localModuleMeta = this._metadataResolver.getNgModuleMetadata(localMod)!; - this._filterJitIdentifiers(localModuleMeta.declaredDirectives).forEach((dirRef) => { - moduleByJitDirective.set(dirRef, localModuleMeta); - const dirMeta = this._metadataResolver.getDirectiveMetadata(dirRef); - if (dirMeta.isComponent) { - templates.add(this._createCompiledTemplate(dirMeta, localModuleMeta)); - if (allComponentFactories) { - const template = - this._createCompiledHostTemplate(dirMeta.type.reference, localModuleMeta); - templates.add(template); - allComponentFactories.push(dirMeta.componentFactory as object); - } - } - }); - }); - transJitModules.forEach((localMod) => { - const localModuleMeta = this._metadataResolver.getNgModuleMetadata(localMod)!; - this._filterJitIdentifiers(localModuleMeta.declaredDirectives).forEach((dirRef) => { - const dirMeta = this._metadataResolver.getDirectiveMetadata(dirRef); - if (dirMeta.isComponent) { - dirMeta.entryComponents.forEach((entryComponentType) => { - const moduleMeta = moduleByJitDirective.get(entryComponentType.componentType)!; - templates.add( - this._createCompiledHostTemplate(entryComponentType.componentType, moduleMeta)); - }); - } - }); - localModuleMeta.entryComponents.forEach((entryComponentType) => { - if (!this.hasAotSummary(entryComponentType.componentType)) { - const moduleMeta = moduleByJitDirective.get(entryComponentType.componentType)!; - templates.add( - this._createCompiledHostTemplate(entryComponentType.componentType, moduleMeta)); - } - }); - }); - templates.forEach((template) => this._compileTemplate(template)); - } - - clearCacheFor(type: Type) { - this._compiledNgModuleCache.delete(type); - this._metadataResolver.clearCacheFor(type); - this._compiledHostTemplateCache.delete(type); - const compiledTemplate = this._compiledTemplateCache.get(type); - if (compiledTemplate) { - this._compiledTemplateCache.delete(type); - } - } - - clearCache(): void { - // Note: don't clear the _addedAotSummaries, as they don't change! - this._metadataResolver.clearCache(); - this._compiledTemplateCache.clear(); - this._compiledHostTemplateCache.clear(); - this._compiledNgModuleCache.clear(); - } - - private _createCompiledHostTemplate(compType: Type, ngModule: CompileNgModuleMetadata): - CompiledTemplate { - if (!ngModule) { - throw new Error(`Component ${ - stringify( - compType)} is not part of any NgModule or the module has not been imported into your module.`); - } - let compiledTemplate = this._compiledHostTemplateCache.get(compType); - if (!compiledTemplate) { - const compMeta = this._metadataResolver.getDirectiveMetadata(compType); - assertComponent(compMeta); - - const hostMeta = this._metadataResolver.getHostComponentMetadata( - compMeta, (compMeta.componentFactory as any).viewDefFactory); - compiledTemplate = - new CompiledTemplate(true, compMeta.type, hostMeta, ngModule, [compMeta.type]); - this._compiledHostTemplateCache.set(compType, compiledTemplate); - } - return compiledTemplate; - } - - private _createCompiledTemplate( - compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata): CompiledTemplate { - let compiledTemplate = this._compiledTemplateCache.get(compMeta.type.reference); - if (!compiledTemplate) { - assertComponent(compMeta); - compiledTemplate = new CompiledTemplate( - false, compMeta.type, compMeta, ngModule, ngModule.transitiveModule.directives); - this._compiledTemplateCache.set(compMeta.type.reference, compiledTemplate); - } - return compiledTemplate; - } - - private _compileTemplate(template: CompiledTemplate) { - if (template.isCompiled) { - return; - } - const compMeta = template.compMeta; - const externalStylesheetsByModuleUrl = new Map(); - const outputContext = createOutputContext(); - const componentStylesheet = this._styleCompiler.compileComponent(outputContext, compMeta); - compMeta.template !.externalStylesheets.forEach((stylesheetMeta) => { - const compiledStylesheet = - this._styleCompiler.compileStyles(createOutputContext(), compMeta, stylesheetMeta); - externalStylesheetsByModuleUrl.set(stylesheetMeta.moduleUrl!, compiledStylesheet); - }); - this._resolveStylesCompileResult(componentStylesheet, externalStylesheetsByModuleUrl); - const pipes = template.ngModule.transitiveModule.pipes.map( - pipe => this._metadataResolver.getPipeSummary(pipe.reference)); - const {template: parsedTemplate, pipes: usedPipes} = - this._parseTemplate(compMeta, template.ngModule, template.directives); - const compileResult = this._viewCompiler.compileComponent( - outputContext, compMeta, parsedTemplate, ir.variable(componentStylesheet.stylesVar), - usedPipes); - const evalResult = this._interpretOrJit( - templateJitUrl(template.ngModule.type, template.compMeta), outputContext.statements); - const viewClass = evalResult[compileResult.viewClassVar]; - const rendererType = evalResult[compileResult.rendererTypeVar]; - template.compiled(viewClass, rendererType); - } - - private _parseTemplate( - compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, - directiveIdentifiers: CompileIdentifierMetadata[]): - {template: TemplateAst[], pipes: CompilePipeSummary[]} { - // Note: ! is ok here as components always have a template. - const preserveWhitespaces = compMeta.template !.preserveWhitespaces; - const directives = - directiveIdentifiers.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference)); - const pipes = ngModule.transitiveModule.pipes.map( - pipe => this._metadataResolver.getPipeSummary(pipe.reference)); - return this._templateParser.parse( - compMeta, compMeta.template !.htmlAst!, directives, pipes, ngModule.schemas, - templateSourceUrl(ngModule.type, compMeta, compMeta.template !), preserveWhitespaces); - } - - private _resolveStylesCompileResult( - result: CompiledStylesheet, externalStylesheetsByModuleUrl: Map) { - result.dependencies.forEach((dep, i) => { - const nestedCompileResult = externalStylesheetsByModuleUrl.get(dep.moduleUrl)!; - const nestedStylesArr = this._resolveAndEvalStylesCompileResult( - nestedCompileResult, externalStylesheetsByModuleUrl); - dep.setValue(nestedStylesArr); - }); - } - - private _resolveAndEvalStylesCompileResult( - result: CompiledStylesheet, - externalStylesheetsByModuleUrl: Map): string[] { - this._resolveStylesCompileResult(result, externalStylesheetsByModuleUrl); - return this._interpretOrJit( - sharedStylesheetJitUrl(result.meta, this._sharedStylesheetCount++), - result.outputCtx.statements)[result.stylesVar]; - } - - private _interpretOrJit(sourceUrl: string, statements: ir.Statement[]): any { - if (!this._compilerConfig.useJit) { - return interpretStatements(statements, this._reflector); - } else { - return this._jitEvaluator.evaluateStatements( - sourceUrl, statements, this._reflector, this._compilerConfig.jitDevMode); - } - } -} - -class CompiledTemplate { - private _viewClass: Function = null!; - isCompiled = false; - - constructor( - public isHost: boolean, public compType: CompileIdentifierMetadata, - public compMeta: CompileDirectiveMetadata, public ngModule: CompileNgModuleMetadata, - public directives: CompileIdentifierMetadata[]) {} - - compiled(viewClass: Function, rendererType: any) { - this._viewClass = viewClass; - (this.compMeta.componentViewType).setDelegate(viewClass); - for (let prop in rendererType) { - (this.compMeta.rendererType)[prop] = rendererType[prop]; - } - this.isCompiled = true; - } -} - -function assertComponent(meta: CompileDirectiveMetadata) { - if (!meta.isComponent) { - throw new Error( - `Could not compile '${identifierName(meta.type)}' because it is not a component.`); - } -} - -function createOutputContext(): OutputContext { - const importExpr = (symbol: any) => - ir.importExpr({name: identifierName(symbol), moduleName: null, runtime: symbol}); - return {statements: [], genFilePath: '', importExpr, constantPool: new ConstantPool()}; -} diff --git a/packages/compiler/src/lifecycle_reflector.ts b/packages/compiler/src/lifecycle_reflector.ts deleted file mode 100644 index 90ff55772d7..00000000000 --- a/packages/compiler/src/lifecycle_reflector.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {CompileReflector} from './compile_reflector'; - -export enum LifecycleHooks { - OnInit, - OnDestroy, - DoCheck, - OnChanges, - AfterContentInit, - AfterContentChecked, - AfterViewInit, - AfterViewChecked -} - -export const LIFECYCLE_HOOKS_VALUES = [ - LifecycleHooks.OnInit, LifecycleHooks.OnDestroy, LifecycleHooks.DoCheck, LifecycleHooks.OnChanges, - LifecycleHooks.AfterContentInit, LifecycleHooks.AfterContentChecked, LifecycleHooks.AfterViewInit, - LifecycleHooks.AfterViewChecked -]; - -export function hasLifecycleHook( - reflector: CompileReflector, hook: LifecycleHooks, token: any): boolean { - return reflector.hasLifecycleHook(token, getHookName(hook)); -} - -export function getAllLifecycleHooks(reflector: CompileReflector, token: any): LifecycleHooks[] { - return LIFECYCLE_HOOKS_VALUES.filter(hook => hasLifecycleHook(reflector, hook, token)); -} - -function getHookName(hook: LifecycleHooks): string { - switch (hook) { - case LifecycleHooks.OnInit: - return 'ngOnInit'; - case LifecycleHooks.OnDestroy: - return 'ngOnDestroy'; - case LifecycleHooks.DoCheck: - return 'ngDoCheck'; - case LifecycleHooks.OnChanges: - return 'ngOnChanges'; - case LifecycleHooks.AfterContentInit: - return 'ngAfterContentInit'; - case LifecycleHooks.AfterContentChecked: - return 'ngAfterContentChecked'; - case LifecycleHooks.AfterViewInit: - return 'ngAfterViewInit'; - case LifecycleHooks.AfterViewChecked: - return 'ngAfterViewChecked'; - default: - // This default case is not needed by TypeScript compiler, as the switch is exhaustive. - // However Closure Compiler does not understand that and reports an error in typed mode. - // The `throw new Error` below works around the problem, and the unexpected: never variable - // makes sure tsc still checks this code is unreachable. - const unexpected: never = hook; - throw new Error(`unexpected ${unexpected}`); - } -} diff --git a/packages/compiler/src/metadata_resolver.ts b/packages/compiler/src/metadata_resolver.ts deleted file mode 100644 index 5127191c4a7..00000000000 --- a/packages/compiler/src/metadata_resolver.ts +++ /dev/null @@ -1,1293 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {StaticSymbol, StaticSymbolCache} from './aot/static_symbol'; -import {ngfactoryFilePath} from './aot/util'; -import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions'; -import * as cpl from './compile_metadata'; -import {CompileReflector} from './compile_reflector'; -import {CompilerConfig} from './config'; -import {ChangeDetectionStrategy, Component, createAttribute, createComponent, createHost, createInject, createInjectable, createInjectionToken, createNgModule, createOptional, createSelf, createSkipSelf, Directive, Injectable, ModuleWithProviders, Provider, Query, SchemaMetadata, Type, ViewEncapsulation} from './core'; -import {DirectiveNormalizer} from './directive_normalizer'; -import {DirectiveResolver, findLast} from './directive_resolver'; -import {Identifiers} from './identifiers'; -import {getAllLifecycleHooks} from './lifecycle_reflector'; -import {HtmlParser} from './ml_parser/html_parser'; -import {NgModuleResolver} from './ng_module_resolver'; -import {CompileIdentifierMetadata, identifierName, syntaxError} from './parse_util'; -import {PipeResolver} from './pipe_resolver'; -import {ElementSchemaRegistry} from './schema/element_schema_registry'; -import {CssSelector} from './selector'; -import {SummaryResolver} from './summary_resolver'; -import {Console, isPromise, noUndefined, resolveForwardRef, stringify, SyncAsync, ValueTransformer, visitValue} from './util'; - -export type ErrorCollector = (error: any, type?: any) => void; - -export const ERROR_COMPONENT_TYPE = 'ngComponentType'; - -const MISSING_NG_MODULE_METADATA_ERROR_DATA = 'ngMissingNgModuleMetadataErrorData'; -export interface MissingNgModuleMetadataErrorData { - fileName: string; - className: string; -} - - -export function getMissingNgModuleMetadataErrorData(error: any): MissingNgModuleMetadataErrorData| - null { - return error[MISSING_NG_MODULE_METADATA_ERROR_DATA] ?? null; -} - -// Design notes: -// - don't lazily create metadata: -// For some metadata, we need to do async work sometimes, -// so the user has to kick off this loading. -// But we want to report errors even when the async work is -// not required to check that the user would have been able -// to wait correctly. -export class CompileMetadataResolver { - private _nonNormalizedDirectiveCache = - new Map(); - private _directiveCache = new Map(); - private _summaryCache = new Map(); - private _pipeCache = new Map(); - private _ngModuleCache = new Map(); - private _ngModuleOfTypes = new Map(); - private _shallowModuleCache = new Map(); - - constructor( - private _config: CompilerConfig, private _htmlParser: HtmlParser, - private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver, - private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver, - private _schemaRegistry: ElementSchemaRegistry, - private _directiveNormalizer: DirectiveNormalizer, private _console: Console, - private _staticSymbolCache: StaticSymbolCache, private _reflector: CompileReflector, - private _errorCollector?: ErrorCollector) {} - - getReflector(): CompileReflector { - return this._reflector; - } - - clearCacheFor(type: Type) { - const dirMeta = this._directiveCache.get(type); - this._directiveCache.delete(type); - this._nonNormalizedDirectiveCache.delete(type); - this._summaryCache.delete(type); - this._pipeCache.delete(type); - this._ngModuleOfTypes.delete(type); - // Clear all of the NgModule as they contain transitive information! - this._ngModuleCache.clear(); - if (dirMeta) { - this._directiveNormalizer.clearCacheFor(dirMeta); - } - } - - clearCache(): void { - this._directiveCache.clear(); - this._nonNormalizedDirectiveCache.clear(); - this._summaryCache.clear(); - this._pipeCache.clear(); - this._ngModuleCache.clear(); - this._ngModuleOfTypes.clear(); - this._directiveNormalizer.clearCache(); - } - - private _createProxyClass(baseType: any, name: string): cpl.ProxyClass { - let delegate: any = null; - const proxyClass: cpl.ProxyClass = function(this: unknown) { - if (!delegate) { - throw new Error( - `Illegal state: Class ${name} for type ${stringify(baseType)} is not compiled yet!`); - } - return delegate.apply(this, arguments); - }; - proxyClass.setDelegate = (d) => { - delegate = d; - (proxyClass).prototype = d.prototype; - }; - // Make stringify work correctly - (proxyClass).overriddenName = name; - return proxyClass; - } - - private getGeneratedClass(dirType: any, name: string): StaticSymbol|cpl.ProxyClass { - if (dirType instanceof StaticSymbol) { - return this._staticSymbolCache.get(ngfactoryFilePath(dirType.filePath), name); - } else { - return this._createProxyClass(dirType, name); - } - } - - private getComponentViewClass(dirType: any): StaticSymbol|cpl.ProxyClass { - return this.getGeneratedClass(dirType, cpl.viewClassName(dirType, 0)); - } - - getHostComponentViewClass(dirType: any): StaticSymbol|cpl.ProxyClass { - return this.getGeneratedClass(dirType, cpl.hostViewClassName(dirType)); - } - - getHostComponentType(dirType: any): StaticSymbol|cpl.ProxyClass { - const name = `${identifierName({reference: dirType})}_Host`; - if (dirType instanceof StaticSymbol) { - return this._staticSymbolCache.get(dirType.filePath, name); - } - - return this._createProxyClass(dirType, name); - } - - private getRendererType(dirType: any): StaticSymbol|object { - if (dirType instanceof StaticSymbol) { - return this._staticSymbolCache.get( - ngfactoryFilePath(dirType.filePath), cpl.rendererTypeName(dirType)); - } else { - // returning an object as proxy, - // that we fill later during runtime compilation. - return {}; - } - } - - private getComponentFactory( - selector: string, dirType: any, inputs: {[key: string]: string}|null, - outputs: {[key: string]: string}): StaticSymbol|object { - if (dirType instanceof StaticSymbol) { - return this._staticSymbolCache.get( - ngfactoryFilePath(dirType.filePath), cpl.componentFactoryName(dirType)); - } else { - const hostView = this.getHostComponentViewClass(dirType); - // Note: ngContentSelectors will be filled later once the template is - // loaded. - const createComponentFactory = - this._reflector.resolveExternalReference(Identifiers.createComponentFactory); - return createComponentFactory(selector, dirType, hostView, inputs, outputs, []); - } - } - - private initComponentFactory(factory: StaticSymbol|object, ngContentSelectors: string[]) { - if (!(factory instanceof StaticSymbol)) { - (factory as any).ngContentSelectors.push(...ngContentSelectors); - } - } - - private _loadSummary(type: any, kind: cpl.CompileSummaryKind): cpl.CompileTypeSummary|null { - let typeSummary = this._summaryCache.get(type); - if (!typeSummary) { - const summary = this._summaryResolver.resolveSummary(type); - typeSummary = summary ? summary.type : null; - this._summaryCache.set(type, typeSummary || null); - } - return typeSummary && typeSummary.summaryKind === kind ? typeSummary : null; - } - - getHostComponentMetadata( - compMeta: cpl.CompileDirectiveMetadata, - hostViewType?: StaticSymbol|cpl.ProxyClass): cpl.CompileDirectiveMetadata { - const hostType = this.getHostComponentType(compMeta.type.reference); - if (!hostViewType) { - hostViewType = this.getHostComponentViewClass(hostType); - } - // Note: ! is ok here as this method should only be called with normalized directive - // metadata, which always fills in the selector. - const template = CssSelector.parse(compMeta.selector!)[0].getMatchingElementTemplate(); - const templateUrl = ''; - const htmlAst = this._htmlParser.parse(template, templateUrl); - return cpl.CompileDirectiveMetadata.create({ - isHost: true, - type: {reference: hostType, diDeps: [], lifecycleHooks: []}, - template: new cpl.CompileTemplateMetadata({ - encapsulation: ViewEncapsulation.None, - template, - templateUrl, - htmlAst, - styles: [], - styleUrls: [], - ngContentSelectors: [], - animations: [], - isInline: true, - externalStylesheets: [], - interpolation: null, - preserveWhitespaces: false, - }), - exportAs: null, - changeDetection: ChangeDetectionStrategy.Default, - inputs: [], - outputs: [], - host: {}, - isComponent: true, - selector: '*', - providers: [], - viewProviders: [], - queries: [], - guards: {}, - viewQueries: [], - componentViewType: hostViewType, - rendererType: {id: '__Host__', encapsulation: ViewEncapsulation.None, styles: [], data: {}} as - object, - entryComponents: [], - componentFactory: null - }); - } - - loadDirectiveMetadata(ngModuleType: any, directiveType: any, isSync: boolean): SyncAsync { - if (this._directiveCache.has(directiveType)) { - return null; - } - directiveType = resolveForwardRef(directiveType); - const {annotation, metadata} = this.getNonNormalizedDirectiveMetadata(directiveType)!; - - const createDirectiveMetadata = (templateMetadata: cpl.CompileTemplateMetadata|null) => { - const normalizedDirMeta = new cpl.CompileDirectiveMetadata({ - isHost: false, - type: metadata.type, - isComponent: metadata.isComponent, - selector: metadata.selector, - exportAs: metadata.exportAs, - changeDetection: metadata.changeDetection, - inputs: metadata.inputs, - outputs: metadata.outputs, - hostListeners: metadata.hostListeners, - hostProperties: metadata.hostProperties, - hostAttributes: metadata.hostAttributes, - providers: metadata.providers, - viewProviders: metadata.viewProviders, - queries: metadata.queries, - guards: metadata.guards, - viewQueries: metadata.viewQueries, - entryComponents: metadata.entryComponents, - componentViewType: metadata.componentViewType, - rendererType: metadata.rendererType, - componentFactory: metadata.componentFactory, - template: templateMetadata - }); - if (templateMetadata) { - this.initComponentFactory(metadata.componentFactory!, templateMetadata.ngContentSelectors); - } - this._directiveCache.set(directiveType, normalizedDirMeta); - this._summaryCache.set(directiveType, normalizedDirMeta.toSummary()); - return null; - }; - - if (metadata.isComponent) { - const template = metadata.template !; - const templateMeta = this._directiveNormalizer.normalizeTemplate({ - ngModuleType, - componentType: directiveType, - moduleUrl: this._reflector.componentModuleUrl(directiveType, annotation), - encapsulation: template.encapsulation, - template: template.template, - templateUrl: template.templateUrl, - styles: template.styles, - styleUrls: template.styleUrls, - animations: template.animations, - interpolation: template.interpolation, - preserveWhitespaces: template.preserveWhitespaces - }); - if (isPromise(templateMeta) && isSync) { - this._reportError(componentStillLoadingError(directiveType), directiveType); - return null; - } - return SyncAsync.then(templateMeta, createDirectiveMetadata); - } else { - // directive - createDirectiveMetadata(null); - return null; - } - } - - getNonNormalizedDirectiveMetadata(directiveType: any): - {annotation: Directive, metadata: cpl.CompileDirectiveMetadata}|null { - directiveType = resolveForwardRef(directiveType); - if (!directiveType) { - return null; - } - let cacheEntry = this._nonNormalizedDirectiveCache.get(directiveType); - if (cacheEntry) { - return cacheEntry; - } - const dirMeta = this._directiveResolver.resolve(directiveType, false); - if (!dirMeta) { - return null; - } - let nonNormalizedTemplateMetadata: cpl.CompileTemplateMetadata = undefined!; - - if (createComponent.isTypeOf(dirMeta)) { - // component - const compMeta = dirMeta as Component; - assertArrayOfStrings('styles', compMeta.styles); - assertArrayOfStrings('styleUrls', compMeta.styleUrls); - assertInterpolationSymbols('interpolation', compMeta.interpolation); - - const animations = compMeta.animations; - - nonNormalizedTemplateMetadata = new cpl.CompileTemplateMetadata({ - encapsulation: noUndefined(compMeta.encapsulation), - template: noUndefined(compMeta.template), - templateUrl: noUndefined(compMeta.templateUrl), - htmlAst: null, - styles: compMeta.styles || [], - styleUrls: compMeta.styleUrls || [], - animations: animations || [], - interpolation: noUndefined(compMeta.interpolation), - isInline: !!compMeta.template, - externalStylesheets: [], - ngContentSelectors: [], - preserveWhitespaces: noUndefined(dirMeta.preserveWhitespaces), - }); - } - - let changeDetectionStrategy: ChangeDetectionStrategy = null!; - let viewProviders: cpl.CompileProviderMetadata[] = []; - let entryComponentMetadata: cpl.CompileEntryComponentMetadata[] = []; - let selector = dirMeta.selector; - - if (createComponent.isTypeOf(dirMeta)) { - // Component - const compMeta = dirMeta as Component; - changeDetectionStrategy = compMeta.changeDetection!; - if (compMeta.viewProviders) { - viewProviders = this._getProvidersMetadata( - compMeta.viewProviders, entryComponentMetadata, - `viewProviders for "${stringifyType(directiveType)}"`, [], directiveType); - } - if (compMeta.entryComponents) { - entryComponentMetadata = flattenAndDedupeArray(compMeta.entryComponents) - .map((type) => this._getEntryComponentMetadata(type)!) - .concat(entryComponentMetadata); - } - if (!selector) { - selector = this._schemaRegistry.getDefaultComponentElementName(); - } - } else { - // Directive - if (!selector) { - selector = null!; - } - } - - let providers: cpl.CompileProviderMetadata[] = []; - if (dirMeta.providers != null) { - providers = this._getProvidersMetadata( - dirMeta.providers, entryComponentMetadata, - `providers for "${stringifyType(directiveType)}"`, [], directiveType); - } - let queries: cpl.CompileQueryMetadata[] = []; - let viewQueries: cpl.CompileQueryMetadata[] = []; - if (dirMeta.queries != null) { - queries = this._getQueriesMetadata(dirMeta.queries, false, directiveType); - viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType); - } - - const metadata = cpl.CompileDirectiveMetadata.create({ - isHost: false, - selector: selector, - exportAs: noUndefined(dirMeta.exportAs), - isComponent: !!nonNormalizedTemplateMetadata, - type: this._getTypeMetadata(directiveType), - template: nonNormalizedTemplateMetadata, - changeDetection: changeDetectionStrategy, - inputs: dirMeta.inputs || [], - outputs: dirMeta.outputs || [], - host: dirMeta.host || {}, - providers: providers || [], - viewProviders: viewProviders || [], - queries: queries || [], - guards: dirMeta.guards || {}, - viewQueries: viewQueries || [], - entryComponents: entryComponentMetadata, - componentViewType: nonNormalizedTemplateMetadata ? this.getComponentViewClass(directiveType) : - null, - rendererType: nonNormalizedTemplateMetadata ? this.getRendererType(directiveType) : null, - componentFactory: null - }); - if (nonNormalizedTemplateMetadata) { - metadata.componentFactory = - this.getComponentFactory(selector, directiveType, metadata.inputs, metadata.outputs); - } - cacheEntry = {metadata, annotation: dirMeta}; - this._nonNormalizedDirectiveCache.set(directiveType, cacheEntry); - return cacheEntry; - } - - /** - * Gets the metadata for the given directive. - * This assumes `loadNgModuleDirectiveAndPipeMetadata` has been called first. - */ - getDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata { - const dirMeta = this._directiveCache.get(directiveType)!; - if (!dirMeta) { - this._reportError( - syntaxError( - `Illegal state: getDirectiveMetadata can only be called after loadNgModuleDirectiveAndPipeMetadata for a module that declares it. Directive ${ - stringifyType(directiveType)}.`), - directiveType); - } - return dirMeta; - } - - getDirectiveSummary(dirType: any): cpl.CompileDirectiveSummary { - const dirSummary = - this._loadSummary(dirType, cpl.CompileSummaryKind.Directive); - if (!dirSummary) { - this._reportError( - syntaxError( - `Illegal state: Could not load the summary for directive ${stringifyType(dirType)}.`), - dirType); - } - return dirSummary; - } - - isDirective(type: any) { - return !!this._loadSummary(type, cpl.CompileSummaryKind.Directive) || - this._directiveResolver.isDirective(type); - } - - isAbstractDirective(type: any): boolean { - const summary = - this._loadSummary(type, cpl.CompileSummaryKind.Directive) as cpl.CompileDirectiveSummary; - if (summary && !summary.isComponent) { - return !summary.selector; - } - - const meta = this._directiveResolver.resolve(type, false); - if (meta && !createComponent.isTypeOf(meta)) { - return !meta.selector; - } - - return false; - } - - isPipe(type: any) { - return !!this._loadSummary(type, cpl.CompileSummaryKind.Pipe) || - this._pipeResolver.isPipe(type); - } - - isNgModule(type: any) { - return !!this._loadSummary(type, cpl.CompileSummaryKind.NgModule) || - this._ngModuleResolver.isNgModule(type); - } - - getNgModuleSummary(moduleType: any, alreadyCollecting: Set|null = null): - cpl.CompileNgModuleSummary|null { - let moduleSummary: cpl.CompileNgModuleSummary|null = - this._loadSummary(moduleType, cpl.CompileSummaryKind.NgModule); - if (!moduleSummary) { - const moduleMeta = this.getNgModuleMetadata(moduleType, false, alreadyCollecting); - moduleSummary = moduleMeta ? moduleMeta.toSummary() : null; - if (moduleSummary) { - this._summaryCache.set(moduleType, moduleSummary); - } - } - return moduleSummary; - } - - /** - * Loads the declared directives and pipes of an NgModule. - */ - loadNgModuleDirectiveAndPipeMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true): - Promise { - const ngModule = this.getNgModuleMetadata(moduleType, throwIfNotFound); - const loading: Promise[] = []; - if (ngModule) { - ngModule.declaredDirectives.forEach((id) => { - const promise = this.loadDirectiveMetadata(moduleType, id.reference, isSync); - if (promise) { - loading.push(promise); - } - }); - ngModule.declaredPipes.forEach((id) => this._loadPipeMetadata(id.reference)); - } - return Promise.all(loading); - } - - getShallowModuleMetadata(moduleType: any): cpl.CompileShallowModuleMetadata|null { - let compileMeta = this._shallowModuleCache.get(moduleType); - if (compileMeta) { - return compileMeta; - } - - const ngModuleMeta = - findLast(this._reflector.shallowAnnotations(moduleType), createNgModule.isTypeOf); - - compileMeta = { - type: this._getTypeMetadata(moduleType), - rawExports: ngModuleMeta.exports, - rawImports: ngModuleMeta.imports, - rawProviders: ngModuleMeta.providers, - }; - - this._shallowModuleCache.set(moduleType, compileMeta); - return compileMeta; - } - - getNgModuleMetadata( - moduleType: any, throwIfNotFound = true, - alreadyCollecting: Set|null = null): cpl.CompileNgModuleMetadata|null { - moduleType = resolveForwardRef(moduleType); - let compileMeta = this._ngModuleCache.get(moduleType); - if (compileMeta) { - return compileMeta; - } - const meta = this._ngModuleResolver.resolve(moduleType, throwIfNotFound); - if (!meta) { - return null; - } - const declaredDirectives: CompileIdentifierMetadata[] = []; - const exportedNonModuleIdentifiers: CompileIdentifierMetadata[] = []; - const declaredPipes: CompileIdentifierMetadata[] = []; - const importedModules: cpl.CompileNgModuleSummary[] = []; - const exportedModules: cpl.CompileNgModuleSummary[] = []; - const providers: cpl.CompileProviderMetadata[] = []; - const entryComponents: cpl.CompileEntryComponentMetadata[] = []; - const bootstrapComponents: CompileIdentifierMetadata[] = []; - const schemas: SchemaMetadata[] = []; - - if (meta.imports) { - flattenAndDedupeArray(meta.imports).forEach((importedType) => { - let importedModuleType: Type = undefined!; - if (isValidType(importedType)) { - importedModuleType = importedType; - } else if (importedType && importedType.ngModule) { - const moduleWithProviders: ModuleWithProviders = importedType; - importedModuleType = moduleWithProviders.ngModule; - if (moduleWithProviders.providers) { - providers.push(...this._getProvidersMetadata( - moduleWithProviders.providers, entryComponents, - `provider for the NgModule '${stringifyType(importedModuleType)}'`, [], - importedType)); - } - } - - if (importedModuleType) { - if (this._checkSelfImport(moduleType, importedModuleType)) return; - if (!alreadyCollecting) alreadyCollecting = new Set(); - if (alreadyCollecting.has(importedModuleType)) { - this._reportError( - syntaxError(`${this._getTypeDescriptor(importedModuleType)} '${ - stringifyType(importedType)}' is imported recursively by the module '${ - stringifyType(moduleType)}'.`), - moduleType); - return; - } - alreadyCollecting.add(importedModuleType); - const importedModuleSummary = - this.getNgModuleSummary(importedModuleType, alreadyCollecting); - alreadyCollecting.delete(importedModuleType); - if (!importedModuleSummary) { - const err = syntaxError(`Unexpected ${this._getTypeDescriptor(importedType)} '${ - stringifyType(importedType)}' imported by the module '${ - stringifyType(moduleType)}'. Please add a @NgModule annotation.`); - // If possible, record additional context for this error to enable more useful - // diagnostics on the compiler side. - if (importedType instanceof StaticSymbol) { - (err as any)[MISSING_NG_MODULE_METADATA_ERROR_DATA] = { - fileName: importedType.filePath, - className: importedType.name, - } as MissingNgModuleMetadataErrorData; - } - this._reportError(err, moduleType); - return; - } - importedModules.push(importedModuleSummary); - } else { - this._reportError( - syntaxError( - `Unexpected value '${stringifyType(importedType)}' imported by the module '${ - stringifyType(moduleType)}'`), - moduleType); - return; - } - }); - } - - if (meta.exports) { - flattenAndDedupeArray(meta.exports).forEach((exportedType) => { - if (!isValidType(exportedType)) { - this._reportError( - syntaxError( - `Unexpected value '${stringifyType(exportedType)}' exported by the module '${ - stringifyType(moduleType)}'`), - moduleType); - return; - } - if (!alreadyCollecting) alreadyCollecting = new Set(); - if (alreadyCollecting.has(exportedType)) { - this._reportError( - syntaxError(`${this._getTypeDescriptor(exportedType)} '${ - stringify(exportedType)}' is exported recursively by the module '${ - stringifyType(moduleType)}'`), - moduleType); - return; - } - alreadyCollecting.add(exportedType); - const exportedModuleSummary = this.getNgModuleSummary(exportedType, alreadyCollecting); - alreadyCollecting.delete(exportedType); - if (exportedModuleSummary) { - exportedModules.push(exportedModuleSummary); - } else { - exportedNonModuleIdentifiers.push(this._getIdentifierMetadata(exportedType)); - } - }); - } - - // Note: This will be modified later, so we rely on - // getting a new instance every time! - const transitiveModule = this._getTransitiveNgModuleMetadata(importedModules, exportedModules); - if (meta.declarations) { - flattenAndDedupeArray(meta.declarations).forEach((declaredType) => { - if (!isValidType(declaredType)) { - this._reportError( - syntaxError( - `Unexpected value '${stringifyType(declaredType)}' declared by the module '${ - stringifyType(moduleType)}'`), - moduleType); - return; - } - const declaredIdentifier = this._getIdentifierMetadata(declaredType); - if (this.isDirective(declaredType)) { - if (this.isAbstractDirective(declaredType)) { - this._reportError( - syntaxError( - `Directive ${stringifyType(declaredType)} has no selector, please add it!`), - declaredType); - } - transitiveModule.addDirective(declaredIdentifier); - declaredDirectives.push(declaredIdentifier); - this._addTypeToModule(declaredType, moduleType); - } else if (this.isPipe(declaredType)) { - transitiveModule.addPipe(declaredIdentifier); - transitiveModule.pipes.push(declaredIdentifier); - declaredPipes.push(declaredIdentifier); - this._addTypeToModule(declaredType, moduleType); - } else { - this._reportError( - syntaxError(`Unexpected ${this._getTypeDescriptor(declaredType)} '${ - stringifyType(declaredType)}' declared by the module '${ - stringifyType( - moduleType)}'. Please add a @Pipe/@Directive/@Component annotation.`), - moduleType); - return; - } - }); - } - - const exportedDirectives: CompileIdentifierMetadata[] = []; - const exportedPipes: CompileIdentifierMetadata[] = []; - exportedNonModuleIdentifiers.forEach((exportedId) => { - if (transitiveModule.directivesSet.has(exportedId.reference)) { - exportedDirectives.push(exportedId); - transitiveModule.addExportedDirective(exportedId); - } else if (transitiveModule.pipesSet.has(exportedId.reference)) { - exportedPipes.push(exportedId); - transitiveModule.addExportedPipe(exportedId); - } else { - this._reportError( - syntaxError(`Can't export ${this._getTypeDescriptor(exportedId.reference)} ${ - stringifyType(exportedId.reference)} from ${ - stringifyType(moduleType)} as it was neither declared nor imported!`), - moduleType); - return; - } - }); - - // The providers of the module have to go last - // so that they overwrite any other provider we already added. - if (meta.providers) { - providers.push(...this._getProvidersMetadata( - meta.providers, entryComponents, - `provider for the NgModule '${stringifyType(moduleType)}'`, [], moduleType)); - } - - if (meta.entryComponents) { - entryComponents.push(...flattenAndDedupeArray(meta.entryComponents) - .map(type => this._getEntryComponentMetadata(type)!)); - } - - if (meta.bootstrap) { - flattenAndDedupeArray(meta.bootstrap).forEach(type => { - if (!isValidType(type)) { - this._reportError( - syntaxError(`Unexpected value '${ - stringifyType(type)}' used in the bootstrap property of module '${ - stringifyType(moduleType)}'`), - moduleType); - return; - } - bootstrapComponents.push(this._getIdentifierMetadata(type)); - }); - } - - entryComponents.push( - ...bootstrapComponents.map(type => this._getEntryComponentMetadata(type.reference)!)); - - if (meta.schemas) { - schemas.push(...flattenAndDedupeArray(meta.schemas)); - } - - compileMeta = new cpl.CompileNgModuleMetadata({ - type: this._getTypeMetadata(moduleType), - providers, - entryComponents, - bootstrapComponents, - schemas, - declaredDirectives, - exportedDirectives, - declaredPipes, - exportedPipes, - importedModules, - exportedModules, - transitiveModule, - id: meta.id || null, - }); - - entryComponents.forEach((id) => transitiveModule.addEntryComponent(id)); - providers.forEach((provider) => transitiveModule.addProvider(provider, compileMeta!.type)); - transitiveModule.addModule(compileMeta.type); - this._ngModuleCache.set(moduleType, compileMeta); - return compileMeta; - } - - private _checkSelfImport(moduleType: Type, importedModuleType: Type): boolean { - if (moduleType === importedModuleType) { - this._reportError( - syntaxError(`'${stringifyType(moduleType)}' module can't import itself`), moduleType); - return true; - } - return false; - } - - private _getTypeDescriptor(type: Type): string { - if (isValidType(type)) { - if (this.isDirective(type)) { - return 'directive'; - } - - if (this.isPipe(type)) { - return 'pipe'; - } - - if (this.isNgModule(type)) { - return 'module'; - } - } - - if ((type as any).provide) { - return 'provider'; - } - - return 'value'; - } - - - private _addTypeToModule(type: Type, moduleType: Type) { - const oldModule = this._ngModuleOfTypes.get(type); - if (oldModule && oldModule !== moduleType) { - this._reportError( - syntaxError( - `Type ${stringifyType(type)} is part of the declarations of 2 modules: ${ - stringifyType(oldModule)} and ${stringifyType(moduleType)}! ` + - `Please consider moving ${stringifyType(type)} to a higher module that imports ${ - stringifyType(oldModule)} and ${stringifyType(moduleType)}. ` + - `You can also create a new NgModule that exports and includes ${ - stringifyType(type)} then import that NgModule in ${ - stringifyType(oldModule)} and ${stringifyType(moduleType)}.`), - moduleType); - return; - } - this._ngModuleOfTypes.set(type, moduleType); - } - - private _getTransitiveNgModuleMetadata( - importedModules: cpl.CompileNgModuleSummary[], - exportedModules: cpl.CompileNgModuleSummary[]): cpl.TransitiveCompileNgModuleMetadata { - // collect `providers` / `entryComponents` from all imported and all exported modules - const result = new cpl.TransitiveCompileNgModuleMetadata(); - const modulesByToken = new Map>(); - importedModules.concat(exportedModules).forEach((modSummary) => { - modSummary.modules.forEach((mod) => result.addModule(mod)); - modSummary.entryComponents.forEach((comp) => result.addEntryComponent(comp)); - const addedTokens = new Set(); - modSummary.providers.forEach((entry) => { - const tokenRef = cpl.tokenReference(entry.provider.token); - let prevModules = modulesByToken.get(tokenRef); - if (!prevModules) { - prevModules = new Set(); - modulesByToken.set(tokenRef, prevModules); - } - const moduleRef = entry.module.reference; - // Note: the providers of one module may still contain multiple providers - // per token (e.g. for multi providers), and we need to preserve these. - if (addedTokens.has(tokenRef) || !prevModules.has(moduleRef)) { - prevModules.add(moduleRef); - addedTokens.add(tokenRef); - result.addProvider(entry.provider, entry.module); - } - }); - }); - exportedModules.forEach((modSummary) => { - modSummary.exportedDirectives.forEach((id) => result.addExportedDirective(id)); - modSummary.exportedPipes.forEach((id) => result.addExportedPipe(id)); - }); - importedModules.forEach((modSummary) => { - modSummary.exportedDirectives.forEach((id) => result.addDirective(id)); - modSummary.exportedPipes.forEach((id) => result.addPipe(id)); - }); - return result; - } - - private _getIdentifierMetadata(type: Type): CompileIdentifierMetadata { - type = resolveForwardRef(type); - return {reference: type}; - } - - isInjectable(type: any): boolean { - const annotations = this._reflector.tryAnnotations(type); - return annotations.some(ann => createInjectable.isTypeOf(ann)); - } - - getInjectableSummary(type: any): cpl.CompileTypeSummary { - return { - summaryKind: cpl.CompileSummaryKind.Injectable, - type: this._getTypeMetadata(type, null, false) - }; - } - - getInjectableMetadata( - type: any, dependencies: any[]|null = null, - throwOnUnknownDeps: boolean = true): cpl.CompileInjectableMetadata|null { - const typeSummary = this._loadSummary(type, cpl.CompileSummaryKind.Injectable); - const typeMetadata = typeSummary ? - typeSummary.type : - this._getTypeMetadata(type, dependencies, throwOnUnknownDeps); - - const annotations: Injectable[] = - this._reflector.annotations(type).filter(ann => createInjectable.isTypeOf(ann)); - - if (annotations.length === 0) { - return null; - } - - const meta = annotations[annotations.length - 1]; - return { - symbol: type, - type: typeMetadata, - providedIn: meta.providedIn, - useValue: meta.useValue, - useClass: meta.useClass, - useExisting: meta.useExisting, - useFactory: meta.useFactory, - deps: meta.deps, - }; - } - - private _getTypeMetadata(type: Type, dependencies: any[]|null = null, throwOnUnknownDeps = true): - cpl.CompileTypeMetadata { - const identifier = this._getIdentifierMetadata(type); - return { - reference: identifier.reference, - diDeps: this._getDependenciesMetadata(identifier.reference, dependencies, throwOnUnknownDeps), - lifecycleHooks: getAllLifecycleHooks(this._reflector, identifier.reference), - }; - } - - private _getFactoryMetadata(factory: Function, dependencies: any[]|null = null): - cpl.CompileFactoryMetadata { - factory = resolveForwardRef(factory); - return {reference: factory, diDeps: this._getDependenciesMetadata(factory, dependencies)}; - } - - /** - * Gets the metadata for the given pipe. - * This assumes `loadNgModuleDirectiveAndPipeMetadata` has been called first. - */ - getPipeMetadata(pipeType: any): cpl.CompilePipeMetadata|null { - const pipeMeta = this._pipeCache.get(pipeType); - if (!pipeMeta) { - this._reportError( - syntaxError( - `Illegal state: getPipeMetadata can only be called after loadNgModuleDirectiveAndPipeMetadata for a module that declares it. Pipe ${ - stringifyType(pipeType)}.`), - pipeType); - } - return pipeMeta || null; - } - - getPipeSummary(pipeType: any): cpl.CompilePipeSummary { - const pipeSummary = - this._loadSummary(pipeType, cpl.CompileSummaryKind.Pipe); - if (!pipeSummary) { - this._reportError( - syntaxError( - `Illegal state: Could not load the summary for pipe ${stringifyType(pipeType)}.`), - pipeType); - } - return pipeSummary; - } - - getOrLoadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata { - let pipeMeta = this._pipeCache.get(pipeType); - if (!pipeMeta) { - pipeMeta = this._loadPipeMetadata(pipeType); - } - return pipeMeta; - } - - private _loadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata { - pipeType = resolveForwardRef(pipeType); - const pipeAnnotation = this._pipeResolver.resolve(pipeType)!; - - const pipeMeta = new cpl.CompilePipeMetadata({ - type: this._getTypeMetadata(pipeType), - name: pipeAnnotation.name, - pure: !!pipeAnnotation.pure - }); - this._pipeCache.set(pipeType, pipeMeta); - this._summaryCache.set(pipeType, pipeMeta.toSummary()); - return pipeMeta; - } - - private _getDependenciesMetadata( - typeOrFunc: Type|Function, dependencies: any[]|null, - throwOnUnknownDeps = true): cpl.CompileDiDependencyMetadata[] { - let hasUnknownDeps = false; - const params = dependencies || this._reflector.parameters(typeOrFunc) || []; - - const dependenciesMetadata: cpl.CompileDiDependencyMetadata[] = params.map((param) => { - let isAttribute = false; - let isHost = false; - let isSelf = false; - let isSkipSelf = false; - let isOptional = false; - let token: any = null; - if (Array.isArray(param)) { - param.forEach((paramEntry: any) => { - if (createHost.isTypeOf(paramEntry)) { - isHost = true; - } else if (createSelf.isTypeOf(paramEntry)) { - isSelf = true; - } else if (createSkipSelf.isTypeOf(paramEntry)) { - isSkipSelf = true; - } else if (createOptional.isTypeOf(paramEntry)) { - isOptional = true; - } else if (createAttribute.isTypeOf(paramEntry)) { - isAttribute = true; - token = (paramEntry as any).attributeName; - } else if (createInject.isTypeOf(paramEntry)) { - token = (paramEntry as any).token; - } else if ( - createInjectionToken.isTypeOf(paramEntry) || - (paramEntry as any) instanceof StaticSymbol) { - token = paramEntry; - } else if (isValidType(paramEntry) && token == null) { - token = paramEntry; - } - }); - } else { - token = param; - } - if (token == null) { - hasUnknownDeps = true; - return {}; - } - - return { - isAttribute, - isHost, - isSelf, - isSkipSelf, - isOptional, - token: this._getTokenMetadata(token) - }; - }); - - if (hasUnknownDeps) { - const depsTokens = - dependenciesMetadata.map((dep) => dep.token ? stringifyType(dep.token) : '?').join(', '); - const message = - `Can't resolve all parameters for ${stringifyType(typeOrFunc)}: (${depsTokens}).`; - if (throwOnUnknownDeps || this._config.strictInjectionParameters) { - this._reportError(syntaxError(message), typeOrFunc); - } - } - - return dependenciesMetadata; - } - - private _getTokenMetadata(token: any): cpl.CompileTokenMetadata { - token = resolveForwardRef(token); - let compileToken: cpl.CompileTokenMetadata; - if (typeof token === 'string') { - compileToken = {value: token}; - } else { - compileToken = {identifier: {reference: token}}; - } - return compileToken; - } - - private _getProvidersMetadata( - providers: Provider[], targetEntryComponents: cpl.CompileEntryComponentMetadata[], - debugInfo?: string, compileProviders: cpl.CompileProviderMetadata[] = [], - type?: any): cpl.CompileProviderMetadata[] { - providers.forEach((provider: any, providerIdx: number) => { - if (Array.isArray(provider)) { - this._getProvidersMetadata(provider, targetEntryComponents, debugInfo, compileProviders); - } else { - provider = resolveForwardRef(provider); - let providerMeta: cpl.ProviderMeta = undefined!; - if (provider && typeof provider === 'object' && provider.hasOwnProperty('provide')) { - this._validateProvider(provider); - providerMeta = new cpl.ProviderMeta(provider.provide, provider); - } else if (isValidType(provider)) { - providerMeta = new cpl.ProviderMeta(provider, {useClass: provider}); - } else if (provider === void 0) { - this._reportError(syntaxError( - `Encountered undefined provider! Usually this means you have a circular dependencies. This might be caused by using 'barrel' index.ts files.`)); - return; - } else { - const providersInfo = - providers - .reduce( - (soFar: string[], seenProvider: any, seenProviderIdx: number) => { - if (seenProviderIdx < providerIdx) { - soFar.push(`${stringifyType(seenProvider)}`); - } else if (seenProviderIdx == providerIdx) { - soFar.push(`?${stringifyType(seenProvider)}?`); - } else if (seenProviderIdx == providerIdx + 1) { - soFar.push('...'); - } - return soFar; - }, - []) - .join(', '); - this._reportError( - syntaxError(`Invalid ${ - debugInfo ? - debugInfo : - 'provider'} - only instances of Provider and Type are allowed, got: [${ - providersInfo}]`), - type); - return; - } - if (providerMeta.token === - this._reflector.resolveExternalReference(Identifiers.ANALYZE_FOR_ENTRY_COMPONENTS)) { - targetEntryComponents.push(...this._getEntryComponentsFromProvider(providerMeta, type)); - } else { - compileProviders.push(this.getProviderMetadata(providerMeta)); - } - } - }); - return compileProviders; - } - - private _validateProvider(provider: any): void { - if (provider.hasOwnProperty('useClass') && provider.useClass == null) { - this._reportError(syntaxError(`Invalid provider for ${ - stringifyType(provider.provide)}. useClass cannot be ${provider.useClass}. - Usually it happens when: - 1. There's a circular dependency (might be caused by using index.ts (barrel) files). - 2. Class was used before it was declared. Use forwardRef in this case.`)); - } - } - - private _getEntryComponentsFromProvider(provider: cpl.ProviderMeta, type?: any): - cpl.CompileEntryComponentMetadata[] { - const components: cpl.CompileEntryComponentMetadata[] = []; - const collectedIdentifiers: CompileIdentifierMetadata[] = []; - - if (provider.useFactory || provider.useExisting || provider.useClass) { - this._reportError( - syntaxError(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports useValue!`), type); - return []; - } - - if (!provider.multi) { - this._reportError( - syntaxError(`The ANALYZE_FOR_ENTRY_COMPONENTS token only supports 'multi = true'!`), - type); - return []; - } - - extractIdentifiers(provider.useValue, collectedIdentifiers); - collectedIdentifiers.forEach((identifier) => { - const entry = this._getEntryComponentMetadata(identifier.reference, false); - if (entry) { - components.push(entry); - } - }); - return components; - } - - private _getEntryComponentMetadata(dirType: any, throwIfNotFound = true): - cpl.CompileEntryComponentMetadata|null { - const dirMeta = this.getNonNormalizedDirectiveMetadata(dirType); - if (dirMeta && dirMeta.metadata.isComponent) { - return {componentType: dirType, componentFactory: dirMeta.metadata.componentFactory!}; - } - const dirSummary = - this._loadSummary(dirType, cpl.CompileSummaryKind.Directive); - if (dirSummary && dirSummary.isComponent) { - return {componentType: dirType, componentFactory: dirSummary.componentFactory!}; - } - if (throwIfNotFound) { - throw syntaxError(`${dirType.name} cannot be used as an entry component.`); - } - return null; - } - - private _getInjectableTypeMetadata(type: Type, dependencies: any[]|null = null): - cpl.CompileTypeMetadata { - const typeSummary = this._loadSummary(type, cpl.CompileSummaryKind.Injectable); - if (typeSummary) { - return typeSummary.type; - } - return this._getTypeMetadata(type, dependencies); - } - - getProviderMetadata(provider: cpl.ProviderMeta): cpl.CompileProviderMetadata { - let compileDeps: cpl.CompileDiDependencyMetadata[] = undefined!; - let compileTypeMetadata: cpl.CompileTypeMetadata = null!; - let compileFactoryMetadata: cpl.CompileFactoryMetadata = null!; - let token: cpl.CompileTokenMetadata = this._getTokenMetadata(provider.token); - - if (provider.useClass) { - compileTypeMetadata = - this._getInjectableTypeMetadata(provider.useClass, provider.dependencies); - compileDeps = compileTypeMetadata.diDeps; - if (provider.token === provider.useClass) { - // use the compileTypeMetadata as it contains information about lifecycleHooks... - token = {identifier: compileTypeMetadata}; - } - } else if (provider.useFactory) { - compileFactoryMetadata = this._getFactoryMetadata(provider.useFactory, provider.dependencies); - compileDeps = compileFactoryMetadata.diDeps; - } - - return { - token: token, - useClass: compileTypeMetadata, - useValue: provider.useValue, - useFactory: compileFactoryMetadata, - useExisting: provider.useExisting ? this._getTokenMetadata(provider.useExisting) : undefined, - deps: compileDeps, - multi: provider.multi - }; - } - - private _getQueriesMetadata( - queries: {[key: string]: Query}, isViewQuery: boolean, - directiveType: Type): cpl.CompileQueryMetadata[] { - const res: cpl.CompileQueryMetadata[] = []; - - Object.keys(queries).forEach((propertyName: string) => { - const query = queries[propertyName]; - if (query.isViewQuery === isViewQuery) { - res.push(this._getQueryMetadata(query, propertyName, directiveType)); - } - }); - - return res; - } - - private _queryVarBindings(selector: any): string[] { - return selector.split(/\s*,\s*/); - } - - private _getQueryMetadata(q: Query, propertyName: string, typeOrFunc: Type|Function): - cpl.CompileQueryMetadata { - let selectors: cpl.CompileTokenMetadata[]; - if (typeof q.selector === 'string') { - selectors = - this._queryVarBindings(q.selector).map(varName => this._getTokenMetadata(varName)); - } else { - if (!q.selector) { - this._reportError( - syntaxError(`Can't construct a query for the property "${propertyName}" of "${ - stringifyType(typeOrFunc)}" since the query selector wasn't defined.`), - typeOrFunc); - selectors = []; - } else { - selectors = [this._getTokenMetadata(q.selector)]; - } - } - - return { - selectors, - first: q.first, - descendants: q.descendants, - emitDistinctChangesOnly: q.emitDistinctChangesOnly, - propertyName, - read: q.read ? this._getTokenMetadata(q.read) : null!, - static: q.static - }; - } - - private _reportError(error: any, type?: any, otherType?: any) { - if (this._errorCollector) { - this._errorCollector(error, type); - if (otherType) { - this._errorCollector(error, otherType); - } - } else { - throw error; - } - } -} - -function flattenArray(tree: any[], out: Array = []): Array { - if (tree) { - for (let i = 0; i < tree.length; i++) { - const item = resolveForwardRef(tree[i]); - if (Array.isArray(item)) { - flattenArray(item, out); - } else { - out.push(item); - } - } - } - return out; -} - -function dedupeArray(array: any[]): Array { - if (array) { - return Array.from(new Set(array)); - } - return []; -} - -function flattenAndDedupeArray(tree: any[]): Array { - return dedupeArray(flattenArray(tree)); -} - -function isValidType(value: any): boolean { - return (value instanceof StaticSymbol) || (value instanceof Type); -} - -function extractIdentifiers(value: any, targetIdentifiers: CompileIdentifierMetadata[]) { - visitValue(value, new _CompileValueConverter(), targetIdentifiers); -} - -class _CompileValueConverter extends ValueTransformer { - override visitOther(value: any, targetIdentifiers: CompileIdentifierMetadata[]): any { - targetIdentifiers.push({reference: value}); - } -} - -function stringifyType(type: any): string { - if (type instanceof StaticSymbol) { - return `${type.name} in ${type.filePath}`; - } else { - return stringify(type); - } -} - -/** - * Indicates that a component is still being loaded in a synchronous compile. - */ -function componentStillLoadingError(compType: Type) { - const error = - Error(`Can't compile synchronously as ${stringify(compType)} is still being loaded!`); - (error as any)[ERROR_COMPONENT_TYPE] = compType; - return error; -} diff --git a/packages/compiler/src/ng_module_compiler.ts b/packages/compiler/src/ng_module_compiler.ts deleted file mode 100644 index 4359a7170ed..00000000000 --- a/packages/compiler/src/ng_module_compiler.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {CompileNgModuleMetadata, CompileProviderMetadata} from './compile_metadata'; -import {CompileReflector} from './compile_reflector'; -import {OutputContext} from './constant_pool'; -import {NodeFlags} from './core'; -import {Identifiers} from './identifiers'; -import * as o from './output/output_ast'; -import {identifierName, typeSourceSpan} from './parse_util'; -import {NgModuleProviderAnalyzer} from './provider_analyzer'; -import {componentFactoryResolverProviderDef, depDef, providerDef} from './view_compiler/provider_compiler'; - -export class NgModuleCompileResult { - constructor(public ngModuleFactoryVar: string) {} -} - -const LOG_VAR = o.variable('_l'); - -export class NgModuleCompiler { - constructor(private reflector: CompileReflector) {} - compile( - ctx: OutputContext, ngModuleMeta: CompileNgModuleMetadata, - extraProviders: CompileProviderMetadata[]): NgModuleCompileResult { - const sourceSpan = typeSourceSpan('NgModule', ngModuleMeta.type); - const entryComponentFactories = ngModuleMeta.transitiveModule.entryComponents; - const bootstrapComponents = ngModuleMeta.bootstrapComponents; - const providerParser = - new NgModuleProviderAnalyzer(this.reflector, ngModuleMeta, extraProviders, sourceSpan); - const providerDefs = - [componentFactoryResolverProviderDef( - this.reflector, ctx, NodeFlags.None, entryComponentFactories)] - .concat(providerParser.parse().map((provider) => providerDef(ctx, provider))) - .map(({providerExpr, depsExpr, flags, tokenExpr}) => { - return o.importExpr(Identifiers.moduleProviderDef).callFn([ - o.literal(flags), tokenExpr, providerExpr, depsExpr - ]); - }); - - const ngModuleDef = o.importExpr(Identifiers.moduleDef).callFn([o.literalArr(providerDefs)]); - const ngModuleDefFactory = - o.fn([new o.FnParam(LOG_VAR.name!)], [new o.ReturnStatement(ngModuleDef)], o.INFERRED_TYPE); - - const ngModuleFactoryVar = `${identifierName(ngModuleMeta.type)}NgFactory`; - this._createNgModuleFactory( - ctx, ngModuleMeta.type.reference, o.importExpr(Identifiers.createModuleFactory).callFn([ - ctx.importExpr(ngModuleMeta.type.reference), - o.literalArr(bootstrapComponents.map(id => ctx.importExpr(id.reference))), - ngModuleDefFactory - ])); - - if (ngModuleMeta.id) { - const id = typeof ngModuleMeta.id === 'string' ? o.literal(ngModuleMeta.id) : - ctx.importExpr(ngModuleMeta.id); - const registerFactoryStmt = o.importExpr(Identifiers.RegisterModuleFactoryFn) - .callFn([id, o.variable(ngModuleFactoryVar)]) - .toStmt(); - ctx.statements.push(registerFactoryStmt); - } - - return new NgModuleCompileResult(ngModuleFactoryVar); - } - - createStub(ctx: OutputContext, ngModuleReference: any) { - this._createNgModuleFactory(ctx, ngModuleReference, o.NULL_EXPR); - } - - private _createNgModuleFactory(ctx: OutputContext, reference: any, value: o.Expression) { - const ngModuleFactoryVar = `${identifierName({reference: reference})}NgFactory`; - const ngModuleFactoryStmt = - o.variable(ngModuleFactoryVar) - .set(value) - .toDeclStmt( - o.importType( - Identifiers.NgModuleFactory, [o.expressionType(ctx.importExpr(reference))!], - [o.TypeModifier.Const]), - [o.StmtModifier.Final, o.StmtModifier.Exported]); - - ctx.statements.push(ngModuleFactoryStmt); - } -} diff --git a/packages/compiler/src/ng_module_resolver.ts b/packages/compiler/src/ng_module_resolver.ts deleted file mode 100644 index 3952b388112..00000000000 --- a/packages/compiler/src/ng_module_resolver.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {CompileReflector} from './compile_reflector'; -import {createNgModule, NgModule, Type} from './core'; -import {findLast} from './directive_resolver'; -import {stringify} from './util'; - - -/** - * Resolves types to {@link NgModule}. - */ -export class NgModuleResolver { - constructor(private _reflector: CompileReflector) {} - - isNgModule(type: any) { - return this._reflector.annotations(type).some(createNgModule.isTypeOf); - } - - resolve(type: Type, throwIfNotFound = true): NgModule|null { - const ngModuleMeta: NgModule = - findLast(this._reflector.annotations(type), createNgModule.isTypeOf); - - if (ngModuleMeta) { - return ngModuleMeta; - } else { - if (throwIfNotFound) { - throw new Error(`No NgModule metadata found for '${stringify(type)}'.`); - } - return null; - } - } -} diff --git a/packages/compiler/src/output/js_emitter.ts b/packages/compiler/src/output/js_emitter.ts deleted file mode 100644 index b3fdc719c6f..00000000000 --- a/packages/compiler/src/output/js_emitter.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {EmitterVisitorContext, OutputEmitter} from './abstract_emitter'; -import {AbstractJsEmitterVisitor} from './abstract_js_emitter'; -import * as o from './output_ast'; - -export class JavaScriptEmitter implements OutputEmitter { - emitStatements(genFilePath: string, stmts: o.Statement[], preamble: string = ''): string { - const converter = new JsEmitterVisitor(); - const ctx = EmitterVisitorContext.createRoot(); - converter.visitAllStatements(stmts, ctx); - - const preambleLines = preamble ? preamble.split('\n') : []; - converter.importsWithPrefixes.forEach((prefix, importedModuleName) => { - // Note: can't write the real word for import as it screws up system.js auto detection... - preambleLines.push( - `var ${prefix} = req` + - `uire('${importedModuleName}');`); - }); - - const sm = ctx.toSourceMapGenerator(genFilePath, preambleLines.length).toJsComment(); - const lines = [...preambleLines, ctx.toSource(), sm]; - if (sm) { - // always add a newline at the end, as some tools have bugs without it. - lines.push(''); - } - return lines.join('\n'); - } -} - -class JsEmitterVisitor extends AbstractJsEmitterVisitor { - importsWithPrefixes = new Map(); - - override visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any { - const {name, moduleName} = ast.value; - if (moduleName) { - let prefix = this.importsWithPrefixes.get(moduleName); - if (prefix == null) { - prefix = `i${this.importsWithPrefixes.size}`; - this.importsWithPrefixes.set(moduleName, prefix); - } - ctx.print(ast, `${prefix}.`); - } - ctx.print(ast, name!); - return null; - } - override visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): any { - super.visitDeclareVarStmt(stmt, ctx); - if (stmt.hasModifier(o.StmtModifier.Exported)) { - ctx.println(stmt, exportVar(stmt.name)); - } - return null; - } - override visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, ctx: EmitterVisitorContext): any { - super.visitDeclareFunctionStmt(stmt, ctx); - if (stmt.hasModifier(o.StmtModifier.Exported)) { - ctx.println(stmt, exportVar(stmt.name)); - } - return null; - } - override visitDeclareClassStmt(stmt: o.ClassStmt, ctx: EmitterVisitorContext): any { - super.visitDeclareClassStmt(stmt, ctx); - if (stmt.hasModifier(o.StmtModifier.Exported)) { - ctx.println(stmt, exportVar(stmt.name)); - } - return null; - } -} - -function exportVar(varName: string): string { - return `Object.defineProperty(exports, '${varName}', { get: function() { return ${varName}; }});`; -} diff --git a/packages/compiler/src/output/output_interpreter.ts b/packages/compiler/src/output/output_interpreter.ts deleted file mode 100644 index d8fc45c5415..00000000000 --- a/packages/compiler/src/output/output_interpreter.ts +++ /dev/null @@ -1,368 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {CompileReflector} from '../compile_reflector'; -import * as o from './output_ast'; -import {debugOutputAstAsTypeScript} from './ts_emitter'; - -export function interpretStatements( - statements: o.Statement[], reflector: CompileReflector): {[key: string]: any} { - const ctx = new _ExecutionContext(null, null, null, new Map()); - const visitor = new StatementInterpreter(reflector); - visitor.visitAllStatements(statements, ctx); - const result: {[key: string]: any} = {}; - ctx.exports.forEach((exportName) => { - result[exportName] = ctx.vars.get(exportName); - }); - return result; -} - -function _executeFunctionStatements( - varNames: string[], varValues: any[], statements: o.Statement[], ctx: _ExecutionContext, - visitor: StatementInterpreter): any { - const childCtx = ctx.createChildWihtLocalVars(); - for (let i = 0; i < varNames.length; i++) { - childCtx.vars.set(varNames[i], varValues[i]); - } - const result = visitor.visitAllStatements(statements, childCtx); - return result ? result.value : null; -} - -class _ExecutionContext { - exports: string[] = []; - - constructor( - public parent: _ExecutionContext|null, public instance: Object|null, - public className: string|null, public vars: Map) {} - - createChildWihtLocalVars(): _ExecutionContext { - return new _ExecutionContext(this, this.instance, this.className, new Map()); - } -} - -class ReturnValue { - constructor(public value: any) {} -} - -function createDynamicClass( - _classStmt: o.ClassStmt, _ctx: _ExecutionContext, _visitor: StatementInterpreter): Function { - const propertyDescriptors: {[key: string]: any} = {}; - - _classStmt.getters.forEach((getter: o.ClassGetter) => { - // Note: use `function` instead of arrow function to capture `this` - propertyDescriptors[getter.name] = { - configurable: false, - get: function() { - const instanceCtx = new _ExecutionContext(_ctx, this, _classStmt.name, _ctx.vars); - return _executeFunctionStatements([], [], getter.body, instanceCtx, _visitor); - } - }; - }); - _classStmt.methods.forEach(function(method: o.ClassMethod) { - const paramNames = method.params.map(param => param.name); - // Note: use `function` instead of arrow function to capture `this` - propertyDescriptors[method.name!] = { - writable: false, - configurable: false, - value: function(...args: any[]) { - const instanceCtx = new _ExecutionContext(_ctx, this, _classStmt.name, _ctx.vars); - return _executeFunctionStatements(paramNames, args, method.body, instanceCtx, _visitor); - } - }; - }); - - const ctorParamNames = _classStmt.constructorMethod.params.map(param => param.name); - // Note: use `function` instead of arrow function to capture `this` - const ctor = function(this: Object, ...args: any[]) { - const instanceCtx = new _ExecutionContext(_ctx, this, _classStmt.name, _ctx.vars); - _classStmt.fields.forEach((field) => { - (this as any)[field.name] = undefined; - }); - _executeFunctionStatements( - ctorParamNames, args, _classStmt.constructorMethod.body, instanceCtx, _visitor); - }; - const superClass = _classStmt.parent ? _classStmt.parent.visitExpression(_visitor, _ctx) : Object; - ctor.prototype = Object.create(superClass.prototype, propertyDescriptors); - return ctor; -} - -class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor { - constructor(private reflector: CompileReflector) {} - debugAst(ast: o.Expression|o.Statement|o.Type): string { - return debugOutputAstAsTypeScript(ast); - } - - visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: _ExecutionContext): any { - const initialValue = stmt.value ? stmt.value.visitExpression(this, ctx) : undefined; - ctx.vars.set(stmt.name, initialValue); - if (stmt.hasModifier(o.StmtModifier.Exported)) { - ctx.exports.push(stmt.name); - } - return null; - } - visitWriteVarExpr(expr: o.WriteVarExpr, ctx: _ExecutionContext): any { - const value = expr.value.visitExpression(this, ctx); - let currCtx = ctx; - while (currCtx != null) { - if (currCtx.vars.has(expr.name)) { - currCtx.vars.set(expr.name, value); - return value; - } - currCtx = currCtx.parent!; - } - throw new Error(`Not declared variable ${expr.name}`); - } - visitWrappedNodeExpr(ast: o.WrappedNodeExpr, ctx: _ExecutionContext): never { - throw new Error('Cannot interpret a WrappedNodeExpr.'); - } - visitTypeofExpr(ast: o.TypeofExpr, ctx: _ExecutionContext): never { - throw new Error('Cannot interpret a TypeofExpr'); - } - visitReadVarExpr(ast: o.ReadVarExpr, ctx: _ExecutionContext): any { - let varName = ast.name!; - if (ast.builtin != null) { - switch (ast.builtin) { - case o.BuiltinVar.Super: - return Object.getPrototypeOf(ctx.instance); - case o.BuiltinVar.This: - return ctx.instance; - case o.BuiltinVar.CatchError: - varName = CATCH_ERROR_VAR; - break; - case o.BuiltinVar.CatchStack: - varName = CATCH_STACK_VAR; - break; - default: - throw new Error(`Unknown builtin variable ${ast.builtin}`); - } - } - let currCtx = ctx; - while (currCtx != null) { - if (currCtx.vars.has(varName)) { - return currCtx.vars.get(varName); - } - currCtx = currCtx.parent!; - } - throw new Error(`Not declared variable ${varName}`); - } - visitWriteKeyExpr(expr: o.WriteKeyExpr, ctx: _ExecutionContext): any { - const receiver = expr.receiver.visitExpression(this, ctx); - const index = expr.index.visitExpression(this, ctx); - const value = expr.value.visitExpression(this, ctx); - receiver[index] = value; - return value; - } - visitWritePropExpr(expr: o.WritePropExpr, ctx: _ExecutionContext): any { - const receiver = expr.receiver.visitExpression(this, ctx); - const value = expr.value.visitExpression(this, ctx); - receiver[expr.name] = value; - return value; - } - - visitInvokeFunctionExpr(stmt: o.InvokeFunctionExpr, ctx: _ExecutionContext): any { - const args = this.visitAllExpressions(stmt.args, ctx); - const fnExpr = stmt.fn as (o.Expression & {receiver: o.Expression | undefined}); - if (fnExpr instanceof o.ReadVarExpr && fnExpr.builtin === o.BuiltinVar.Super) { - ctx.instance!.constructor.prototype.constructor.apply(ctx.instance, args); - return null; - } else { - const fn = fnExpr.visitExpression(this, ctx); - let context: any = null; - if (fnExpr.receiver) { - context = fnExpr.receiver.visitExpression(this, ctx); - } - return fn.apply(context, args); - } - } - visitTaggedTemplateExpr(expr: o.TaggedTemplateExpr, ctx: _ExecutionContext): any { - const templateElements = expr.template.elements.map((e) => e.text); - Object.defineProperty( - templateElements, 'raw', {value: expr.template.elements.map((e) => e.rawText)}); - const args = this.visitAllExpressions(expr.template.expressions, ctx); - args.unshift(templateElements); - const tag = expr.tag.visitExpression(this, ctx); - return tag.apply(null, args); - } - visitReturnStmt(stmt: o.ReturnStatement, ctx: _ExecutionContext): any { - return new ReturnValue(stmt.value.visitExpression(this, ctx)); - } - visitDeclareClassStmt(stmt: o.ClassStmt, ctx: _ExecutionContext): any { - const clazz = createDynamicClass(stmt, ctx, this); - ctx.vars.set(stmt.name, clazz); - if (stmt.hasModifier(o.StmtModifier.Exported)) { - ctx.exports.push(stmt.name); - } - return null; - } - visitExpressionStmt(stmt: o.ExpressionStatement, ctx: _ExecutionContext): any { - return stmt.expr.visitExpression(this, ctx); - } - visitIfStmt(stmt: o.IfStmt, ctx: _ExecutionContext): any { - const condition = stmt.condition.visitExpression(this, ctx); - if (condition) { - return this.visitAllStatements(stmt.trueCase, ctx); - } else if (stmt.falseCase != null) { - return this.visitAllStatements(stmt.falseCase, ctx); - } - return null; - } - visitTryCatchStmt(stmt: o.TryCatchStmt, ctx: _ExecutionContext): any { - try { - return this.visitAllStatements(stmt.bodyStmts, ctx); - } catch (e) { - const childCtx = ctx.createChildWihtLocalVars(); - childCtx.vars.set(CATCH_ERROR_VAR, e); - childCtx.vars.set(CATCH_STACK_VAR, e.stack); - return this.visitAllStatements(stmt.catchStmts, childCtx); - } - } - visitThrowStmt(stmt: o.ThrowStmt, ctx: _ExecutionContext): any { - throw stmt.error.visitExpression(this, ctx); - } - visitInstantiateExpr(ast: o.InstantiateExpr, ctx: _ExecutionContext): any { - const args = this.visitAllExpressions(ast.args, ctx); - const clazz = ast.classExpr.visitExpression(this, ctx); - return new clazz(...args); - } - visitLiteralExpr(ast: o.LiteralExpr, ctx: _ExecutionContext): any { - return ast.value; - } - visitLocalizedString(ast: o.LocalizedString, context: any): any { - return null; - } - visitExternalExpr(ast: o.ExternalExpr, ctx: _ExecutionContext): any { - return this.reflector.resolveExternalReference(ast.value); - } - visitConditionalExpr(ast: o.ConditionalExpr, ctx: _ExecutionContext): any { - if (ast.condition.visitExpression(this, ctx)) { - return ast.trueCase.visitExpression(this, ctx); - } else if (ast.falseCase != null) { - return ast.falseCase.visitExpression(this, ctx); - } - return null; - } - visitNotExpr(ast: o.NotExpr, ctx: _ExecutionContext): any { - return !ast.condition.visitExpression(this, ctx); - } - visitAssertNotNullExpr(ast: o.AssertNotNull, ctx: _ExecutionContext): any { - return ast.condition.visitExpression(this, ctx); - } - visitCastExpr(ast: o.CastExpr, ctx: _ExecutionContext): any { - return ast.value.visitExpression(this, ctx); - } - visitFunctionExpr(ast: o.FunctionExpr, ctx: _ExecutionContext): any { - const paramNames = ast.params.map((param) => param.name); - return _declareFn(paramNames, ast.statements, ctx, this); - } - visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, ctx: _ExecutionContext): any { - const paramNames = stmt.params.map((param) => param.name); - ctx.vars.set(stmt.name, _declareFn(paramNames, stmt.statements, ctx, this)); - if (stmt.hasModifier(o.StmtModifier.Exported)) { - ctx.exports.push(stmt.name); - } - return null; - } - visitUnaryOperatorExpr(ast: o.UnaryOperatorExpr, ctx: _ExecutionContext): any { - const rhs = () => ast.expr.visitExpression(this, ctx); - - switch (ast.operator) { - case o.UnaryOperator.Plus: - return +rhs(); - case o.UnaryOperator.Minus: - return -rhs(); - default: - throw new Error(`Unknown operator ${ast.operator}`); - } - } - visitBinaryOperatorExpr(ast: o.BinaryOperatorExpr, ctx: _ExecutionContext): any { - const lhs = () => ast.lhs.visitExpression(this, ctx); - const rhs = () => ast.rhs.visitExpression(this, ctx); - - switch (ast.operator) { - case o.BinaryOperator.Equals: - return lhs() == rhs(); - case o.BinaryOperator.Identical: - return lhs() === rhs(); - case o.BinaryOperator.NotEquals: - return lhs() != rhs(); - case o.BinaryOperator.NotIdentical: - return lhs() !== rhs(); - case o.BinaryOperator.And: - return lhs() && rhs(); - case o.BinaryOperator.Or: - return lhs() || rhs(); - case o.BinaryOperator.Plus: - return lhs() + rhs(); - case o.BinaryOperator.Minus: - return lhs() - rhs(); - case o.BinaryOperator.Divide: - return lhs() / rhs(); - case o.BinaryOperator.Multiply: - return lhs() * rhs(); - case o.BinaryOperator.Modulo: - return lhs() % rhs(); - case o.BinaryOperator.Lower: - return lhs() < rhs(); - case o.BinaryOperator.LowerEquals: - return lhs() <= rhs(); - case o.BinaryOperator.Bigger: - return lhs() > rhs(); - case o.BinaryOperator.BiggerEquals: - return lhs() >= rhs(); - case o.BinaryOperator.NullishCoalesce: - return lhs() ?? rhs(); - default: - throw new Error(`Unknown operator ${ast.operator}`); - } - } - visitReadPropExpr(ast: o.ReadPropExpr, ctx: _ExecutionContext): any { - let result: any; - const receiver = ast.receiver.visitExpression(this, ctx); - result = receiver[ast.name]; - return result; - } - visitReadKeyExpr(ast: o.ReadKeyExpr, ctx: _ExecutionContext): any { - const receiver = ast.receiver.visitExpression(this, ctx); - const prop = ast.index.visitExpression(this, ctx); - return receiver[prop]; - } - visitLiteralArrayExpr(ast: o.LiteralArrayExpr, ctx: _ExecutionContext): any { - return this.visitAllExpressions(ast.entries, ctx); - } - visitLiteralMapExpr(ast: o.LiteralMapExpr, ctx: _ExecutionContext): any { - const result: {[k: string]: any} = {}; - ast.entries.forEach(entry => result[entry.key] = entry.value.visitExpression(this, ctx)); - return result; - } - visitCommaExpr(ast: o.CommaExpr, context: any): any { - const values = this.visitAllExpressions(ast.parts, context); - return values[values.length - 1]; - } - visitAllExpressions(expressions: o.Expression[], ctx: _ExecutionContext): any { - return expressions.map((expr) => expr.visitExpression(this, ctx)); - } - - visitAllStatements(statements: o.Statement[], ctx: _ExecutionContext): ReturnValue|null { - for (let i = 0; i < statements.length; i++) { - const stmt = statements[i]; - const val = stmt.visitStatement(this, ctx); - if (val instanceof ReturnValue) { - return val; - } - } - return null; - } -} - -function _declareFn( - varNames: string[], statements: o.Statement[], ctx: _ExecutionContext, - visitor: StatementInterpreter): Function { - return (...args: any[]) => _executeFunctionStatements(varNames, args, statements, ctx, visitor); -} - -const CATCH_ERROR_VAR = 'error'; -const CATCH_STACK_VAR = 'stack'; diff --git a/packages/compiler/src/output/output_jit.ts b/packages/compiler/src/output/output_jit.ts index 5d5f3890aac..ab1d70e18a2 100644 --- a/packages/compiler/src/output/output_jit.ts +++ b/packages/compiler/src/output/output_jit.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {CompileReflector} from '../compile_reflector'; import {identifierName} from '../parse_util'; import {EmitterVisitorContext} from './abstract_emitter'; @@ -14,6 +13,10 @@ import {AbstractJsEmitterVisitor} from './abstract_js_emitter'; import * as o from './output_ast'; import {newTrustedFunctionForJIT} from './output_jit_trusted_types'; +export interface ExternalReferenceResolver { + resolveExternalReference(ref: o.ExternalReference): unknown; +} + /** * A helper class to manage the evaluation of JIT generated code. */ @@ -22,15 +25,15 @@ export class JitEvaluator { * * @param sourceUrl The URL of the generated code. * @param statements An array of Angular statement AST nodes to be evaluated. - * @param reflector A helper used when converting the statements to executable code. + * @param refResolver Resolves `o.ExternalReference`s into values. * @param createSourceMaps If true then create a source-map for the generated code and include it * inline as a source-map comment. * @returns A map of all the variables in the generated code. */ evaluateStatements( - sourceUrl: string, statements: o.Statement[], reflector: CompileReflector, + sourceUrl: string, statements: o.Statement[], refResolver: ExternalReferenceResolver, createSourceMaps: boolean): {[key: string]: any} { - const converter = new JitEmitterVisitor(reflector); + const converter = new JitEmitterVisitor(refResolver); const ctx = EmitterVisitorContext.createRoot(); // Ensure generated code is in strict mode if (statements.length > 0 && !isUseStrictStatement(statements[0])) { @@ -101,7 +104,7 @@ export class JitEmitterVisitor extends AbstractJsEmitterVisitor { private _evalArgValues: any[] = []; private _evalExportedVars: string[] = []; - constructor(private reflector: CompileReflector) { + constructor(private refResolver: ExternalReferenceResolver) { super(); } @@ -120,7 +123,7 @@ export class JitEmitterVisitor extends AbstractJsEmitterVisitor { } override visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any { - this._emitReferenceToExternal(ast, this.reflector.resolveExternalReference(ast.value), ctx); + this._emitReferenceToExternal(ast, this.refResolver.resolveExternalReference(ast.value), ctx); return null; } diff --git a/packages/compiler/src/output/ts_emitter.ts b/packages/compiler/src/output/ts_emitter.ts deleted file mode 100644 index 27ff79035ed..00000000000 --- a/packages/compiler/src/output/ts_emitter.ts +++ /dev/null @@ -1,441 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {AbstractEmitterVisitor, CATCH_ERROR_VAR, CATCH_STACK_VAR, EmitterVisitorContext, OutputEmitter} from './abstract_emitter'; -import * as o from './output_ast'; - -export function debugOutputAstAsTypeScript(ast: o.Statement|o.Expression|o.Type|any[]): string { - const converter = new _TsEmitterVisitor(); - const ctx = EmitterVisitorContext.createRoot(); - const asts: any[] = Array.isArray(ast) ? ast : [ast]; - - asts.forEach((ast) => { - if (ast instanceof o.Statement) { - ast.visitStatement(converter, ctx); - } else if (ast instanceof o.Expression) { - ast.visitExpression(converter, ctx); - } else if (ast instanceof o.Type) { - ast.visitType(converter, ctx); - } else { - throw new Error(`Don't know how to print debug info for ${ast}`); - } - }); - return ctx.toSource(); -} - -export type ReferenceFilter = (reference: o.ExternalReference) => boolean; - -export class TypeScriptEmitter implements OutputEmitter { - emitStatementsAndContext( - genFilePath: string, stmts: o.Statement[], preamble: string = '', - emitSourceMaps: boolean = true, referenceFilter?: ReferenceFilter, - importFilter?: ReferenceFilter): {sourceText: string, context: EmitterVisitorContext} { - const converter = new _TsEmitterVisitor(referenceFilter, importFilter); - - const ctx = EmitterVisitorContext.createRoot(); - - converter.visitAllStatements(stmts, ctx); - - const preambleLines = preamble ? preamble.split('\n') : []; - converter.reexports.forEach((reexports, exportedModuleName) => { - const reexportsCode = - reexports.map(reexport => `${reexport.name} as ${reexport.as}`).join(','); - preambleLines.push(`export {${reexportsCode}} from '${exportedModuleName}';`); - }); - - converter.importsWithPrefixes.forEach((prefix, importedModuleName) => { - // Note: can't write the real word for import as it screws up system.js auto detection... - preambleLines.push( - `imp` + - `ort * as ${prefix} from '${importedModuleName}';`); - }); - - const sm = emitSourceMaps ? - ctx.toSourceMapGenerator(genFilePath, preambleLines.length).toJsComment() : - ''; - const lines = [...preambleLines, ctx.toSource(), sm]; - if (sm) { - // always add a newline at the end, as some tools have bugs without it. - lines.push(''); - } - ctx.setPreambleLineCount(preambleLines.length); - return {sourceText: lines.join('\n'), context: ctx}; - } - - emitStatements(genFilePath: string, stmts: o.Statement[], preamble: string = '') { - return this.emitStatementsAndContext(genFilePath, stmts, preamble).sourceText; - } -} - - -class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor { - private typeExpression = 0; - - constructor(private referenceFilter?: ReferenceFilter, private importFilter?: ReferenceFilter) { - super(false); - } - - importsWithPrefixes = new Map(); - reexports = new Map(); - - visitType(t: o.Type|null, ctx: EmitterVisitorContext, defaultType: string = 'any') { - if (t) { - this.typeExpression++; - t.visitType(this, ctx); - this.typeExpression--; - } else { - ctx.print(null, defaultType); - } - } - - override visitLiteralExpr(ast: o.LiteralExpr, ctx: EmitterVisitorContext): any { - const value = ast.value; - if (value == null && ast.type != o.INFERRED_TYPE) { - ctx.print(ast, `(${value} as any)`); - return null; - } - return super.visitLiteralExpr(ast, ctx); - } - - - // Temporary workaround to support strictNullCheck enabled consumers of ngc emit. - // In SNC mode, [] have the type never[], so we cast here to any[]. - // TODO: narrow the cast to a more explicit type, or use a pattern that does not - // start with [].concat. see https://github.com/angular/angular/pull/11846 - override visitLiteralArrayExpr(ast: o.LiteralArrayExpr, ctx: EmitterVisitorContext): any { - if (ast.entries.length === 0) { - ctx.print(ast, '('); - } - const result = super.visitLiteralArrayExpr(ast, ctx); - if (ast.entries.length === 0) { - ctx.print(ast, ' as any[])'); - } - return result; - } - - override visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any { - this._visitIdentifier(ast.value, ast.typeParams, ctx); - return null; - } - - override visitAssertNotNullExpr(ast: o.AssertNotNull, ctx: EmitterVisitorContext): any { - const result = super.visitAssertNotNullExpr(ast, ctx); - ctx.print(ast, '!'); - return result; - } - - override visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): any { - if (stmt.hasModifier(o.StmtModifier.Exported) && stmt.value instanceof o.ExternalExpr && - !stmt.type) { - // check for a reexport - const {name, moduleName} = stmt.value.value; - if (moduleName) { - let reexports = this.reexports.get(moduleName); - if (!reexports) { - reexports = []; - this.reexports.set(moduleName, reexports); - } - reexports.push({name: name!, as: stmt.name}); - return null; - } - } - if (stmt.hasModifier(o.StmtModifier.Exported)) { - ctx.print(stmt, `export `); - } - if (stmt.hasModifier(o.StmtModifier.Final)) { - ctx.print(stmt, `const`); - } else { - ctx.print(stmt, `var`); - } - ctx.print(stmt, ` ${stmt.name}`); - this._printColonType(stmt.type, ctx); - if (stmt.value) { - ctx.print(stmt, ` = `); - stmt.value.visitExpression(this, ctx); - } - ctx.println(stmt, `;`); - return null; - } - - override visitWrappedNodeExpr(ast: o.WrappedNodeExpr, ctx: EmitterVisitorContext): never { - throw new Error('Cannot visit a WrappedNodeExpr when outputting Typescript.'); - } - - override visitCastExpr(ast: o.CastExpr, ctx: EmitterVisitorContext): any { - ctx.print(ast, `(<`); - ast.type!.visitType(this, ctx); - ctx.print(ast, `>`); - ast.value.visitExpression(this, ctx); - ctx.print(ast, `)`); - return null; - } - - override visitInstantiateExpr(ast: o.InstantiateExpr, ctx: EmitterVisitorContext): any { - ctx.print(ast, `new `); - this.typeExpression++; - ast.classExpr.visitExpression(this, ctx); - this.typeExpression--; - ctx.print(ast, `(`); - this.visitAllExpressions(ast.args, ctx, ','); - ctx.print(ast, `)`); - return null; - } - - override visitDeclareClassStmt(stmt: o.ClassStmt, ctx: EmitterVisitorContext): any { - ctx.pushClass(stmt); - if (stmt.hasModifier(o.StmtModifier.Exported)) { - ctx.print(stmt, `export `); - } - ctx.print(stmt, `class ${stmt.name}`); - if (stmt.parent != null) { - ctx.print(stmt, ` extends `); - this.typeExpression++; - stmt.parent.visitExpression(this, ctx); - this.typeExpression--; - } - ctx.println(stmt, ` {`); - ctx.incIndent(); - stmt.fields.forEach((field) => this._visitClassField(field, ctx)); - if (stmt.constructorMethod != null) { - this._visitClassConstructor(stmt, ctx); - } - stmt.getters.forEach((getter) => this._visitClassGetter(getter, ctx)); - stmt.methods.forEach((method) => this._visitClassMethod(method, ctx)); - ctx.decIndent(); - ctx.println(stmt, `}`); - ctx.popClass(); - return null; - } - - private _visitClassField(field: o.ClassField, ctx: EmitterVisitorContext) { - if (field.hasModifier(o.StmtModifier.Private)) { - // comment out as a workaround for #10967 - ctx.print(null, `/*private*/ `); - } - if (field.hasModifier(o.StmtModifier.Static)) { - ctx.print(null, 'static '); - } - ctx.print(null, field.name); - this._printColonType(field.type, ctx); - if (field.initializer) { - ctx.print(null, ' = '); - field.initializer.visitExpression(this, ctx); - } - ctx.println(null, `;`); - } - - private _visitClassGetter(getter: o.ClassGetter, ctx: EmitterVisitorContext) { - if (getter.hasModifier(o.StmtModifier.Private)) { - ctx.print(null, `private `); - } - ctx.print(null, `get ${getter.name}()`); - this._printColonType(getter.type, ctx); - ctx.println(null, ` {`); - ctx.incIndent(); - this.visitAllStatements(getter.body, ctx); - ctx.decIndent(); - ctx.println(null, `}`); - } - - private _visitClassConstructor(stmt: o.ClassStmt, ctx: EmitterVisitorContext) { - ctx.print(stmt, `constructor(`); - this._visitParams(stmt.constructorMethod.params, ctx); - ctx.println(stmt, `) {`); - ctx.incIndent(); - this.visitAllStatements(stmt.constructorMethod.body, ctx); - ctx.decIndent(); - ctx.println(stmt, `}`); - } - - private _visitClassMethod(method: o.ClassMethod, ctx: EmitterVisitorContext) { - if (method.hasModifier(o.StmtModifier.Private)) { - ctx.print(null, `private `); - } - ctx.print(null, `${method.name}(`); - this._visitParams(method.params, ctx); - ctx.print(null, `)`); - this._printColonType(method.type, ctx, 'void'); - ctx.println(null, ` {`); - ctx.incIndent(); - this.visitAllStatements(method.body, ctx); - ctx.decIndent(); - ctx.println(null, `}`); - } - - override visitFunctionExpr(ast: o.FunctionExpr, ctx: EmitterVisitorContext): any { - if (ast.name) { - ctx.print(ast, 'function '); - ctx.print(ast, ast.name); - } - ctx.print(ast, `(`); - this._visitParams(ast.params, ctx); - ctx.print(ast, `)`); - this._printColonType(ast.type, ctx, 'void'); - if (!ast.name) { - ctx.print(ast, ` => `); - } - ctx.println(ast, '{'); - ctx.incIndent(); - this.visitAllStatements(ast.statements, ctx); - ctx.decIndent(); - ctx.print(ast, `}`); - - return null; - } - - override visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, ctx: EmitterVisitorContext): any { - if (stmt.hasModifier(o.StmtModifier.Exported)) { - ctx.print(stmt, `export `); - } - ctx.print(stmt, `function ${stmt.name}(`); - this._visitParams(stmt.params, ctx); - ctx.print(stmt, `)`); - this._printColonType(stmt.type, ctx, 'void'); - ctx.println(stmt, ` {`); - ctx.incIndent(); - this.visitAllStatements(stmt.statements, ctx); - ctx.decIndent(); - ctx.println(stmt, `}`); - return null; - } - - override visitTryCatchStmt(stmt: o.TryCatchStmt, ctx: EmitterVisitorContext): any { - ctx.println(stmt, `try {`); - ctx.incIndent(); - this.visitAllStatements(stmt.bodyStmts, ctx); - ctx.decIndent(); - ctx.println(stmt, `} catch (${CATCH_ERROR_VAR.name}) {`); - ctx.incIndent(); - const catchStmts = - [CATCH_STACK_VAR.set(CATCH_ERROR_VAR.prop('stack', null)).toDeclStmt(null, [ - o.StmtModifier.Final - ])].concat(stmt.catchStmts); - this.visitAllStatements(catchStmts, ctx); - ctx.decIndent(); - ctx.println(stmt, `}`); - return null; - } - - visitBuiltinType(type: o.BuiltinType, ctx: EmitterVisitorContext): any { - let typeStr: string; - switch (type.name) { - case o.BuiltinTypeName.Bool: - typeStr = 'boolean'; - break; - case o.BuiltinTypeName.Dynamic: - typeStr = 'any'; - break; - case o.BuiltinTypeName.Function: - typeStr = 'Function'; - break; - case o.BuiltinTypeName.Number: - typeStr = 'number'; - break; - case o.BuiltinTypeName.Int: - typeStr = 'number'; - break; - case o.BuiltinTypeName.String: - typeStr = 'string'; - break; - case o.BuiltinTypeName.None: - typeStr = 'never'; - break; - default: - throw new Error(`Unsupported builtin type ${type.name}`); - } - ctx.print(null, typeStr); - return null; - } - - visitExpressionType(ast: o.ExpressionType, ctx: EmitterVisitorContext): any { - ast.value.visitExpression(this, ctx); - if (ast.typeParams !== null) { - ctx.print(null, '<'); - this.visitAllObjects(type => this.visitType(type, ctx), ast.typeParams, ctx, ','); - ctx.print(null, '>'); - } - return null; - } - - visitArrayType(type: o.ArrayType, ctx: EmitterVisitorContext): any { - this.visitType(type.of, ctx); - ctx.print(null, `[]`); - return null; - } - - visitMapType(type: o.MapType, ctx: EmitterVisitorContext): any { - ctx.print(null, `{[key: string]:`); - this.visitType(type.valueType, ctx); - ctx.print(null, `}`); - return null; - } - - override getBuiltinMethodName(method: o.BuiltinMethod): string { - let name: string; - switch (method) { - case o.BuiltinMethod.ConcatArray: - name = 'concat'; - break; - case o.BuiltinMethod.SubscribeObservable: - name = 'subscribe'; - break; - case o.BuiltinMethod.Bind: - name = 'bind'; - break; - default: - throw new Error(`Unknown builtin method: ${method}`); - } - return name; - } - - private _visitParams(params: o.FnParam[], ctx: EmitterVisitorContext): void { - this.visitAllObjects(param => { - ctx.print(null, param.name); - this._printColonType(param.type, ctx); - }, params, ctx, ','); - } - - private _visitIdentifier( - value: o.ExternalReference, typeParams: o.Type[]|null, ctx: EmitterVisitorContext): void { - const {name, moduleName} = value; - if (this.referenceFilter && this.referenceFilter(value)) { - ctx.print(null, '(null as any)'); - return; - } - if (moduleName && (!this.importFilter || !this.importFilter(value))) { - let prefix = this.importsWithPrefixes.get(moduleName); - if (prefix == null) { - prefix = `i${this.importsWithPrefixes.size}`; - this.importsWithPrefixes.set(moduleName, prefix); - } - ctx.print(null, `${prefix}.`); - } - ctx.print(null, name!); - - if (this.typeExpression > 0) { - // If we are in a type expression that refers to a generic type then supply - // the required type parameters. If there were not enough type parameters - // supplied, supply any as the type. Outside a type expression the reference - // should not supply type parameters and be treated as a simple value reference - // to the constructor function itself. - const suppliedParameters = typeParams || []; - if (suppliedParameters.length > 0) { - ctx.print(null, `<`); - this.visitAllObjects(type => type.visitType(this, ctx), typeParams!, ctx, ','); - ctx.print(null, `>`); - } - } - } - - private _printColonType(type: o.Type|null, ctx: EmitterVisitorContext, defaultType?: string) { - if (type !== o.INFERRED_TYPE) { - ctx.print(null, ':'); - this.visitType(type, ctx, defaultType); - } - } -} diff --git a/packages/compiler/src/output/value_util.ts b/packages/compiler/src/output/value_util.ts deleted file mode 100644 index 904c50b2aae..00000000000 --- a/packages/compiler/src/output/value_util.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {OutputContext} from '../constant_pool'; -import {ValueTransformer, visitValue} from '../util'; - -import * as o from './output_ast'; - -export const QUOTED_KEYS = '$quoted$'; - -export function convertValueToOutputAst( - ctx: OutputContext, value: any, type: o.Type|null = null): o.Expression { - return visitValue(value, new _ValueOutputAstTransformer(ctx), type); -} - -class _ValueOutputAstTransformer implements ValueTransformer { - constructor(private ctx: OutputContext) {} - visitArray(arr: any[], type: o.Type): o.Expression { - const values: o.Expression[] = []; - // Note Array.map() must not be used to convert the values because it will - // skip over empty elements in arrays constructed using `new Array(length)`, - // resulting in `undefined` elements. This breaks the type guarantee that - // all values in `o.LiteralArrayExpr` are of type `o.Expression`. - // See test case in `value_util_spec.ts`. - for (let i = 0; i < arr.length; ++i) { - values.push(visitValue(arr[i], this, null /* context */)); - } - return o.literalArr(values, type); - } - - visitStringMap(map: {[key: string]: any}, type: o.MapType): o.Expression { - const entries: o.LiteralMapEntry[] = []; - const quotedSet = new Set(map && map[QUOTED_KEYS]); - Object.keys(map).forEach(key => { - entries.push( - new o.LiteralMapEntry(key, visitValue(map[key], this, null), quotedSet.has(key))); - }); - return new o.LiteralMapExpr(entries, type); - } - - visitPrimitive(value: any, type: o.Type): o.Expression { - return o.literal(value, type); - } - - visitOther(value: any, type: o.Type): o.Expression { - if (value instanceof o.Expression) { - return value; - } else { - return this.ctx.importExpr(value); - } - } -} diff --git a/packages/compiler/src/parse_util.ts b/packages/compiler/src/parse_util.ts index 823f507f454..4dc2b285ed4 100644 --- a/packages/compiler/src/parse_util.ts +++ b/packages/compiler/src/parse_util.ts @@ -5,7 +5,6 @@ * 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 {StaticSymbol} from './aot/static_symbol'; import * as chars from './chars'; import {stringify} from './util'; @@ -155,15 +154,6 @@ export class ParseError { } } -export function typeSourceSpan(kind: string, type: CompileIdentifierMetadata): ParseSourceSpan { - const moduleUrl = identifierModuleUrl(type); - const sourceFileName = moduleUrl != null ? `in ${kind} ${identifierName(type)} in ${moduleUrl}` : - `in ${kind} ${identifierName(type)}`; - const sourceFile = new ParseSourceFile('', sourceFileName); - return new ParseSourceSpan( - new ParseLocation(sourceFile, -1, -1, -1), new ParseLocation(sourceFile, -1, -1, -1)); -} - /** * Generates Source Span object for a given R3 Type for JIT mode. * @@ -206,9 +196,6 @@ export function identifierName(compileIdentifier: CompileIdentifierMetadata|null return null; } const ref = compileIdentifier.reference; - if (ref instanceof StaticSymbol) { - return ref.name; - } if (ref['__anonymousType']) { return ref['__anonymousType']; } @@ -228,15 +215,6 @@ export function identifierName(compileIdentifier: CompileIdentifierMetadata|null return identifier; } -export function identifierModuleUrl(compileIdentifier: CompileIdentifierMetadata): string { - const ref = compileIdentifier.reference; - if (ref instanceof StaticSymbol) { - return ref.filePath; - } - // Runtime type - return `./${stringify(ref)}`; -} - export interface CompileIdentifierMetadata { reference: any; } diff --git a/packages/compiler/src/pipe_resolver.ts b/packages/compiler/src/pipe_resolver.ts deleted file mode 100644 index 876d05d9cf7..00000000000 --- a/packages/compiler/src/pipe_resolver.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {CompileReflector} from './compile_reflector'; -import {createPipe, Pipe, Type} from './core'; -import {findLast} from './directive_resolver'; -import {resolveForwardRef, stringify} from './util'; - -/** - * Resolve a `Type` for {@link Pipe}. - * - * This interface can be overridden by the application developer to create custom behavior. - * - * See {@link Compiler} - */ -export class PipeResolver { - constructor(private _reflector: CompileReflector) {} - - isPipe(type: Type) { - const typeMetadata = this._reflector.annotations(resolveForwardRef(type)); - return typeMetadata && typeMetadata.some(createPipe.isTypeOf); - } - - /** - * Return {@link Pipe} for a given `Type`. - */ - resolve(type: Type, throwIfNotFound = true): Pipe|null { - const metas = this._reflector.annotations(resolveForwardRef(type)); - if (metas) { - const annotation = findLast(metas, createPipe.isTypeOf); - if (annotation) { - return annotation; - } - } - if (throwIfNotFound) { - throw new Error(`No Pipe decorator found on ${stringify(type)}`); - } - return null; - } -} diff --git a/packages/compiler/src/provider_analyzer.ts b/packages/compiler/src/provider_analyzer.ts deleted file mode 100644 index 24e6c2395a7..00000000000 --- a/packages/compiler/src/provider_analyzer.ts +++ /dev/null @@ -1,543 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenName, tokenReference} from './compile_metadata'; -import {CompileReflector} from './compile_reflector'; -import {createTokenForExternalReference, Identifiers} from './identifiers'; -import {ParseError, ParseSourceSpan} from './parse_util'; -import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, QueryMatch, ReferenceAst} from './template_parser/template_ast'; - -export class ProviderError extends ParseError { - constructor(message: string, span: ParseSourceSpan) { - super(span, message); - } -} - -export interface QueryWithId { - meta: CompileQueryMetadata; - queryId: number; -} - -export class ProviderViewContext { - /** - * @internal - */ - viewQueries: Map; - /** - * @internal - */ - viewProviders: Map; - errors: ProviderError[] = []; - - constructor(public reflector: CompileReflector, public component: CompileDirectiveMetadata) { - this.viewQueries = _getViewQueries(component); - this.viewProviders = new Map(); - component.viewProviders.forEach((provider) => { - if (this.viewProviders.get(tokenReference(provider.token)) == null) { - this.viewProviders.set(tokenReference(provider.token), true); - } - }); - } -} - -export class ProviderElementContext { - private _contentQueries: Map; - - private _transformedProviders = new Map(); - private _seenProviders = new Map(); - private _allProviders: Map; - private _attrs: {[key: string]: string}; - private _queriedTokens = new Map(); - - public readonly transformedHasViewContainer: boolean = false; - - constructor( - public viewContext: ProviderViewContext, private _parent: ProviderElementContext, - private _isViewRoot: boolean, private _directiveAsts: DirectiveAst[], attrs: AttrAst[], - refs: ReferenceAst[], isTemplate: boolean, contentQueryStartId: number, - private _sourceSpan: ParseSourceSpan) { - this._attrs = {}; - attrs.forEach((attrAst) => this._attrs[attrAst.name] = attrAst.value); - const directivesMeta = _directiveAsts.map(directiveAst => directiveAst.directive); - this._allProviders = - _resolveProvidersFromDirectives(directivesMeta, _sourceSpan, viewContext.errors); - this._contentQueries = _getContentQueries(contentQueryStartId, directivesMeta); - Array.from(this._allProviders.values()).forEach((provider) => { - this._addQueryReadsTo(provider.token, provider.token, this._queriedTokens); - }); - if (isTemplate) { - const templateRefId = - createTokenForExternalReference(this.viewContext.reflector, Identifiers.TemplateRef); - this._addQueryReadsTo(templateRefId, templateRefId, this._queriedTokens); - } - refs.forEach((refAst) => { - let defaultQueryValue = refAst.value || - createTokenForExternalReference(this.viewContext.reflector, Identifiers.ElementRef); - this._addQueryReadsTo({value: refAst.name}, defaultQueryValue, this._queriedTokens); - }); - if (this._queriedTokens.get( - this.viewContext.reflector.resolveExternalReference(Identifiers.ViewContainerRef))) { - this.transformedHasViewContainer = true; - } - - // create the providers that we know are eager first - Array.from(this._allProviders.values()).forEach((provider) => { - const eager = provider.eager || this._queriedTokens.get(tokenReference(provider.token)); - if (eager) { - this._getOrCreateLocalProvider(provider.providerType, provider.token, true); - } - }); - } - - afterElement() { - // collect lazy providers - Array.from(this._allProviders.values()).forEach((provider) => { - this._getOrCreateLocalProvider(provider.providerType, provider.token, false); - }); - } - - get transformProviders(): ProviderAst[] { - // Note: Maps keep their insertion order. - const lazyProviders: ProviderAst[] = []; - const eagerProviders: ProviderAst[] = []; - this._transformedProviders.forEach(provider => { - if (provider.eager) { - eagerProviders.push(provider); - } else { - lazyProviders.push(provider); - } - }); - return lazyProviders.concat(eagerProviders); - } - - get transformedDirectiveAsts(): DirectiveAst[] { - const sortedProviderTypes = this.transformProviders.map(provider => provider.token.identifier); - const sortedDirectives = this._directiveAsts.slice(); - sortedDirectives.sort( - (dir1, dir2) => sortedProviderTypes.indexOf(dir1.directive.type) - - sortedProviderTypes.indexOf(dir2.directive.type)); - return sortedDirectives; - } - - get queryMatches(): QueryMatch[] { - const allMatches: QueryMatch[] = []; - this._queriedTokens.forEach((matches: QueryMatch[]) => { - allMatches.push(...matches); - }); - return allMatches; - } - - private _addQueryReadsTo( - token: CompileTokenMetadata, defaultValue: CompileTokenMetadata, - queryReadTokens: Map) { - this._getQueriesFor(token).forEach((query) => { - const queryValue = query.meta.read || defaultValue; - const tokenRef = tokenReference(queryValue); - let queryMatches = queryReadTokens.get(tokenRef); - if (!queryMatches) { - queryMatches = []; - queryReadTokens.set(tokenRef, queryMatches); - } - queryMatches.push({queryId: query.queryId, value: queryValue}); - }); - } - - private _getQueriesFor(token: CompileTokenMetadata): QueryWithId[] { - const result: QueryWithId[] = []; - let currentEl: ProviderElementContext = this; - let distance = 0; - let queries: QueryWithId[]|undefined; - while (currentEl !== null) { - queries = currentEl._contentQueries.get(tokenReference(token)); - if (queries) { - result.push(...queries.filter((query) => query.meta.descendants || distance <= 1)); - } - if (currentEl._directiveAsts.length > 0) { - distance++; - } - currentEl = currentEl._parent; - } - queries = this.viewContext.viewQueries.get(tokenReference(token)); - if (queries) { - result.push(...queries); - } - return result; - } - - - private _getOrCreateLocalProvider( - requestingProviderType: ProviderAstType, token: CompileTokenMetadata, - eager: boolean): ProviderAst|null { - const resolvedProvider = this._allProviders.get(tokenReference(token)); - if (!resolvedProvider || - ((requestingProviderType === ProviderAstType.Directive || - requestingProviderType === ProviderAstType.PublicService) && - resolvedProvider.providerType === ProviderAstType.PrivateService) || - ((requestingProviderType === ProviderAstType.PrivateService || - requestingProviderType === ProviderAstType.PublicService) && - resolvedProvider.providerType === ProviderAstType.Builtin)) { - return null; - } - let transformedProviderAst = this._transformedProviders.get(tokenReference(token)); - if (transformedProviderAst) { - return transformedProviderAst; - } - if (this._seenProviders.get(tokenReference(token)) != null) { - this.viewContext.errors.push(new ProviderError( - `Cannot instantiate cyclic dependency! ${tokenName(token)}`, this._sourceSpan)); - return null; - } - this._seenProviders.set(tokenReference(token), true); - const transformedProviders = resolvedProvider.providers.map((provider) => { - let transformedUseValue = provider.useValue; - let transformedUseExisting = provider.useExisting!; - let transformedDeps: CompileDiDependencyMetadata[] = undefined!; - if (provider.useExisting != null) { - const existingDiDep = this._getDependency( - resolvedProvider.providerType, {token: provider.useExisting}, eager)!; - if (existingDiDep.token != null) { - transformedUseExisting = existingDiDep.token; - } else { - transformedUseExisting = null!; - transformedUseValue = existingDiDep.value; - } - } else if (provider.useFactory) { - const deps = provider.deps || provider.useFactory.diDeps; - transformedDeps = - deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager)!); - } else if (provider.useClass) { - const deps = provider.deps || provider.useClass.diDeps; - transformedDeps = - deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager)!); - } - return _transformProvider(provider, { - useExisting: transformedUseExisting, - useValue: transformedUseValue, - deps: transformedDeps - }); - }); - transformedProviderAst = - _transformProviderAst(resolvedProvider, {eager: eager, providers: transformedProviders}); - this._transformedProviders.set(tokenReference(token), transformedProviderAst); - return transformedProviderAst; - } - - private _getLocalDependency( - requestingProviderType: ProviderAstType, dep: CompileDiDependencyMetadata, - eager: boolean = false): CompileDiDependencyMetadata|null { - if (dep.isAttribute) { - const attrValue = this._attrs[dep.token!.value]; - return {isValue: true, value: attrValue == null ? null : attrValue}; - } - - if (dep.token != null) { - // access builtints - if ((requestingProviderType === ProviderAstType.Directive || - requestingProviderType === ProviderAstType.Component)) { - if (tokenReference(dep.token) === - this.viewContext.reflector.resolveExternalReference(Identifiers.Renderer) || - tokenReference(dep.token) === - this.viewContext.reflector.resolveExternalReference(Identifiers.ElementRef) || - tokenReference(dep.token) === - this.viewContext.reflector.resolveExternalReference( - Identifiers.ChangeDetectorRef) || - tokenReference(dep.token) === - this.viewContext.reflector.resolveExternalReference(Identifiers.TemplateRef)) { - return dep; - } - if (tokenReference(dep.token) === - this.viewContext.reflector.resolveExternalReference(Identifiers.ViewContainerRef)) { - (this as {transformedHasViewContainer: boolean}).transformedHasViewContainer = true; - } - } - // access the injector - if (tokenReference(dep.token) === - this.viewContext.reflector.resolveExternalReference(Identifiers.Injector)) { - return dep; - } - // access providers - if (this._getOrCreateLocalProvider(requestingProviderType, dep.token, eager) != null) { - return dep; - } - } - return null; - } - - private _getDependency( - requestingProviderType: ProviderAstType, dep: CompileDiDependencyMetadata, - eager: boolean = false): CompileDiDependencyMetadata|null { - let currElement: ProviderElementContext = this; - let currEager: boolean = eager; - let result: CompileDiDependencyMetadata|null = null; - if (!dep.isSkipSelf) { - result = this._getLocalDependency(requestingProviderType, dep, eager); - } - if (dep.isSelf) { - if (!result && dep.isOptional) { - result = {isValue: true, value: null}; - } - } else { - // check parent elements - while (!result && currElement._parent) { - const prevElement = currElement; - currElement = currElement._parent; - if (prevElement._isViewRoot) { - currEager = false; - } - result = currElement._getLocalDependency(ProviderAstType.PublicService, dep, currEager); - } - // check @Host restriction - if (!result) { - if (!dep.isHost || this.viewContext.component.isHost || - this.viewContext.component.type.reference === tokenReference(dep.token!) || - this.viewContext.viewProviders.get(tokenReference(dep.token!)) != null) { - result = dep; - } else { - result = dep.isOptional ? {isValue: true, value: null} : null; - } - } - } - if (!result) { - this.viewContext.errors.push( - new ProviderError(`No provider for ${tokenName(dep.token!)}`, this._sourceSpan)); - } - return result; - } -} - - -export class NgModuleProviderAnalyzer { - private _transformedProviders = new Map(); - private _seenProviders = new Map(); - private _allProviders: Map; - private _errors: ProviderError[] = []; - - constructor( - private reflector: CompileReflector, ngModule: CompileNgModuleMetadata, - extraProviders: CompileProviderMetadata[], sourceSpan: ParseSourceSpan) { - this._allProviders = new Map(); - ngModule.transitiveModule.modules.forEach((ngModuleType: CompileTypeMetadata) => { - const ngModuleProvider = {token: {identifier: ngModuleType}, useClass: ngModuleType}; - _resolveProviders( - [ngModuleProvider], ProviderAstType.PublicService, true, sourceSpan, this._errors, - this._allProviders, /* isModule */ true); - }); - _resolveProviders( - ngModule.transitiveModule.providers.map(entry => entry.provider).concat(extraProviders), - ProviderAstType.PublicService, false, sourceSpan, this._errors, this._allProviders, - /* isModule */ false); - } - - parse(): ProviderAst[] { - Array.from(this._allProviders.values()).forEach((provider) => { - this._getOrCreateLocalProvider(provider.token, provider.eager); - }); - if (this._errors.length > 0) { - const errorString = this._errors.join('\n'); - throw new Error(`Provider parse errors:\n${errorString}`); - } - // Note: Maps keep their insertion order. - const lazyProviders: ProviderAst[] = []; - const eagerProviders: ProviderAst[] = []; - this._transformedProviders.forEach(provider => { - if (provider.eager) { - eagerProviders.push(provider); - } else { - lazyProviders.push(provider); - } - }); - return lazyProviders.concat(eagerProviders); - } - - private _getOrCreateLocalProvider(token: CompileTokenMetadata, eager: boolean): ProviderAst|null { - const resolvedProvider = this._allProviders.get(tokenReference(token)); - if (!resolvedProvider) { - return null; - } - let transformedProviderAst = this._transformedProviders.get(tokenReference(token)); - if (transformedProviderAst) { - return transformedProviderAst; - } - if (this._seenProviders.get(tokenReference(token)) != null) { - this._errors.push(new ProviderError( - `Cannot instantiate cyclic dependency! ${tokenName(token)}`, - resolvedProvider.sourceSpan)); - return null; - } - this._seenProviders.set(tokenReference(token), true); - const transformedProviders = resolvedProvider.providers.map((provider) => { - let transformedUseValue = provider.useValue; - let transformedUseExisting = provider.useExisting!; - let transformedDeps: CompileDiDependencyMetadata[] = undefined!; - if (provider.useExisting != null) { - const existingDiDep = - this._getDependency({token: provider.useExisting}, eager, resolvedProvider.sourceSpan); - if (existingDiDep.token != null) { - transformedUseExisting = existingDiDep.token; - } else { - transformedUseExisting = null!; - transformedUseValue = existingDiDep.value; - } - } else if (provider.useFactory) { - const deps = provider.deps || provider.useFactory.diDeps; - transformedDeps = - deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan)); - } else if (provider.useClass) { - const deps = provider.deps || provider.useClass.diDeps; - transformedDeps = - deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan)); - } - return _transformProvider(provider, { - useExisting: transformedUseExisting, - useValue: transformedUseValue, - deps: transformedDeps - }); - }); - transformedProviderAst = - _transformProviderAst(resolvedProvider, {eager: eager, providers: transformedProviders}); - this._transformedProviders.set(tokenReference(token), transformedProviderAst); - return transformedProviderAst; - } - - private _getDependency( - dep: CompileDiDependencyMetadata, eager: boolean = false, - requestorSourceSpan: ParseSourceSpan): CompileDiDependencyMetadata { - let foundLocal = false; - if (!dep.isSkipSelf && dep.token != null) { - // access the injector - if (tokenReference(dep.token) === - this.reflector.resolveExternalReference(Identifiers.Injector) || - tokenReference(dep.token) === - this.reflector.resolveExternalReference(Identifiers.ComponentFactoryResolver)) { - foundLocal = true; - // access providers - } else if (this._getOrCreateLocalProvider(dep.token, eager) != null) { - foundLocal = true; - } - } - return dep; - } -} - -function _transformProvider( - provider: CompileProviderMetadata, - {useExisting, useValue, deps}: - {useExisting: CompileTokenMetadata, useValue: any, deps: CompileDiDependencyMetadata[]}) { - return { - token: provider.token, - useClass: provider.useClass, - useExisting: useExisting, - useFactory: provider.useFactory, - useValue: useValue, - deps: deps, - multi: provider.multi - }; -} - -function _transformProviderAst( - provider: ProviderAst, - {eager, providers}: {eager: boolean, providers: CompileProviderMetadata[]}): ProviderAst { - return new ProviderAst( - provider.token, provider.multiProvider, provider.eager || eager, providers, - provider.providerType, provider.lifecycleHooks, provider.sourceSpan, provider.isModule); -} - -function _resolveProvidersFromDirectives( - directives: CompileDirectiveSummary[], sourceSpan: ParseSourceSpan, - targetErrors: ParseError[]): Map { - const providersByToken = new Map(); - directives.forEach((directive) => { - const dirProvider: - CompileProviderMetadata = {token: {identifier: directive.type}, useClass: directive.type}; - _resolveProviders( - [dirProvider], - directive.isComponent ? ProviderAstType.Component : ProviderAstType.Directive, true, - sourceSpan, targetErrors, providersByToken, /* isModule */ false); - }); - - // Note: directives need to be able to overwrite providers of a component! - const directivesWithComponentFirst = - directives.filter(dir => dir.isComponent).concat(directives.filter(dir => !dir.isComponent)); - directivesWithComponentFirst.forEach((directive) => { - _resolveProviders( - directive.providers, ProviderAstType.PublicService, false, sourceSpan, targetErrors, - providersByToken, /* isModule */ false); - _resolveProviders( - directive.viewProviders, ProviderAstType.PrivateService, false, sourceSpan, targetErrors, - providersByToken, /* isModule */ false); - }); - return providersByToken; -} - -function _resolveProviders( - providers: CompileProviderMetadata[], providerType: ProviderAstType, eager: boolean, - sourceSpan: ParseSourceSpan, targetErrors: ParseError[], - targetProvidersByToken: Map, isModule: boolean) { - providers.forEach((provider) => { - let resolvedProvider = targetProvidersByToken.get(tokenReference(provider.token)); - if (resolvedProvider != null && !!resolvedProvider.multiProvider !== !!provider.multi) { - targetErrors.push(new ProviderError( - `Mixing multi and non multi provider is not possible for token ${ - tokenName(resolvedProvider.token)}`, - sourceSpan)); - } - if (!resolvedProvider) { - const lifecycleHooks = provider.token.identifier && - (provider.token.identifier).lifecycleHooks ? - (provider.token.identifier).lifecycleHooks : - []; - const isUseValue = !(provider.useClass || provider.useExisting || provider.useFactory); - resolvedProvider = new ProviderAst( - provider.token, !!provider.multi, eager || isUseValue, [provider], providerType, - lifecycleHooks, sourceSpan, isModule); - targetProvidersByToken.set(tokenReference(provider.token), resolvedProvider); - } else { - if (!provider.multi) { - resolvedProvider.providers.length = 0; - } - resolvedProvider.providers.push(provider); - } - }); -} - - -function _getViewQueries(component: CompileDirectiveMetadata): Map { - // Note: queries start with id 1 so we can use the number in a Bloom filter! - let viewQueryId = 1; - const viewQueries = new Map(); - if (component.viewQueries) { - component.viewQueries.forEach( - (query) => _addQueryToTokenMap(viewQueries, {meta: query, queryId: viewQueryId++})); - } - return viewQueries; -} - -function _getContentQueries( - contentQueryStartId: number, directives: CompileDirectiveSummary[]): Map { - let contentQueryId = contentQueryStartId; - const contentQueries = new Map(); - directives.forEach((directive, directiveIndex) => { - if (directive.queries) { - directive.queries.forEach( - (query) => _addQueryToTokenMap(contentQueries, {meta: query, queryId: contentQueryId++})); - } - }); - return contentQueries; -} - -function _addQueryToTokenMap(map: Map, query: QueryWithId) { - query.meta.selectors.forEach((token: CompileTokenMetadata) => { - let entry = map.get(tokenReference(token)); - if (!entry) { - entry = []; - map.set(tokenReference(token), entry); - } - entry.push(query); - }); -} diff --git a/packages/compiler/src/render3/r3_jit.ts b/packages/compiler/src/render3/r3_jit.ts index a0a51307deb..731aa1eb3ce 100644 --- a/packages/compiler/src/render3/r3_jit.ts +++ b/packages/compiler/src/render3/r3_jit.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {CompileReflector} from '../compile_reflector'; import * as o from '../output/output_ast'; +import {ExternalReferenceResolver} from '../output/output_jit'; /** * Implementation of `CompileReflector` which resolves references to @angular/core @@ -15,10 +15,10 @@ import * as o from '../output/output_ast'; * * Only supports `resolveExternalReference`, all other methods throw. */ -export class R3JitReflector implements CompileReflector { - constructor(private context: {[key: string]: any}) {} +export class R3JitReflector implements ExternalReferenceResolver { + constructor(private context: {[key: string]: unknown}) {} - resolveExternalReference(ref: o.ExternalReference): any { + resolveExternalReference(ref: o.ExternalReference): unknown { // This reflector only handles @angular/core imports. if (ref.moduleName !== '@angular/core') { throw new Error(`Cannot resolve external reference to ${ @@ -29,36 +29,4 @@ export class R3JitReflector implements CompileReflector { } return this.context[ref.name!]; } - - parameters(typeOrFunc: any): any[][] { - throw new Error('Not implemented.'); - } - - annotations(typeOrFunc: any): any[] { - throw new Error('Not implemented.'); - } - - shallowAnnotations(typeOrFunc: any): any[] { - throw new Error('Not implemented.'); - } - - tryAnnotations(typeOrFunc: any): any[] { - throw new Error('Not implemented.'); - } - - propMetadata(typeOrFunc: any): {[key: string]: any[];} { - throw new Error('Not implemented.'); - } - - hasLifecycleHook(type: any, lcProperty: string): boolean { - throw new Error('Not implemented.'); - } - - guards(typeOrFunc: any): {[key: string]: any;} { - throw new Error('Not implemented.'); - } - - componentModuleUrl(type: any, cmpMetadata: any): string { - throw new Error('Not implemented.'); - } } diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 6e38d9c251b..475ce119b74 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {CompileDirectiveSummary} from '../../compile_metadata'; import {BindingForm, convertPropertyBinding} from '../../compiler_util/expression_converter'; import {ConstantPool} from '../../constant_pool'; import * as core from '../../core'; @@ -15,7 +14,6 @@ import * as o from '../../output/output_ast'; import {ParseError, ParseSourceSpan, sanitizeIdentifier} from '../../parse_util'; import {CssSelector, SelectorMatcher} from '../../selector'; import {ShadowCss} from '../../shadow_css'; -import {CONTENT_ATTR, HOST_ATTR} from '../../style_compiler'; import {BindingParser} from '../../template_parser/binding_parser'; import {error} from '../../util'; import {BoundEvent} from '../r3_ast'; @@ -32,6 +30,11 @@ import {asLiteral, chainedInstruction, conditionallyCreateMapObjectLiteral, CONT // If there is a match, the first matching group will contain the attribute name to bind. const ATTR_REGEX = /attr\.([^\]]+)/; + +const COMPONENT_VARIABLE = '%COMP%'; +const HOST_ATTR = `_nghost-${COMPONENT_VARIABLE}`; +const CONTENT_ATTR = `_ngcontent-${COMPONENT_VARIABLE}`; + function baseDirectiveFields( meta: R3DirectiveMetadata, constantPool: ConstantPool, bindingParser: BindingParser): DefinitionMap { @@ -483,18 +486,18 @@ function createHostBindingsFunction( const updateStatements: o.Statement[] = []; const hostBindingSourceSpan = typeSourceSpan; - const directiveSummary = metadataAsSummary(hostBindingsMetadata); // Calculate host event bindings - const eventBindings = - bindingParser.createDirectiveHostEventAsts(directiveSummary, hostBindingSourceSpan); + const eventBindings = bindingParser.createDirectiveHostEventAsts( + hostBindingsMetadata.listeners, hostBindingSourceSpan); if (eventBindings && eventBindings.length) { const listeners = createHostListeners(eventBindings, name); createStatements.push(...listeners); } // Calculate the host property bindings - const bindings = bindingParser.createBoundHostProperties(directiveSummary, hostBindingSourceSpan); + const bindings = bindingParser.createBoundHostProperties( + hostBindingsMetadata.properties, hostBindingSourceSpan); const allOtherBindings: ParsedProperty[] = []; // We need to calculate the total amount of binding slots required by @@ -707,19 +710,6 @@ function createHostListeners(eventBindings: ParsedEvent[], name?: string): o.Sta return instructions; } -function metadataAsSummary(meta: R3HostMetadata): CompileDirectiveSummary { - // clang-format off - return { - // This is used by the BindingParser, which only deals with listeners and properties. There's no - // need to pass attributes to it. - hostAttributes: {}, - hostListeners: meta.listeners, - hostProperties: meta.properties, - } as CompileDirectiveSummary; - // clang-format on -} - - const HOST_REG_EXP = /^(?:\[([^\]]+)\])|(?:\(([^\)]+)\))$/; // Represents the groups in the above regex. @@ -804,12 +794,11 @@ export function parseHostBindings(host: {[key: string]: string|o.Expression}): P */ export function verifyHostBindings( bindings: ParsedHostBindings, sourceSpan: ParseSourceSpan): ParseError[] { - const summary = metadataAsSummary(bindings); // TODO: abstract out host bindings verification logic and use it instead of // creating events and properties ASTs to detect errors (FW-996) const bindingParser = makeBindingParser(); - bindingParser.createDirectiveHostEventAsts(summary, sourceSpan); - bindingParser.createBoundHostProperties(summary, sourceSpan); + bindingParser.createDirectiveHostEventAsts(bindings.listeners, sourceSpan); + bindingParser.createBoundHostProperties(bindings.properties, sourceSpan); return bindingParser.errors; } diff --git a/packages/compiler/src/render3/view/styling_builder.ts b/packages/compiler/src/render3/view/styling_builder.ts index eea4fe75ad2..060668c2a2e 100644 --- a/packages/compiler/src/render3/view/styling_builder.ts +++ b/packages/compiler/src/render3/view/styling_builder.ts @@ -6,10 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ import {AttributeMarker} from '../../core'; -import {AST, ASTWithSource, BindingPipe, BindingType, Interpolation} from '../../expression_parser/ast'; +import {AST, ASTWithSource, BindingPipe, BindingType, EmptyExpr, Interpolation} from '../../expression_parser/ast'; import * as o from '../../output/output_ast'; import {ParseSourceSpan} from '../../parse_util'; -import {isEmptyExpression} from '../../template_parser/template_parser'; import * as t from '../r3_ast'; import {Identifiers as R3} from '../r3_identifiers'; @@ -618,3 +617,10 @@ function getStylePropInterpolationExpression(interpolation: Interpolation) { function isCssCustomProperty(name: string): boolean { return name.startsWith('--'); } + +function isEmptyExpression(ast: AST): boolean { + if (ast instanceof ASTWithSource) { + ast = ast.ast; + } + return ast instanceof EmptyExpr; +} diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 286a0e8aa69..82b01f58d5b 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {flatten} from '../../compile_metadata'; import {BindingForm, BuiltinFunctionCall, convertActionBinding, convertPropertyBinding, convertUpdateArguments, LocalResolver} from '../../compiler_util/expression_converter'; import {ConstantPool} from '../../constant_pool'; import * as core from '../../core'; @@ -2226,8 +2225,7 @@ const elementRegistry = new DomElementSchemaRegistry(); */ export function makeBindingParser( interpolationConfig: InterpolationConfig = DEFAULT_INTERPOLATION_CONFIG): BindingParser { - return new BindingParser( - new IvyParser(new Lexer()), interpolationConfig, elementRegistry, null, []); + return new BindingParser(new IvyParser(new Lexer()), interpolationConfig, elementRegistry, []); } export function resolveSanitizationFn(context: core.SecurityContext, isAttribute?: boolean) { @@ -2406,3 +2404,10 @@ export interface ParsedTemplate { */ commentNodes?: t.Comment[]; } + +function flatten(list: Array): T[] { + return list.reduce((flat: any[], item: T|T[]): T[] => { + const flatItem = Array.isArray(item) ? flatten(item) : item; + return (flat).concat(flatItem); + }, []); +} diff --git a/packages/compiler/src/style_compiler.ts b/packages/compiler/src/style_compiler.ts deleted file mode 100644 index f766f9d4505..00000000000 --- a/packages/compiler/src/style_compiler.ts +++ /dev/null @@ -1,99 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {CompileDirectiveMetadata, CompileStylesheetMetadata} from './compile_metadata'; -import {OutputContext} from './constant_pool'; -import {ViewEncapsulation} from './core'; -import * as o from './output/output_ast'; -import {identifierModuleUrl, identifierName} from './parse_util'; -import {ShadowCss} from './shadow_css'; -import {UrlResolver} from './url_resolver'; - -const COMPONENT_VARIABLE = '%COMP%'; -export const HOST_ATTR = `_nghost-${COMPONENT_VARIABLE}`; -export const CONTENT_ATTR = `_ngcontent-${COMPONENT_VARIABLE}`; - -export class StylesCompileDependency { - constructor( - public name: string, public moduleUrl: string, public setValue: (value: any) => void) {} -} - -export class CompiledStylesheet { - constructor( - public outputCtx: OutputContext, public stylesVar: string, - public dependencies: StylesCompileDependency[], public isShimmed: boolean, - public meta: CompileStylesheetMetadata) {} -} - -export class StyleCompiler { - private _shadowCss: ShadowCss = new ShadowCss(); - - constructor(private _urlResolver: UrlResolver) {} - - compileComponent(outputCtx: OutputContext, comp: CompileDirectiveMetadata): CompiledStylesheet { - const template = comp.template !; - return this._compileStyles( - outputCtx, comp, new CompileStylesheetMetadata({ - styles: template.styles, - styleUrls: template.styleUrls, - moduleUrl: identifierModuleUrl(comp.type) - }), - this.needsStyleShim(comp), true); - } - - compileStyles( - outputCtx: OutputContext, comp: CompileDirectiveMetadata, - stylesheet: CompileStylesheetMetadata, - shim: boolean = this.needsStyleShim(comp)): CompiledStylesheet { - return this._compileStyles(outputCtx, comp, stylesheet, shim, false); - } - - needsStyleShim(comp: CompileDirectiveMetadata): boolean { - return comp.template !.encapsulation === ViewEncapsulation.Emulated; - } - - private _compileStyles( - outputCtx: OutputContext, comp: CompileDirectiveMetadata, - stylesheet: CompileStylesheetMetadata, shim: boolean, - isComponentStylesheet: boolean): CompiledStylesheet { - const styleExpressions: o.Expression[] = - stylesheet.styles.map(plainStyle => o.literal(this._shimIfNeeded(plainStyle, shim))); - const dependencies: StylesCompileDependency[] = []; - stylesheet.styleUrls.forEach((styleUrl) => { - const exprIndex = styleExpressions.length; - // Note: This placeholder will be filled later. - styleExpressions.push(null!); - dependencies.push(new StylesCompileDependency( - getStylesVarName(null), styleUrl, - (value) => styleExpressions[exprIndex] = outputCtx.importExpr(value))); - }); - // styles variable contains plain strings and arrays of other styles arrays (recursive), - // so we set its type to dynamic. - const stylesVar = getStylesVarName(isComponentStylesheet ? comp : null); - const stmt = o.variable(stylesVar) - .set(o.literalArr( - styleExpressions, new o.ArrayType(o.DYNAMIC_TYPE, [o.TypeModifier.Const]))) - .toDeclStmt(null, isComponentStylesheet ? [o.StmtModifier.Final] : [ - o.StmtModifier.Final, o.StmtModifier.Exported - ]); - outputCtx.statements.push(stmt); - return new CompiledStylesheet(outputCtx, stylesVar, dependencies, shim, stylesheet); - } - - private _shimIfNeeded(style: string, shim: boolean): string { - return shim ? this._shadowCss.shimCssText(style, CONTENT_ATTR, HOST_ATTR) : style; - } -} - -function getStylesVarName(component: CompileDirectiveMetadata|null): string { - let result = `styles`; - if (component) { - result += `_${identifierName(component.type)}`; - } - return result; -} diff --git a/packages/compiler/src/style_url_resolver.ts b/packages/compiler/src/style_url_resolver.ts index 8b1d2d3a70a..d982b393fdc 100644 --- a/packages/compiler/src/style_url_resolver.ts +++ b/packages/compiler/src/style_url_resolver.ts @@ -9,40 +9,10 @@ // Some of the code comes from WebComponents.JS // https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js -import {UrlResolver} from './url_resolver'; - -export class StyleWithImports { - constructor(public style: string, public styleUrls: string[]) {} -} - export function isStyleUrlResolvable(url: string): boolean { if (url == null || url.length === 0 || url[0] == '/') return false; const schemeMatch = url.match(URL_WITH_SCHEMA_REGEXP); return schemeMatch === null || schemeMatch[1] == 'package' || schemeMatch[1] == 'asset'; } -/** - * Rewrites stylesheets by resolving and removing the @import urls that - * are either relative or don't have a `package:` scheme - */ -export function extractStyleUrls( - resolver: UrlResolver, baseUrl: string, cssText: string): StyleWithImports { - const foundUrls: string[] = []; - - const modifiedCssText = cssText.replace(CSS_STRIPPABLE_COMMENT_REGEXP, '') - .replace(CSS_IMPORT_REGEXP, (...m: string[]) => { - const url = m[1] || m[2]; - if (!isStyleUrlResolvable(url)) { - // Do not attempt to resolve non-package absolute URLs with URI - // scheme - return m[0]; - } - foundUrls.push(resolver.resolve(baseUrl, url)); - return ''; - }); - return new StyleWithImports(modifiedCssText, foundUrls); -} - -const CSS_IMPORT_REGEXP = /@import\s+(?:url\()?\s*(?:(?:['"]([^'"]*))|([^;\)\s]*))[^;]*;?/g; -const CSS_STRIPPABLE_COMMENT_REGEXP = /\/\*(?!#\s*(?:sourceURL|sourceMappingURL)=)[\s\S]+?\*\//g; const URL_WITH_SCHEMA_REGEXP = /^([^:/?#]+):/; diff --git a/packages/compiler/src/summary_resolver.ts b/packages/compiler/src/summary_resolver.ts deleted file mode 100644 index 55e0ecd440c..00000000000 --- a/packages/compiler/src/summary_resolver.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {CompileTypeSummary} from './compile_metadata'; -import {Type} from './core'; - -export interface Summary { - symbol: T; - metadata: any; - type?: CompileTypeSummary; -} - -export abstract class SummaryResolver { - abstract isLibraryFile(fileName: string): boolean; - abstract toSummaryFileName(fileName: string, referringSrcFileName: string): string; - abstract fromSummaryFileName(fileName: string, referringLibFileName: string): string; - abstract resolveSummary(reference: T): Summary|null; - abstract getSymbolsOf(filePath: string): T[]|null; - abstract getImportAs(reference: T): T; - abstract getKnownModuleName(fileName: string): string|null; - abstract addSummary(summary: Summary): void; -} - -export class JitSummaryResolver implements SummaryResolver { - private _summaries = new Map>(); - - isLibraryFile(): boolean { - return false; - } - toSummaryFileName(fileName: string): string { - return fileName; - } - fromSummaryFileName(fileName: string): string { - return fileName; - } - resolveSummary(reference: Type): Summary|null { - return this._summaries.get(reference) || null; - } - getSymbolsOf(): Type[] { - return []; - } - getImportAs(reference: Type): Type { - return reference; - } - getKnownModuleName(fileName: string) { - return null; - } - addSummary(summary: Summary) { - this._summaries.set(summary.symbol, summary); - } -} diff --git a/packages/compiler/src/template_parser/binding_parser.ts b/packages/compiler/src/template_parser/binding_parser.ts index 8757e39d79d..64d1b762d37 100644 --- a/packages/compiler/src/template_parser/binding_parser.ts +++ b/packages/compiler/src/template_parser/binding_parser.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata'; import {SecurityContext} from '../core'; import {AbsoluteSourceSpan, ASTWithSource, BindingPipe, BindingType, BoundElementProperty, EmptyExpr, ParsedEvent, ParsedEventType, ParsedProperty, ParsedPropertyType, ParsedVariable, ParserError, RecursiveAstVisitor, TemplateBinding, VariableBinding} from '../expression_parser/ast'; import {Parser} from '../expression_parser/parser'; @@ -24,98 +23,82 @@ const STYLE_PREFIX = 'style'; const TEMPLATE_ATTR_PREFIX = '*'; const ANIMATE_PROP_PREFIX = 'animate-'; +export interface HostProperties { + [key: string]: string; +} + +export interface HostListeners { + [key: string]: string; +} + /** * Parses bindings in templates and in the directive host area. */ export class BindingParser { - pipesByName: Map|null = null; - - private _usedPipes: Map = new Map(); - constructor( private _exprParser: Parser, private _interpolationConfig: InterpolationConfig, - private _schemaRegistry: ElementSchemaRegistry, pipes: CompilePipeSummary[]|null, - public errors: ParseError[]) { - // When the `pipes` parameter is `null`, do not check for used pipes - // This is used in IVY when we might not know the available pipes at compile time - if (pipes) { - const pipesByName: Map = new Map(); - pipes.forEach(pipe => pipesByName.set(pipe.name, pipe)); - this.pipesByName = pipesByName; - } - } + private _schemaRegistry: ElementSchemaRegistry, public errors: ParseError[]) {} get interpolationConfig(): InterpolationConfig { return this._interpolationConfig; } - getUsedPipes(): CompilePipeSummary[] { - return Array.from(this._usedPipes.values()); - } - - createBoundHostProperties(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan): + createBoundHostProperties(properties: HostProperties, sourceSpan: ParseSourceSpan): ParsedProperty[]|null { - if (dirMeta.hostProperties) { - const boundProps: ParsedProperty[] = []; - Object.keys(dirMeta.hostProperties).forEach(propName => { - const expression = dirMeta.hostProperties[propName]; - if (typeof expression === 'string') { - this.parsePropertyBinding( - propName, expression, true, sourceSpan, sourceSpan.start.offset, undefined, [], - // Use the `sourceSpan` for `keySpan`. This isn't really accurate, but neither is the - // sourceSpan, as it represents the sourceSpan of the host itself rather than the - // source of the host binding (which doesn't exist in the template). Regardless, - // neither of these values are used in Ivy but are only here to satisfy the function - // signature. This should likely be refactored in the future so that `sourceSpan` - // isn't being used inaccurately. - boundProps, sourceSpan); - } else { - this._reportError( - `Value of the host property binding "${ - propName}" needs to be a string representing an expression but got "${ - expression}" (${typeof expression})`, - sourceSpan); - } - }); - return boundProps; + const boundProps: ParsedProperty[] = []; + for (const propName of Object.keys(properties)) { + const expression = properties[propName]; + if (typeof expression === 'string') { + this.parsePropertyBinding( + propName, expression, true, sourceSpan, sourceSpan.start.offset, undefined, [], + // Use the `sourceSpan` for `keySpan`. This isn't really accurate, but neither is the + // sourceSpan, as it represents the sourceSpan of the host itself rather than the + // source of the host binding (which doesn't exist in the template). Regardless, + // neither of these values are used in Ivy but are only here to satisfy the function + // signature. This should likely be refactored in the future so that `sourceSpan` + // isn't being used inaccurately. + boundProps, sourceSpan); + } else { + this._reportError( + `Value of the host property binding "${ + propName}" needs to be a string representing an expression but got "${ + expression}" (${typeof expression})`, + sourceSpan); + } } - return null; + return boundProps; } createDirectiveHostPropertyAsts( - dirMeta: CompileDirectiveSummary, elementSelector: string, + hostProperties: HostProperties, elementSelector: string, sourceSpan: ParseSourceSpan): BoundElementProperty[]|null { - const boundProps = this.createBoundHostProperties(dirMeta, sourceSpan); + const boundProps = this.createBoundHostProperties(hostProperties, sourceSpan); return boundProps && boundProps.map((prop) => this.createBoundElementProperty(elementSelector, prop)); } - createDirectiveHostEventAsts(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan): + createDirectiveHostEventAsts(hostListeners: HostListeners, sourceSpan: ParseSourceSpan): ParsedEvent[]|null { - if (dirMeta.hostListeners) { - const targetEvents: ParsedEvent[] = []; - Object.keys(dirMeta.hostListeners).forEach(propName => { - const expression = dirMeta.hostListeners[propName]; - if (typeof expression === 'string') { - // Use the `sourceSpan` for `keySpan` and `handlerSpan`. This isn't really accurate, but - // neither is the `sourceSpan`, as it represents the `sourceSpan` of the host itself - // rather than the source of the host binding (which doesn't exist in the template). - // Regardless, neither of these values are used in Ivy but are only here to satisfy the - // function signature. This should likely be refactored in the future so that `sourceSpan` - // isn't being used inaccurately. - this.parseEvent( - propName, expression, sourceSpan, sourceSpan, [], targetEvents, sourceSpan); - } else { - this._reportError( - `Value of the host listener "${ - propName}" needs to be a string representing an expression but got "${ - expression}" (${typeof expression})`, - sourceSpan); - } - }); - return targetEvents; + const targetEvents: ParsedEvent[] = []; + for (const propName of Object.keys(hostListeners)) { + const expression = hostListeners[propName]; + if (typeof expression === 'string') { + // Use the `sourceSpan` for `keySpan` and `handlerSpan`. This isn't really accurate, but + // neither is the `sourceSpan`, as it represents the `sourceSpan` of the host itself + // rather than the source of the host binding (which doesn't exist in the template). + // Regardless, neither of these values are used in Ivy but are only here to satisfy the + // function signature. This should likely be refactored in the future so that `sourceSpan` + // isn't being used inaccurately. + this.parseEvent(propName, expression, sourceSpan, sourceSpan, [], targetEvents, sourceSpan); + } else { + this._reportError( + `Value of the host listener "${ + propName}" needs to be a string representing an expression but got "${ + expression}" (${typeof expression})`, + sourceSpan); + } } - return null; + return targetEvents; } parseInterpolation(value: string, sourceSpan: ParseSourceSpan): ASTWithSource { @@ -126,7 +109,6 @@ export class BindingParser { const ast = this._exprParser.parseInterpolation( value, sourceInfo, absoluteOffset, this._interpolationConfig)!; if (ast) this._reportExpressionParserErrors(ast.errors, sourceSpan); - this._checkPipes(ast, sourceSpan); return ast; } catch (e) { this._reportError(`${e}`, sourceSpan); @@ -147,7 +129,6 @@ export class BindingParser { const ast = this._exprParser.parseInterpolationExpression(expression, sourceInfo, absoluteOffset); if (ast) this._reportExpressionParserErrors(ast.errors, sourceSpan); - this._checkPipes(ast, sourceSpan); return ast; } catch (e) { this._reportError(`${e}`, sourceSpan); @@ -223,11 +204,6 @@ export class BindingParser { const bindingsResult = this._exprParser.parseTemplateBindings( tplKey, tplValue, sourceInfo, absoluteKeyOffset, absoluteValueOffset); this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan); - bindingsResult.templateBindings.forEach((binding) => { - if (binding.value instanceof ASTWithSource) { - this._checkPipes(binding.value, sourceSpan); - } - }); bindingsResult.warnings.forEach((warning) => { this._reportError(warning, sourceSpan, ParseErrorLevel.WARNING); }); @@ -360,7 +336,6 @@ export class BindingParser { this._exprParser.parseBinding( value, sourceInfo, absoluteOffset, this._interpolationConfig); if (ast) this._reportExpressionParserErrors(ast.errors, sourceSpan); - this._checkPipes(ast, sourceSpan); return ast; } catch (e) { this._reportError(`${e}`, sourceSpan); @@ -512,7 +487,6 @@ export class BindingParser { this._reportError(`Empty expressions are not allowed`, sourceSpan); return this._exprParser.wrapLiteralPrimitive('ERROR', sourceInfo, absoluteOffset); } - this._checkPipes(ast, sourceSpan); return ast; } catch (e) { this._reportError(`${e}`, sourceSpan); @@ -532,25 +506,6 @@ export class BindingParser { } } - // Make sure all the used pipes are known in `this.pipesByName` - private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan): void { - if (ast && this.pipesByName) { - const collector = new PipeCollector(); - ast.visit(collector); - collector.pipes.forEach((ast, pipeName) => { - const pipeMeta = this.pipesByName!.get(pipeName); - if (!pipeMeta) { - this._reportError( - `The pipe '${pipeName}' could not be found`, - new ParseSourceSpan( - sourceSpan.start.moveBy(ast.span.start), sourceSpan.start.moveBy(ast.span.end))); - } else { - this._usedPipes.set(pipeName, pipeMeta); - } - }); - } - } - /** * @param propName the name of the property / attribute * @param sourceSpan diff --git a/packages/compiler/src/template_parser/template_ast.ts b/packages/compiler/src/template_parser/template_ast.ts deleted file mode 100644 index 323a70afa39..00000000000 --- a/packages/compiler/src/template_parser/template_ast.ts +++ /dev/null @@ -1,393 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {AstPath} from '../ast_path'; -import {CompileDirectiveSummary, CompileProviderMetadata, CompileTokenMetadata} from '../compile_metadata'; -import {SecurityContext} from '../core'; -import {ASTWithSource, BindingType, BoundElementProperty, ParsedEvent, ParsedEventType, ParsedVariable} from '../expression_parser/ast'; -import {LifecycleHooks} from '../lifecycle_reflector'; -import {ParseSourceSpan} from '../parse_util'; - - - -/** - * An Abstract Syntax Tree node representing part of a parsed Angular template. - */ -export interface TemplateAst { - /** - * The source span from which this node was parsed. - */ - sourceSpan: ParseSourceSpan; - - /** - * Visit this node and possibly transform it. - */ - visit(visitor: TemplateAstVisitor, context: any): any; -} - -/** - * A segment of text within the template. - */ -export class TextAst implements TemplateAst { - constructor( - public value: string, public ngContentIndex: number, public sourceSpan: ParseSourceSpan) {} - visit(visitor: TemplateAstVisitor, context: any): any { - return visitor.visitText(this, context); - } -} - -/** - * A bound expression within the text of a template. - */ -export class BoundTextAst implements TemplateAst { - constructor( - public value: ASTWithSource, public ngContentIndex: number, - public sourceSpan: ParseSourceSpan) {} - visit(visitor: TemplateAstVisitor, context: any): any { - return visitor.visitBoundText(this, context); - } -} - -/** - * A plain attribute on an element. - */ -export class AttrAst implements TemplateAst { - constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {} - visit(visitor: TemplateAstVisitor, context: any): any { - return visitor.visitAttr(this, context); - } -} - -export const enum PropertyBindingType { - // A normal binding to a property (e.g. `[property]="expression"`). - Property, - // A binding to an element attribute (e.g. `[attr.name]="expression"`). - Attribute, - // A binding to a CSS class (e.g. `[class.name]="condition"`). - Class, - // A binding to a style rule (e.g. `[style.rule]="expression"`). - Style, - // A binding to an animation reference (e.g. `[animate.key]="expression"`). - Animation, -} - -const BoundPropertyMapping = { - [BindingType.Animation]: PropertyBindingType.Animation, - [BindingType.Attribute]: PropertyBindingType.Attribute, - [BindingType.Class]: PropertyBindingType.Class, - [BindingType.Property]: PropertyBindingType.Property, - [BindingType.Style]: PropertyBindingType.Style, -}; - -/** - * A binding for an element property (e.g. `[property]="expression"`) or an animation trigger (e.g. - * `[@trigger]="stateExp"`) - */ -export class BoundElementPropertyAst implements TemplateAst { - readonly isAnimation: boolean; - - constructor( - public name: string, public type: PropertyBindingType, - public securityContext: SecurityContext, public value: ASTWithSource, - public unit: string|null, public sourceSpan: ParseSourceSpan) { - this.isAnimation = this.type === PropertyBindingType.Animation; - } - - static fromBoundProperty(prop: BoundElementProperty) { - const type = BoundPropertyMapping[prop.type]; - return new BoundElementPropertyAst( - prop.name, type, prop.securityContext, prop.value, prop.unit, prop.sourceSpan); - } - - visit(visitor: TemplateAstVisitor, context: any): any { - return visitor.visitElementProperty(this, context); - } -} - -/** - * A binding for an element event (e.g. `(event)="handler()"`) or an animation trigger event (e.g. - * `(@trigger.phase)="callback($event)"`). - */ -export class BoundEventAst implements TemplateAst { - readonly fullName: string; - readonly isAnimation: boolean; - - constructor( - public name: string, public target: string|null, public phase: string|null, - public handler: ASTWithSource, public sourceSpan: ParseSourceSpan, - public handlerSpan: ParseSourceSpan) { - this.fullName = BoundEventAst.calcFullName(this.name, this.target, this.phase); - this.isAnimation = !!this.phase; - } - - static calcFullName(name: string, target: string|null, phase: string|null): string { - if (target) { - return `${target}:${name}`; - } - if (phase) { - return `@${name}.${phase}`; - } - - return name; - } - - static fromParsedEvent(event: ParsedEvent) { - const target: string|null = event.type === ParsedEventType.Regular ? event.targetOrPhase : null; - const phase: string|null = - event.type === ParsedEventType.Animation ? event.targetOrPhase : null; - return new BoundEventAst( - event.name, target, phase, event.handler, event.sourceSpan, event.handlerSpan); - } - - visit(visitor: TemplateAstVisitor, context: any): any { - return visitor.visitEvent(this, context); - } -} - -/** - * A reference declaration on an element (e.g. `let someName="expression"`). - */ -export class ReferenceAst implements TemplateAst { - constructor( - public name: string, public value: CompileTokenMetadata, public originalValue: string, - public sourceSpan: ParseSourceSpan) {} - visit(visitor: TemplateAstVisitor, context: any): any { - return visitor.visitReference(this, context); - } -} - -/** - * A variable declaration on a (e.g. `var-someName="someLocalName"`). - */ -export class VariableAst implements TemplateAst { - constructor( - public readonly name: string, public readonly value: string, - public readonly sourceSpan: ParseSourceSpan, public readonly valueSpan?: ParseSourceSpan) {} - - static fromParsedVariable(v: ParsedVariable) { - return new VariableAst(v.name, v.value, v.sourceSpan, v.valueSpan); - } - - visit(visitor: TemplateAstVisitor, context: any): any { - return visitor.visitVariable(this, context); - } -} - -/** - * An element declaration in a template. - */ -export class ElementAst implements TemplateAst { - constructor( - public name: string, public attrs: AttrAst[], public inputs: BoundElementPropertyAst[], - public outputs: BoundEventAst[], public references: ReferenceAst[], - public directives: DirectiveAst[], public providers: ProviderAst[], - public hasViewContainer: boolean, public queryMatches: QueryMatch[], - public children: TemplateAst[], public ngContentIndex: number|null, - public sourceSpan: ParseSourceSpan, public endSourceSpan: ParseSourceSpan|null) {} - - visit(visitor: TemplateAstVisitor, context: any): any { - return visitor.visitElement(this, context); - } -} - -/** - * A `` element included in an Angular template. - */ -export class EmbeddedTemplateAst implements TemplateAst { - constructor( - public attrs: AttrAst[], public outputs: BoundEventAst[], public references: ReferenceAst[], - public variables: VariableAst[], public directives: DirectiveAst[], - public providers: ProviderAst[], public hasViewContainer: boolean, - public queryMatches: QueryMatch[], public children: TemplateAst[], - public ngContentIndex: number, public sourceSpan: ParseSourceSpan) {} - - visit(visitor: TemplateAstVisitor, context: any): any { - return visitor.visitEmbeddedTemplate(this, context); - } -} - -/** - * A directive property with a bound value (e.g. `*ngIf="condition"). - */ -export class BoundDirectivePropertyAst implements TemplateAst { - constructor( - public directiveName: string, public templateName: string, public value: ASTWithSource, - public sourceSpan: ParseSourceSpan) {} - visit(visitor: TemplateAstVisitor, context: any): any { - return visitor.visitDirectiveProperty(this, context); - } -} - -/** - * A directive declared on an element. - */ -export class DirectiveAst implements TemplateAst { - constructor( - public directive: CompileDirectiveSummary, public inputs: BoundDirectivePropertyAst[], - public hostProperties: BoundElementPropertyAst[], public hostEvents: BoundEventAst[], - public contentQueryStartId: number, public sourceSpan: ParseSourceSpan) {} - visit(visitor: TemplateAstVisitor, context: any): any { - return visitor.visitDirective(this, context); - } -} - -/** - * A provider declared on an element - */ -export class ProviderAst implements TemplateAst { - constructor( - public token: CompileTokenMetadata, public multiProvider: boolean, public eager: boolean, - public providers: CompileProviderMetadata[], public providerType: ProviderAstType, - public lifecycleHooks: LifecycleHooks[], public sourceSpan: ParseSourceSpan, - readonly isModule: boolean) {} - - visit(visitor: TemplateAstVisitor, context: any): any { - // No visit method in the visitor for now... - return null; - } -} - -export enum ProviderAstType { - PublicService, - PrivateService, - Component, - Directive, - Builtin -} - -/** - * Position where content is to be projected (instance of `` in a template). - */ -export class NgContentAst implements TemplateAst { - constructor( - public index: number, public ngContentIndex: number, public sourceSpan: ParseSourceSpan) {} - visit(visitor: TemplateAstVisitor, context: any): any { - return visitor.visitNgContent(this, context); - } -} - -export interface QueryMatch { - queryId: number; - value: CompileTokenMetadata; -} - -/** - * A visitor for {@link TemplateAst} trees that will process each node. - */ -export interface TemplateAstVisitor { - // Returning a truthy value from `visit()` will prevent `templateVisitAll()` from the call to - // the typed method and result returned will become the result included in `visitAll()`s - // result array. - visit?(ast: TemplateAst, context: any): any; - - visitNgContent(ast: NgContentAst, context: any): any; - visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any; - visitElement(ast: ElementAst, context: any): any; - visitReference(ast: ReferenceAst, context: any): any; - visitVariable(ast: VariableAst, context: any): any; - visitEvent(ast: BoundEventAst, context: any): any; - visitElementProperty(ast: BoundElementPropertyAst, context: any): any; - visitAttr(ast: AttrAst, context: any): any; - visitBoundText(ast: BoundTextAst, context: any): any; - visitText(ast: TextAst, context: any): any; - visitDirective(ast: DirectiveAst, context: any): any; - visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any; -} - -/** - * A visitor that accepts each node but doesn't do anything. It is intended to be used - * as the base class for a visitor that is only interested in a subset of the node types. - */ -export class NullTemplateVisitor implements TemplateAstVisitor { - visitNgContent(ast: NgContentAst, context: any): void {} - visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): void {} - visitElement(ast: ElementAst, context: any): void {} - visitReference(ast: ReferenceAst, context: any): void {} - visitVariable(ast: VariableAst, context: any): void {} - visitEvent(ast: BoundEventAst, context: any): void {} - visitElementProperty(ast: BoundElementPropertyAst, context: any): void {} - visitAttr(ast: AttrAst, context: any): void {} - visitBoundText(ast: BoundTextAst, context: any): void {} - visitText(ast: TextAst, context: any): void {} - visitDirective(ast: DirectiveAst, context: any): void {} - visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): void {} -} - -/** - * Base class that can be used to build a visitor that visits each node - * in an template ast recursively. - */ -export class RecursiveTemplateAstVisitor extends NullTemplateVisitor implements TemplateAstVisitor { - constructor() { - super(); - } - - // Nodes with children - override visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: any): any { - return this.visitChildren(context, visit => { - visit(ast.attrs); - visit(ast.references); - visit(ast.variables); - visit(ast.directives); - visit(ast.providers); - visit(ast.children); - }); - } - - override visitElement(ast: ElementAst, context: any): any { - return this.visitChildren(context, visit => { - visit(ast.attrs); - visit(ast.inputs); - visit(ast.outputs); - visit(ast.references); - visit(ast.directives); - visit(ast.providers); - visit(ast.children); - }); - } - - override visitDirective(ast: DirectiveAst, context: any): any { - return this.visitChildren(context, visit => { - visit(ast.inputs); - visit(ast.hostProperties); - visit(ast.hostEvents); - }); - } - - protected visitChildren( - context: any, - cb: (visit: ((children: V[]|undefined) => void)) => void) { - let results: any[][] = []; - let t = this; - function visit(children: T[]|undefined) { - if (children && children.length) results.push(templateVisitAll(t, children, context)); - } - cb(visit); - return Array.prototype.concat.apply([], results); - } -} - -/** - * Visit every node in a list of {@link TemplateAst}s with the given {@link TemplateAstVisitor}. - */ -export function templateVisitAll( - visitor: TemplateAstVisitor, asts: TemplateAst[], context: any = null): any[] { - const result: any[] = []; - const visit = visitor.visit ? - (ast: TemplateAst) => visitor.visit!(ast, context) || ast.visit(visitor, context) : - (ast: TemplateAst) => ast.visit(visitor, context); - asts.forEach(ast => { - const astResult = visit(ast); - if (astResult) { - result.push(astResult); - } - }); - return result; -} - -export type TemplateAstPath = AstPath; diff --git a/packages/compiler/src/template_parser/template_parser.ts b/packages/compiler/src/template_parser/template_parser.ts deleted file mode 100644 index d680c90def5..00000000000 --- a/packages/compiler/src/template_parser/template_parser.ts +++ /dev/null @@ -1,939 +0,0 @@ -/** - * @license - * Copyright Google LLC 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 {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileTokenMetadata, CompileTypeMetadata} from '../compile_metadata'; -import {CompileReflector} from '../compile_reflector'; -import {CompilerConfig} from '../config'; -import {SchemaMetadata} from '../core'; -import {AST, ASTWithSource, EmptyExpr, ParsedEvent, ParsedProperty, ParsedVariable} from '../expression_parser/ast'; -import {Parser} from '../expression_parser/parser'; -import {createTokenForExternalReference, createTokenForReference, Identifiers} from '../identifiers'; -import * as html from '../ml_parser/ast'; -import {HtmlParser} from '../ml_parser/html_parser'; -import {removeWhitespaces, replaceNgsp} from '../ml_parser/html_whitespaces'; -import {expandNodes} from '../ml_parser/icu_ast_expander'; -import {InterpolationConfig} from '../ml_parser/interpolation_config'; -import {ParseTreeResult} from '../ml_parser/parser'; -import {isNgTemplate, splitNsName} from '../ml_parser/tags'; -import {identifierName, ParseError, ParseErrorLevel, ParseSourceSpan, syntaxError} from '../parse_util'; -import {ProviderElementContext, ProviderViewContext} from '../provider_analyzer'; -import {ElementSchemaRegistry} from '../schema/element_schema_registry'; -import {CssSelector, SelectorMatcher} from '../selector'; -import {isStyleUrlResolvable} from '../style_url_resolver'; -import {Console, newArray} from '../util'; - -import {BindingParser} from './binding_parser'; -import * as t from './template_ast'; -import {PreparsedElementType, preparseElement} from './template_preparser'; - -const BIND_NAME_REGEXP = - /^(?:(?:(?:(bind-)|(let-)|(ref-|#)|(on-)|(bindon-)|(@))(.*))|\[\(([^\)]+)\)\]|\[([^\]]+)\]|\(([^\)]+)\))$/; - -// Group 1 = "bind-" -const KW_BIND_IDX = 1; -// Group 2 = "let-" -const KW_LET_IDX = 2; -// Group 3 = "ref-/#" -const KW_REF_IDX = 3; -// Group 4 = "on-" -const KW_ON_IDX = 4; -// Group 5 = "bindon-" -const KW_BINDON_IDX = 5; -// Group 6 = "@" -const KW_AT_IDX = 6; -// Group 7 = the identifier after "bind-", "let-", "ref-/#", "on-", "bindon-" or "@" -const IDENT_KW_IDX = 7; -// Group 8 = identifier inside [()] -const IDENT_BANANA_BOX_IDX = 8; -// Group 9 = identifier inside [] -const IDENT_PROPERTY_IDX = 9; -// Group 10 = identifier inside () -const IDENT_EVENT_IDX = 10; - -const TEMPLATE_ATTR_PREFIX = '*'; -const CLASS_ATTR = 'class'; - -let _TEXT_CSS_SELECTOR!: CssSelector; -function TEXT_CSS_SELECTOR(): CssSelector { - if (!_TEXT_CSS_SELECTOR) { - _TEXT_CSS_SELECTOR = CssSelector.parse('*')[0]; - } - return _TEXT_CSS_SELECTOR; -} - -export class TemplateParseError extends ParseError { - constructor(message: string, span: ParseSourceSpan, level: ParseErrorLevel) { - super(span, message, level); - } -} - -export class TemplateParseResult { - constructor( - public templateAst?: t.TemplateAst[], public usedPipes?: CompilePipeSummary[], - public errors?: ParseError[]) {} -} - -export class TemplateParser { - constructor( - private _config: CompilerConfig, private _reflector: CompileReflector, - private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry, - private _htmlParser: HtmlParser, private _console: Console|null, - public transforms: t.TemplateAstVisitor[]) {} - - public get expressionParser() { - return this._exprParser; - } - - parse( - component: CompileDirectiveMetadata, template: string|ParseTreeResult, - directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[], schemas: SchemaMetadata[], - templateUrl: string, - preserveWhitespaces: boolean): {template: t.TemplateAst[], pipes: CompilePipeSummary[]} { - const result = this.tryParse( - component, template, directives, pipes, schemas, templateUrl, preserveWhitespaces); - const warnings = result.errors!.filter(error => error.level === ParseErrorLevel.WARNING); - - const errors = result.errors!.filter(error => error.level === ParseErrorLevel.ERROR); - - if (warnings.length > 0) { - this._console?.warn(`Template parse warnings:\n${warnings.join('\n')}`); - } - - if (errors.length > 0) { - const errorString = errors.join('\n'); - throw syntaxError(`Template parse errors:\n${errorString}`, errors); - } - - return {template: result.templateAst!, pipes: result.usedPipes!}; - } - - tryParse( - component: CompileDirectiveMetadata, template: string|ParseTreeResult, - directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[], schemas: SchemaMetadata[], - templateUrl: string, preserveWhitespaces: boolean): TemplateParseResult { - let htmlParseResult = typeof template === 'string' ? - this._htmlParser!.parse(template, templateUrl, { - tokenizeExpansionForms: true, - interpolationConfig: this.getInterpolationConfig(component) - }) : - template; - - if (!preserveWhitespaces) { - htmlParseResult = removeWhitespaces(htmlParseResult); - } - - return this.tryParseHtml( - this.expandHtml(htmlParseResult), component, directives, pipes, schemas); - } - - tryParseHtml( - htmlAstWithErrors: ParseTreeResult, component: CompileDirectiveMetadata, - directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[], - schemas: SchemaMetadata[]): TemplateParseResult { - let result: t.TemplateAst[]; - const errors = htmlAstWithErrors.errors; - const usedPipes: CompilePipeSummary[] = []; - if (htmlAstWithErrors.rootNodes.length > 0) { - const uniqDirectives = removeSummaryDuplicates(directives); - const uniqPipes = removeSummaryDuplicates(pipes); - const providerViewContext = new ProviderViewContext(this._reflector, component); - let interpolationConfig: InterpolationConfig = undefined!; - if (component.template && component.template.interpolation) { - interpolationConfig = { - start: component.template.interpolation[0], - end: component.template.interpolation[1] - }; - } - const bindingParser = new BindingParser( - this._exprParser, interpolationConfig!, this._schemaRegistry, uniqPipes, errors); - const parseVisitor = new TemplateParseVisitor( - this._reflector, this._config, providerViewContext, uniqDirectives, bindingParser, - this._schemaRegistry, schemas, errors); - result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT); - errors.push(...providerViewContext.errors); - usedPipes.push(...bindingParser.getUsedPipes()); - } else { - result = []; - } - this._assertNoReferenceDuplicationOnTemplate(result, errors); - - if (errors.length > 0) { - return new TemplateParseResult(result, usedPipes, errors); - } - - if (this.transforms) { - this.transforms.forEach((transform: t.TemplateAstVisitor) => { - result = t.templateVisitAll(transform, result); - }); - } - - return new TemplateParseResult(result, usedPipes, errors); - } - - expandHtml(htmlAstWithErrors: ParseTreeResult, forced: boolean = false): ParseTreeResult { - const errors: ParseError[] = htmlAstWithErrors.errors; - - if (errors.length == 0 || forced) { - // Transform ICU messages to angular directives - const expandedHtmlAst = expandNodes(htmlAstWithErrors.rootNodes); - errors.push(...expandedHtmlAst.errors); - htmlAstWithErrors = new ParseTreeResult(expandedHtmlAst.nodes, errors); - } - return htmlAstWithErrors; - } - - getInterpolationConfig(component: CompileDirectiveMetadata): InterpolationConfig|undefined { - if (component.template) { - return InterpolationConfig.fromArray(component.template.interpolation); - } - return undefined; - } - - /** @internal */ - _assertNoReferenceDuplicationOnTemplate(result: t.TemplateAst[], errors: TemplateParseError[]): - void { - const existingReferences: string[] = []; - - result.filter(element => !!(element).references) - .forEach(element => (element).references.forEach((reference: t.ReferenceAst) => { - const name = reference.name; - if (existingReferences.indexOf(name) < 0) { - existingReferences.push(name); - } else { - const error = new TemplateParseError( - `Reference "#${name}" is defined several times`, reference.sourceSpan, - ParseErrorLevel.ERROR); - errors.push(error); - } - })); - } -} - -class TemplateParseVisitor implements html.Visitor { - selectorMatcher = new SelectorMatcher(); - directivesIndex = new Map(); - ngContentCount = 0; - contentQueryStartId: number; - - constructor( - private reflector: CompileReflector, private config: CompilerConfig, - public providerViewContext: ProviderViewContext, directives: CompileDirectiveSummary[], - private _bindingParser: BindingParser, private _schemaRegistry: ElementSchemaRegistry, - private _schemas: SchemaMetadata[], private _targetErrors: TemplateParseError[]) { - // Note: queries start with id 1 so we can use the number in a Bloom filter! - this.contentQueryStartId = providerViewContext.component.viewQueries.length + 1; - directives.forEach((directive, index) => { - const selector = CssSelector.parse(directive.selector!); - this.selectorMatcher.addSelectables(selector, directive); - this.directivesIndex.set(directive, index); - }); - } - - visitExpansion(expansion: html.Expansion, context: any): any { - return null; - } - - visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any { - return null; - } - - visitText(text: html.Text, parent: ElementContext): any { - const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR())!; - const valueNoNgsp = replaceNgsp(text.value); - const expr = this._bindingParser.parseInterpolation(valueNoNgsp, text.sourceSpan); - return expr ? new t.BoundTextAst(expr, ngContentIndex, text.sourceSpan) : - new t.TextAst(valueNoNgsp, ngContentIndex, text.sourceSpan); - } - - visitAttribute(attribute: html.Attribute, context: any): any { - return new t.AttrAst(attribute.name, attribute.value, attribute.sourceSpan); - } - - visitComment(comment: html.Comment, context: any): any { - return null; - } - - visitElement(element: html.Element, parent: ElementContext): any { - const queryStartIndex = this.contentQueryStartId; - const elName = element.name; - const preparsedElement = preparseElement(element); - if (preparsedElement.type === PreparsedElementType.SCRIPT || - preparsedElement.type === PreparsedElementType.STYLE) { - // Skipping