Merge remote-tracking branch 'en/master' into aio
This commit is contained in:
commit
9a43523368
@ -71,6 +71,7 @@ def _ngc_tsconfig(ctx, files, srcs, **kwargs):
|
|||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
"generateCodeForLibraries": False,
|
"generateCodeForLibraries": False,
|
||||||
"allowEmptyCodegenFiles": True,
|
"allowEmptyCodegenFiles": True,
|
||||||
|
"enableSummariesforJit": True,
|
||||||
# FIXME: wrong place to de-dupe
|
# FIXME: wrong place to de-dupe
|
||||||
"expectedOut": depset([o.path for o in expected_outs]).to_list()
|
"expectedOut": depset([o.path for o in expected_outs]).to_list()
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
// For TypeScript 1.8, we have to lay out generated files
|
// For TypeScript 1.8, we have to lay out generated files
|
||||||
// in the same source directory with your code.
|
// in the same source directory with your code.
|
||||||
"genDir": ".",
|
"genDir": ".",
|
||||||
"debug": true
|
"debug": true,
|
||||||
|
"enableSummariesForJit": true
|
||||||
},
|
},
|
||||||
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
@ -18,7 +18,7 @@ import * as api from './transformers/api';
|
|||||||
import * as ngc from './transformers/entry_points';
|
import * as ngc from './transformers/entry_points';
|
||||||
import {GENERATED_FILES} from './transformers/util';
|
import {GENERATED_FILES} from './transformers/util';
|
||||||
|
|
||||||
import {exitCodeFromResult, performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration, PerformCompilationResult} from './perform_compile';
|
import {exitCodeFromResult, performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration, PerformCompilationResult, filterErrorsAndWarnings} from './perform_compile';
|
||||||
import {performWatchCompilation, createPerformWatchHost} from './perform_watch';
|
import {performWatchCompilation, createPerformWatchHost} from './perform_watch';
|
||||||
import {isSyntaxError} from '@angular/compiler';
|
import {isSyntaxError} from '@angular/compiler';
|
||||||
|
|
||||||
@ -130,8 +130,9 @@ export function readCommandLineAndConfiguration(
|
|||||||
function reportErrorsAndExit(
|
function reportErrorsAndExit(
|
||||||
options: api.CompilerOptions, allDiagnostics: Diagnostics,
|
options: api.CompilerOptions, allDiagnostics: Diagnostics,
|
||||||
consoleError: (s: string) => void = console.error): number {
|
consoleError: (s: string) => void = console.error): number {
|
||||||
if (allDiagnostics.length) {
|
const errorsAndWarnings = filterErrorsAndWarnings(allDiagnostics);
|
||||||
consoleError(formatDiagnostics(options, allDiagnostics));
|
if (errorsAndWarnings.length) {
|
||||||
|
consoleError(formatDiagnostics(options, errorsAndWarnings));
|
||||||
}
|
}
|
||||||
return exitCodeFromResult(allDiagnostics);
|
return exitCodeFromResult(allDiagnostics);
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,11 @@ import {ParseSourceSpan} from '@angular/compiler';
|
|||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {formatDiagnostics as formatDiagnosticsOrig} from './perform_compile';
|
import {formatDiagnostics as formatDiagnosticsOrig} from './perform_compile';
|
||||||
|
import {Program as ProgramOrig} from './transformers/api';
|
||||||
import {createCompilerHost as createCompilerOrig} from './transformers/compiler_host';
|
import {createCompilerHost as createCompilerOrig} from './transformers/compiler_host';
|
||||||
import {createProgram as createProgramOrig} from './transformers/program';
|
import {createProgram as createProgramOrig} from './transformers/program';
|
||||||
|
|
||||||
|
|
||||||
// Interfaces from ./transformers/api;
|
// Interfaces from ./transformers/api;
|
||||||
export interface Diagnostic {
|
export interface Diagnostic {
|
||||||
messageText: string;
|
messageText: string;
|
||||||
@ -92,12 +94,6 @@ export interface TsEmitArguments {
|
|||||||
|
|
||||||
export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; }
|
export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; }
|
||||||
|
|
||||||
export interface LibrarySummary {
|
|
||||||
fileName: string;
|
|
||||||
text: string;
|
|
||||||
sourceFile?: ts.SourceFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Program {
|
export interface Program {
|
||||||
getTsProgram(): ts.Program;
|
getTsProgram(): ts.Program;
|
||||||
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken): ts.Diagnostic[];
|
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken): ts.Diagnostic[];
|
||||||
@ -116,7 +112,6 @@ export interface Program {
|
|||||||
customTransformers?: CustomTransformers,
|
customTransformers?: CustomTransformers,
|
||||||
emitCallback?: TsEmitCallback
|
emitCallback?: TsEmitCallback
|
||||||
}): ts.EmitResult;
|
}): ts.EmitResult;
|
||||||
getLibrarySummaries(): LibrarySummary[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper for createProgram.
|
// Wrapper for createProgram.
|
||||||
@ -124,7 +119,7 @@ export function createProgram(
|
|||||||
{rootNames, options, host, oldProgram}:
|
{rootNames, options, host, oldProgram}:
|
||||||
{rootNames: string[], options: CompilerOptions, host: CompilerHost, oldProgram?: Program}):
|
{rootNames: string[], options: CompilerOptions, host: CompilerHost, oldProgram?: Program}):
|
||||||
Program {
|
Program {
|
||||||
return createProgramOrig({rootNames, options, host, oldProgram});
|
return createProgramOrig({rootNames, options, host, oldProgram: oldProgram as ProgramOrig});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper for createCompilerHost.
|
// Wrapper for createCompilerHost.
|
||||||
|
@ -13,11 +13,16 @@ import * as ts from 'typescript';
|
|||||||
|
|
||||||
import * as api from './transformers/api';
|
import * as api from './transformers/api';
|
||||||
import * as ng from './transformers/entry_points';
|
import * as ng from './transformers/entry_points';
|
||||||
|
import {createMessageDiagnostic} from './transformers/util';
|
||||||
|
|
||||||
const TS_EXT = /\.ts$/;
|
const TS_EXT = /\.ts$/;
|
||||||
|
|
||||||
export type Diagnostics = Array<ts.Diagnostic|api.Diagnostic>;
|
export type Diagnostics = Array<ts.Diagnostic|api.Diagnostic>;
|
||||||
|
|
||||||
|
export function filterErrorsAndWarnings(diagnostics: Diagnostics): Diagnostics {
|
||||||
|
return diagnostics.filter(d => d.category !== ts.DiagnosticCategory.Message);
|
||||||
|
}
|
||||||
|
|
||||||
export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnostics): string {
|
export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnostics): string {
|
||||||
if (diags && diags.length) {
|
if (diags && diags.length) {
|
||||||
const tsFormatHost: ts.FormatDiagnosticsHost = {
|
const tsFormatHost: ts.FormatDiagnosticsHost = {
|
||||||
@ -123,7 +128,7 @@ export interface PerformCompilationResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function exitCodeFromResult(diags: Diagnostics | undefined): number {
|
export function exitCodeFromResult(diags: Diagnostics | undefined): number {
|
||||||
if (!diags || diags.length === 0) {
|
if (!diags || filterErrorsAndWarnings(diags).length === 0) {
|
||||||
// If we have a result and didn't get any errors, we succeeded.
|
// If we have a result and didn't get any errors, we succeeded.
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -154,7 +159,13 @@ export function performCompilation({rootNames, options, host, oldProgram, emitCa
|
|||||||
|
|
||||||
program = ng.createProgram({rootNames, host, options, oldProgram});
|
program = ng.createProgram({rootNames, host, options, oldProgram});
|
||||||
|
|
||||||
|
const beforeDiags = Date.now();
|
||||||
allDiagnostics.push(...gatherDiagnostics(program !));
|
allDiagnostics.push(...gatherDiagnostics(program !));
|
||||||
|
if (options.diagnostics) {
|
||||||
|
const afterDiags = Date.now();
|
||||||
|
allDiagnostics.push(
|
||||||
|
createMessageDiagnostic(`Time for diagnostics: ${afterDiags - beforeDiags}ms.`));
|
||||||
|
}
|
||||||
|
|
||||||
if (!hasErrors(allDiagnostics)) {
|
if (!hasErrors(allDiagnostics)) {
|
||||||
emitResult = program !.emit({emitCallback, customTransformers, emitFlags});
|
emitResult = program !.emit({emitCallback, customTransformers, emitFlags});
|
||||||
|
@ -13,27 +13,7 @@ import * as ts from 'typescript';
|
|||||||
import {Diagnostics, ParsedConfiguration, PerformCompilationResult, exitCodeFromResult, performCompilation, readConfiguration} from './perform_compile';
|
import {Diagnostics, ParsedConfiguration, PerformCompilationResult, exitCodeFromResult, performCompilation, readConfiguration} from './perform_compile';
|
||||||
import * as api from './transformers/api';
|
import * as api from './transformers/api';
|
||||||
import {createCompilerHost} from './transformers/entry_points';
|
import {createCompilerHost} from './transformers/entry_points';
|
||||||
|
import {createMessageDiagnostic} from './transformers/util';
|
||||||
const ChangeDiagnostics = {
|
|
||||||
Compilation_complete_Watching_for_file_changes: {
|
|
||||||
category: ts.DiagnosticCategory.Message,
|
|
||||||
messageText: 'Compilation complete. Watching for file changes.',
|
|
||||||
code: api.DEFAULT_ERROR_CODE,
|
|
||||||
source: api.SOURCE
|
|
||||||
},
|
|
||||||
Compilation_failed_Watching_for_file_changes: {
|
|
||||||
category: ts.DiagnosticCategory.Message,
|
|
||||||
messageText: 'Compilation failed. Watching for file changes.',
|
|
||||||
code: api.DEFAULT_ERROR_CODE,
|
|
||||||
source: api.SOURCE
|
|
||||||
},
|
|
||||||
File_change_detected_Starting_incremental_compilation: {
|
|
||||||
category: ts.DiagnosticCategory.Message,
|
|
||||||
messageText: 'File change detected. Starting incremental compilation.',
|
|
||||||
code: api.DEFAULT_ERROR_CODE,
|
|
||||||
source: api.SOURCE
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function totalCompilationTimeDiagnostic(timeInMillis: number): api.Diagnostic {
|
function totalCompilationTimeDiagnostic(timeInMillis: number): api.Diagnostic {
|
||||||
let duration: string;
|
let duration: string;
|
||||||
@ -231,9 +211,11 @@ export function performWatchCompilation(host: PerformWatchHost):
|
|||||||
const exitCode = exitCodeFromResult(compileResult.diagnostics);
|
const exitCode = exitCodeFromResult(compileResult.diagnostics);
|
||||||
if (exitCode == 0) {
|
if (exitCode == 0) {
|
||||||
cachedProgram = compileResult.program;
|
cachedProgram = compileResult.program;
|
||||||
host.reportDiagnostics([ChangeDiagnostics.Compilation_complete_Watching_for_file_changes]);
|
host.reportDiagnostics(
|
||||||
|
[createMessageDiagnostic('Compilation complete. Watching for file changes.')]);
|
||||||
} else {
|
} else {
|
||||||
host.reportDiagnostics([ChangeDiagnostics.Compilation_failed_Watching_for_file_changes]);
|
host.reportDiagnostics(
|
||||||
|
[createMessageDiagnostic('Compilation failed. Watching for file changes.')]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return compileResult.diagnostics;
|
return compileResult.diagnostics;
|
||||||
@ -285,7 +267,7 @@ export function performWatchCompilation(host: PerformWatchHost):
|
|||||||
function recompile() {
|
function recompile() {
|
||||||
timerHandleForRecompilation = undefined;
|
timerHandleForRecompilation = undefined;
|
||||||
host.reportDiagnostics(
|
host.reportDiagnostics(
|
||||||
[ChangeDiagnostics.File_change_detected_Starting_incremental_compilation]);
|
[createMessageDiagnostic('File change detected. Starting incremental compilation.')]);
|
||||||
doCompilation();
|
doCompilation();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ParseSourceSpan} from '@angular/compiler';
|
import {GeneratedFile, ParseSourceSpan} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
export const DEFAULT_ERROR_CODE = 100;
|
export const DEFAULT_ERROR_CODE = 100;
|
||||||
@ -144,6 +144,12 @@ export interface CompilerOptions extends ts.CompilerOptions {
|
|||||||
|
|
||||||
/** generate all possible generated files */
|
/** generate all possible generated files */
|
||||||
allowEmptyCodegenFiles?: boolean;
|
allowEmptyCodegenFiles?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to generate .ngsummary.ts files that allow to use AOTed artifacts
|
||||||
|
* in JIT mode. This is off by default.
|
||||||
|
*/
|
||||||
|
enableSummariesForJit?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompilerHost extends ts.CompilerHost {
|
export interface CompilerHost extends ts.CompilerHost {
|
||||||
@ -214,6 +220,9 @@ export interface TsEmitArguments {
|
|||||||
|
|
||||||
export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; }
|
export interface TsEmitCallback { (args: TsEmitArguments): ts.EmitResult; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
export interface LibrarySummary {
|
export interface LibrarySummary {
|
||||||
fileName: string;
|
fileName: string;
|
||||||
text: string;
|
text: string;
|
||||||
@ -300,6 +309,13 @@ export interface Program {
|
|||||||
* Returns the .d.ts / .ngsummary.json / .ngfactory.d.ts files of libraries that have been emitted
|
* 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
|
* in this program or previous programs with paths that emulate the fact that these libraries
|
||||||
* have been compiled before with no outDir.
|
* have been compiled before with no outDir.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
*/
|
*/
|
||||||
getLibrarySummaries(): LibrarySummary[];
|
getLibrarySummaries(): Map<string, LibrarySummary>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
getEmittedGeneratedFiles(): Map<string, GeneratedFile>;
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,6 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
|||||||
private generatedSourceFiles = new Map<string, GenSourceFile>();
|
private generatedSourceFiles = new Map<string, GenSourceFile>();
|
||||||
private generatedCodeFor = new Map<string, string[]>();
|
private generatedCodeFor = new Map<string, string[]>();
|
||||||
private emitter = new TypeScriptEmitter();
|
private emitter = new TypeScriptEmitter();
|
||||||
private librarySummaries = new Map<string, LibrarySummary>();
|
|
||||||
getCancellationToken: () => ts.CancellationToken;
|
getCancellationToken: () => ts.CancellationToken;
|
||||||
getDefaultLibLocation: () => string;
|
getDefaultLibLocation: () => string;
|
||||||
trace: (s: string) => void;
|
trace: (s: string) => void;
|
||||||
@ -68,9 +67,8 @@ export class TsCompilerAotCompilerTypeCheckHostAdapter extends
|
|||||||
constructor(
|
constructor(
|
||||||
private rootFiles: string[], options: CompilerOptions, context: CompilerHost,
|
private rootFiles: string[], options: CompilerOptions, context: CompilerHost,
|
||||||
private metadataProvider: MetadataProvider, private codeGenerator: CodeGenerator,
|
private metadataProvider: MetadataProvider, private codeGenerator: CodeGenerator,
|
||||||
librarySummaries: LibrarySummary[]) {
|
private librarySummaries = new Map<string, LibrarySummary>()) {
|
||||||
super(options, context);
|
super(options, context);
|
||||||
librarySummaries.forEach(summary => this.librarySummaries.set(summary.fileName, summary));
|
|
||||||
this.moduleResolutionCache = ts.createModuleResolutionCache(
|
this.moduleResolutionCache = ts.createModuleResolutionCache(
|
||||||
this.context.getCurrentDirectory !(), this.context.getCanonicalFileName.bind(this.context));
|
this.context.getCurrentDirectory !(), this.context.getCanonicalFileName.bind(this.context));
|
||||||
const basePath = this.options.basePath !;
|
const basePath = this.options.basePath !;
|
||||||
|
@ -10,6 +10,7 @@ import {GeneratedFile} from '@angular/compiler';
|
|||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {TypeScriptNodeEmitter} from './node_emitter';
|
import {TypeScriptNodeEmitter} from './node_emitter';
|
||||||
|
import {GENERATED_FILES} from './util';
|
||||||
|
|
||||||
const PREAMBLE = `/**
|
const PREAMBLE = `/**
|
||||||
* @fileoverview This file is generated by the Angular template compiler.
|
* @fileoverview This file is generated by the Angular template compiler.
|
||||||
@ -18,17 +19,17 @@ const PREAMBLE = `/**
|
|||||||
* tslint:disable
|
* tslint:disable
|
||||||
*/`;
|
*/`;
|
||||||
|
|
||||||
export function getAngularEmitterTransformFactory(generatedFiles: GeneratedFile[]): () =>
|
export function getAngularEmitterTransformFactory(generatedFiles: Map<string, GeneratedFile>): () =>
|
||||||
(sourceFile: ts.SourceFile) => ts.SourceFile {
|
(sourceFile: ts.SourceFile) => ts.SourceFile {
|
||||||
return function() {
|
return function() {
|
||||||
const map = new Map(generatedFiles.filter(g => g.stmts && g.stmts.length)
|
|
||||||
.map<[string, GeneratedFile]>(g => [g.genFileUrl, g]));
|
|
||||||
const emitter = new TypeScriptNodeEmitter();
|
const emitter = new TypeScriptNodeEmitter();
|
||||||
return function(sourceFile: ts.SourceFile): ts.SourceFile {
|
return function(sourceFile: ts.SourceFile): ts.SourceFile {
|
||||||
const g = map.get(sourceFile.fileName);
|
const g = generatedFiles.get(sourceFile.fileName);
|
||||||
if (g && g.stmts) {
|
if (g && g.stmts) {
|
||||||
const [newSourceFile] = emitter.updateSourceFile(sourceFile, g.stmts, PREAMBLE);
|
const [newSourceFile] = emitter.updateSourceFile(sourceFile, g.stmts, PREAMBLE);
|
||||||
return newSourceFile;
|
return newSourceFile;
|
||||||
|
} else if (GENERATED_FILES.test(sourceFile.fileName)) {
|
||||||
|
return ts.updateSourceFileNode(sourceFile, []);
|
||||||
}
|
}
|
||||||
return sourceFile;
|
return sourceFile;
|
||||||
};
|
};
|
||||||
|
@ -18,7 +18,13 @@ import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, D
|
|||||||
import {CodeGenerator, TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host';
|
import {CodeGenerator, TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host';
|
||||||
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
|
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
|
||||||
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
|
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
|
||||||
import {GENERATED_FILES, StructureIsReused, tsStructureIsReused} from './util';
|
import {GENERATED_FILES, StructureIsReused, createMessageDiagnostic, tsStructureIsReused} from './util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of files that are emitable via calling ts.Program.emit
|
||||||
|
* passing individual targetSourceFiles.
|
||||||
|
*/
|
||||||
|
const MAX_FILE_COUNT_FOR_SINGLE_FILE_EMIT = 20;
|
||||||
|
|
||||||
const emptyModules: NgAnalyzedModules = {
|
const emptyModules: NgAnalyzedModules = {
|
||||||
ngModules: [],
|
ngModules: [],
|
||||||
@ -32,18 +38,20 @@ const defaultEmitCallback: TsEmitCallback =
|
|||||||
program.emit(
|
program.emit(
|
||||||
targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
|
targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
|
||||||
|
|
||||||
|
|
||||||
class AngularCompilerProgram implements Program {
|
class AngularCompilerProgram implements Program {
|
||||||
private metadataCache: LowerMetadataCache;
|
private metadataCache: LowerMetadataCache;
|
||||||
private oldProgramLibrarySummaries: LibrarySummary[] = [];
|
private oldProgramLibrarySummaries: Map<string, LibrarySummary>|undefined;
|
||||||
|
private oldProgramEmittedGeneratedFiles: Map<string, GeneratedFile>|undefined;
|
||||||
// Note: This will be cleared out as soon as we create the _tsProgram
|
// Note: This will be cleared out as soon as we create the _tsProgram
|
||||||
private oldTsProgram: ts.Program|undefined;
|
private oldTsProgram: ts.Program|undefined;
|
||||||
private emittedLibrarySummaries: LibrarySummary[]|undefined;
|
private emittedLibrarySummaries: LibrarySummary[]|undefined;
|
||||||
|
private emittedGeneratedFiles: GeneratedFile[]|undefined;
|
||||||
|
|
||||||
// Lazily initialized fields
|
// Lazily initialized fields
|
||||||
private _typeCheckHost: TypeCheckHost;
|
private _typeCheckHost: TypeCheckHost;
|
||||||
private _compiler: AotCompiler;
|
private _compiler: AotCompiler;
|
||||||
private _tsProgram: ts.Program;
|
private _tsProgram: ts.Program;
|
||||||
|
private _changedNonGenFileNames: string[]|undefined;
|
||||||
private _analyzedModules: NgAnalyzedModules|undefined;
|
private _analyzedModules: NgAnalyzedModules|undefined;
|
||||||
private _structuralDiagnostics: Diagnostic[]|undefined;
|
private _structuralDiagnostics: Diagnostic[]|undefined;
|
||||||
private _programWithStubs: ts.Program|undefined;
|
private _programWithStubs: ts.Program|undefined;
|
||||||
@ -60,6 +68,7 @@ class AngularCompilerProgram implements Program {
|
|||||||
this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined;
|
this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined;
|
||||||
if (oldProgram) {
|
if (oldProgram) {
|
||||||
this.oldProgramLibrarySummaries = oldProgram.getLibrarySummaries();
|
this.oldProgramLibrarySummaries = oldProgram.getLibrarySummaries();
|
||||||
|
this.oldProgramEmittedGeneratedFiles = oldProgram.getEmittedGeneratedFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.flatModuleOutFile) {
|
if (options.flatModuleOutFile) {
|
||||||
@ -81,10 +90,26 @@ class AngularCompilerProgram implements Program {
|
|||||||
this.metadataCache = new LowerMetadataCache({quotedNames: true}, !!options.strictMetadataEmit);
|
this.metadataCache = new LowerMetadataCache({quotedNames: true}, !!options.strictMetadataEmit);
|
||||||
}
|
}
|
||||||
|
|
||||||
getLibrarySummaries(): LibrarySummary[] {
|
getLibrarySummaries(): Map<string, LibrarySummary> {
|
||||||
const result = [...this.oldProgramLibrarySummaries];
|
const result = new Map<string, LibrarySummary>();
|
||||||
|
if (this.oldProgramLibrarySummaries) {
|
||||||
|
this.oldProgramLibrarySummaries.forEach((summary, fileName) => result.set(fileName, summary));
|
||||||
|
}
|
||||||
if (this.emittedLibrarySummaries) {
|
if (this.emittedLibrarySummaries) {
|
||||||
result.push(...this.emittedLibrarySummaries);
|
this.emittedLibrarySummaries.forEach(
|
||||||
|
(summary, fileName) => result.set(summary.fileName, summary));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEmittedGeneratedFiles(): Map<string, GeneratedFile> {
|
||||||
|
const result = new Map<string, GeneratedFile>();
|
||||||
|
if (this.oldProgramEmittedGeneratedFiles) {
|
||||||
|
this.oldProgramEmittedGeneratedFiles.forEach(
|
||||||
|
(genFile, fileName) => result.set(fileName, genFile));
|
||||||
|
}
|
||||||
|
if (this.emittedGeneratedFiles) {
|
||||||
|
this.emittedGeneratedFiles.forEach((genFile) => result.set(genFile.genFileUrl, genFile));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -142,6 +167,7 @@ class AngularCompilerProgram implements Program {
|
|||||||
customTransformers?: CustomTransformers,
|
customTransformers?: CustomTransformers,
|
||||||
emitCallback?: TsEmitCallback
|
emitCallback?: TsEmitCallback
|
||||||
} = {}): ts.EmitResult {
|
} = {}): ts.EmitResult {
|
||||||
|
const emitStart = Date.now();
|
||||||
if (emitFlags & EmitFlags.I18nBundle) {
|
if (emitFlags & EmitFlags.I18nBundle) {
|
||||||
const locale = this.options.i18nOutLocale || null;
|
const locale = this.options.i18nOutLocale || null;
|
||||||
const file = this.options.i18nOutFile || null;
|
const file = this.options.i18nOutFile || null;
|
||||||
@ -153,7 +179,7 @@ class AngularCompilerProgram implements Program {
|
|||||||
0) {
|
0) {
|
||||||
return {emitSkipped: true, diagnostics: [], emittedFiles: []};
|
return {emitSkipped: true, diagnostics: [], emittedFiles: []};
|
||||||
}
|
}
|
||||||
const {genFiles, genDiags} = this.generateFilesForEmit(emitFlags);
|
let {genFiles, genDiags} = this.generateFilesForEmit(emitFlags);
|
||||||
if (genDiags.length) {
|
if (genDiags.length) {
|
||||||
return {
|
return {
|
||||||
diagnostics: genDiags,
|
diagnostics: genDiags,
|
||||||
@ -161,11 +187,11 @@ class AngularCompilerProgram implements Program {
|
|||||||
emittedFiles: [],
|
emittedFiles: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const emittedLibrarySummaries = this.emittedLibrarySummaries = [];
|
this.emittedGeneratedFiles = genFiles;
|
||||||
|
|
||||||
const outSrcMapping: Array<{sourceFile: ts.SourceFile, outFileName: string}> = [];
|
const outSrcMapping: Array<{sourceFile: ts.SourceFile, outFileName: string}> = [];
|
||||||
const genFileByFileName = new Map<string, GeneratedFile>();
|
const genFileByFileName = new Map<string, GeneratedFile>();
|
||||||
genFiles.forEach(genFile => genFileByFileName.set(genFile.genFileUrl, genFile));
|
genFiles.forEach(genFile => genFileByFileName.set(genFile.genFileUrl, genFile));
|
||||||
|
this.emittedLibrarySummaries = [];
|
||||||
const writeTsFile: ts.WriteFileCallback =
|
const writeTsFile: ts.WriteFileCallback =
|
||||||
(outFileName, outData, writeByteOrderMark, onError?, sourceFiles?) => {
|
(outFileName, outData, writeByteOrderMark, onError?, sourceFiles?) => {
|
||||||
const sourceFile = sourceFiles && sourceFiles.length == 1 ? sourceFiles[0] : null;
|
const sourceFile = sourceFiles && sourceFiles.length == 1 ? sourceFiles[0] : null;
|
||||||
@ -176,7 +202,8 @@ class AngularCompilerProgram implements Program {
|
|||||||
}
|
}
|
||||||
this.writeFile(outFileName, outData, writeByteOrderMark, onError, genFile, sourceFiles);
|
this.writeFile(outFileName, outData, writeByteOrderMark, onError, genFile, sourceFiles);
|
||||||
};
|
};
|
||||||
|
const tsCustomTansformers = this.calculateTransforms(genFileByFileName, customTransformers);
|
||||||
|
const emitOnlyDtsFiles = (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS;
|
||||||
// Restore the original references before we emit so TypeScript doesn't emit
|
// Restore the original references before we emit so TypeScript doesn't emit
|
||||||
// a reference to the .d.ts file.
|
// a reference to the .d.ts file.
|
||||||
const augmentedReferences = new Map<ts.SourceFile, ts.FileReference[]>();
|
const augmentedReferences = new Map<ts.SourceFile, ts.FileReference[]>();
|
||||||
@ -187,16 +214,44 @@ class AngularCompilerProgram implements Program {
|
|||||||
sourceFile.referencedFiles = originalReferences;
|
sourceFile.referencedFiles = originalReferences;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const genTsFiles: GeneratedFile[] = [];
|
||||||
|
const genJsonFiles: GeneratedFile[] = [];
|
||||||
|
genFiles.forEach(gf => {
|
||||||
|
if (gf.stmts) {
|
||||||
|
genTsFiles.push(gf);
|
||||||
|
}
|
||||||
|
if (gf.source) {
|
||||||
|
genJsonFiles.push(gf);
|
||||||
|
}
|
||||||
|
});
|
||||||
let emitResult: ts.EmitResult;
|
let emitResult: ts.EmitResult;
|
||||||
|
let emittedUserTsCount: number;
|
||||||
try {
|
try {
|
||||||
|
const emitChangedFilesOnly = this._changedNonGenFileNames &&
|
||||||
|
this._changedNonGenFileNames.length < MAX_FILE_COUNT_FOR_SINGLE_FILE_EMIT;
|
||||||
|
if (emitChangedFilesOnly) {
|
||||||
|
const fileNamesToEmit =
|
||||||
|
[...this._changedNonGenFileNames !, ...genTsFiles.map(gf => gf.genFileUrl)];
|
||||||
|
emitResult = mergeEmitResults(
|
||||||
|
fileNamesToEmit.map((fileName) => emitResult = emitCallback({
|
||||||
|
program: this.tsProgram,
|
||||||
|
host: this.host,
|
||||||
|
options: this.options,
|
||||||
|
writeFile: writeTsFile, emitOnlyDtsFiles,
|
||||||
|
customTransformers: tsCustomTansformers,
|
||||||
|
targetSourceFile: this.tsProgram.getSourceFile(fileName),
|
||||||
|
})));
|
||||||
|
emittedUserTsCount = this._changedNonGenFileNames !.length;
|
||||||
|
} else {
|
||||||
emitResult = emitCallback({
|
emitResult = emitCallback({
|
||||||
program: this.tsProgram,
|
program: this.tsProgram,
|
||||||
host: this.host,
|
host: this.host,
|
||||||
options: this.options,
|
options: this.options,
|
||||||
writeFile: writeTsFile,
|
writeFile: writeTsFile, emitOnlyDtsFiles,
|
||||||
emitOnlyDtsFiles: (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS,
|
customTransformers: tsCustomTansformers
|
||||||
customTransformers: this.calculateTransforms(genFiles, customTransformers)
|
|
||||||
});
|
});
|
||||||
|
emittedUserTsCount = this.tsProgram.getSourceFiles().length - genTsFiles.length;
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
// Restore the references back to the augmented value to ensure that the
|
// Restore the references back to the augmented value to ensure that the
|
||||||
// checks that TypeScript makes for project structure reuse will succeed.
|
// checks that TypeScript makes for project structure reuse will succeed.
|
||||||
@ -207,10 +262,10 @@ class AngularCompilerProgram implements Program {
|
|||||||
|
|
||||||
if (!outSrcMapping.length) {
|
if (!outSrcMapping.length) {
|
||||||
// if no files were emitted by TypeScript, also don't emit .json files
|
// if no files were emitted by TypeScript, also don't emit .json files
|
||||||
|
emitResult.diagnostics.push(createMessageDiagnostic(`Emitted no files.`));
|
||||||
return emitResult;
|
return emitResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let sampleSrcFileName: string|undefined;
|
let sampleSrcFileName: string|undefined;
|
||||||
let sampleOutFileName: string|undefined;
|
let sampleOutFileName: string|undefined;
|
||||||
if (outSrcMapping.length) {
|
if (outSrcMapping.length) {
|
||||||
@ -220,16 +275,16 @@ class AngularCompilerProgram implements Program {
|
|||||||
const srcToOutPath =
|
const srcToOutPath =
|
||||||
createSrcToOutPathMapper(this.options.outDir, sampleSrcFileName, sampleOutFileName);
|
createSrcToOutPathMapper(this.options.outDir, sampleSrcFileName, sampleOutFileName);
|
||||||
if (emitFlags & EmitFlags.Codegen) {
|
if (emitFlags & EmitFlags.Codegen) {
|
||||||
genFiles.forEach(gf => {
|
genJsonFiles.forEach(gf => {
|
||||||
if (gf.source) {
|
|
||||||
const outFileName = srcToOutPath(gf.genFileUrl);
|
const outFileName = srcToOutPath(gf.genFileUrl);
|
||||||
this.writeFile(outFileName, gf.source, false, undefined, gf);
|
this.writeFile(outFileName, gf.source !, false, undefined, gf);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let metadataJsonCount = 0;
|
||||||
if (emitFlags & EmitFlags.Metadata) {
|
if (emitFlags & EmitFlags.Metadata) {
|
||||||
this.tsProgram.getSourceFiles().forEach(sf => {
|
this.tsProgram.getSourceFiles().forEach(sf => {
|
||||||
if (!sf.isDeclarationFile && !GENERATED_FILES.test(sf.fileName)) {
|
if (!sf.isDeclarationFile && !GENERATED_FILES.test(sf.fileName)) {
|
||||||
|
metadataJsonCount++;
|
||||||
const metadata = this.metadataCache.getMetadata(sf);
|
const metadata = this.metadataCache.getMetadata(sf);
|
||||||
const metadataText = JSON.stringify([metadata]);
|
const metadataText = JSON.stringify([metadata]);
|
||||||
const outFileName = srcToOutPath(sf.fileName.replace(/\.ts$/, '.metadata.json'));
|
const outFileName = srcToOutPath(sf.fileName.replace(/\.ts$/, '.metadata.json'));
|
||||||
@ -237,6 +292,15 @@ class AngularCompilerProgram implements Program {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const emitEnd = Date.now();
|
||||||
|
if (this.options.diagnostics) {
|
||||||
|
emitResult.diagnostics.push(createMessageDiagnostic([
|
||||||
|
`Emitted in ${emitEnd - emitStart}ms`,
|
||||||
|
`- ${emittedUserTsCount} user ts files`,
|
||||||
|
`- ${genTsFiles.length} generated ts files`,
|
||||||
|
`- ${genJsonFiles.length + metadataJsonCount} generated json files`,
|
||||||
|
].join('\n')));
|
||||||
|
}
|
||||||
return emitResult;
|
return emitResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,8 +345,9 @@ class AngularCompilerProgram implements Program {
|
|||||||
(this._semanticDiagnostics = this.generateSemanticDiagnostics());
|
(this._semanticDiagnostics = this.generateSemanticDiagnostics());
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateTransforms(genFiles: GeneratedFile[], customTransformers?: CustomTransformers):
|
private calculateTransforms(
|
||||||
ts.CustomTransformers {
|
genFiles: Map<string, GeneratedFile>,
|
||||||
|
customTransformers?: CustomTransformers): ts.CustomTransformers {
|
||||||
const beforeTs: ts.TransformerFactory<ts.SourceFile>[] = [];
|
const beforeTs: ts.TransformerFactory<ts.SourceFile>[] = [];
|
||||||
if (!this.options.disableExpressionLowering) {
|
if (!this.options.disableExpressionLowering) {
|
||||||
beforeTs.push(getExpressionLoweringTransformFactory(this.metadataCache));
|
beforeTs.push(getExpressionLoweringTransformFactory(this.metadataCache));
|
||||||
@ -353,6 +418,16 @@ class AngularCompilerProgram implements Program {
|
|||||||
sourceFiles.push(sf.fileName);
|
sourceFiles.push(sf.fileName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (oldTsProgram) {
|
||||||
|
// TODO(tbosch): if one of the files contains a `const enum`
|
||||||
|
// always emit all files!
|
||||||
|
const changedNonGenFileNames = this._changedNonGenFileNames = [] as string[];
|
||||||
|
tmpProgram.getSourceFiles().forEach(sf => {
|
||||||
|
if (!GENERATED_FILES.test(sf.fileName) && oldTsProgram.getSourceFile(sf.fileName) !== sf) {
|
||||||
|
changedNonGenFileNames.push(sf.fileName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
return {tmpProgram, sourceFiles, hostAdapter, rootNames};
|
return {tmpProgram, sourceFiles, hostAdapter, rootNames};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -418,7 +493,14 @@ class AngularCompilerProgram implements Program {
|
|||||||
if (!(emitFlags & EmitFlags.Codegen)) {
|
if (!(emitFlags & EmitFlags.Codegen)) {
|
||||||
return {genFiles: [], genDiags: []};
|
return {genFiles: [], genDiags: []};
|
||||||
}
|
}
|
||||||
const genFiles = this.compiler.emitAllImpls(this.analyzedModules);
|
let genFiles = this.compiler.emitAllImpls(this.analyzedModules);
|
||||||
|
if (this.oldProgramEmittedGeneratedFiles) {
|
||||||
|
const oldProgramEmittedGeneratedFiles = this.oldProgramEmittedGeneratedFiles;
|
||||||
|
genFiles = genFiles.filter(genFile => {
|
||||||
|
const oldGenFile = oldProgramEmittedGeneratedFiles.get(genFile.genFileUrl);
|
||||||
|
return !oldGenFile || !genFile.isEquivalent(oldGenFile);
|
||||||
|
});
|
||||||
|
}
|
||||||
return {genFiles, genDiags: []};
|
return {genFiles, genDiags: []};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO(tbosch): check whether we can actually have syntax errors here,
|
// TODO(tbosch): check whether we can actually have syntax errors here,
|
||||||
@ -533,7 +615,7 @@ function getAotCompilerOptions(options: CompilerOptions): AotCompilerOptions {
|
|||||||
locale: options.i18nInLocale,
|
locale: options.i18nInLocale,
|
||||||
i18nFormat: options.i18nInFormat || options.i18nOutFormat, translations, missingTranslation,
|
i18nFormat: options.i18nInFormat || options.i18nOutFormat, translations, missingTranslation,
|
||||||
enableLegacyTemplate: options.enableLegacyTemplate,
|
enableLegacyTemplate: options.enableLegacyTemplate,
|
||||||
enableSummariesForJit: true,
|
enableSummariesForJit: options.enableSummariesForJit,
|
||||||
preserveWhitespaces: options.preserveWhitespaces,
|
preserveWhitespaces: options.preserveWhitespaces,
|
||||||
fullTemplateTypeCheck: options.fullTemplateTypeCheck,
|
fullTemplateTypeCheck: options.fullTemplateTypeCheck,
|
||||||
allowEmptyCodegenFiles: options.allowEmptyCodegenFiles,
|
allowEmptyCodegenFiles: options.allowEmptyCodegenFiles,
|
||||||
@ -649,3 +731,15 @@ export function i18nGetExtension(formatName: string): string {
|
|||||||
|
|
||||||
throw new Error(`Unsupported format "${formatName}"`);
|
throw new Error(`Unsupported format "${formatName}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mergeEmitResults(emitResults: ts.EmitResult[]): ts.EmitResult {
|
||||||
|
const diagnostics: ts.Diagnostic[] = [];
|
||||||
|
let emitSkipped = true;
|
||||||
|
const emittedFiles: string[] = [];
|
||||||
|
for (const er of emitResults) {
|
||||||
|
diagnostics.push(...er.diagnostics);
|
||||||
|
emitSkipped = emitSkipped || er.emitSkipped;
|
||||||
|
emittedFiles.push(...er.emittedFiles);
|
||||||
|
}
|
||||||
|
return {diagnostics, emitSkipped, emittedFiles};
|
||||||
|
}
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {DEFAULT_ERROR_CODE, Diagnostic, SOURCE} from './api';
|
||||||
|
|
||||||
export const GENERATED_FILES = /(.*?)\.(ngfactory|shim\.ngstyle|ngstyle|ngsummary)\.(js|d\.ts|ts)$/;
|
export const GENERATED_FILES = /(.*?)\.(ngfactory|shim\.ngstyle|ngstyle|ngsummary)\.(js|d\.ts|ts)$/;
|
||||||
|
|
||||||
export const enum StructureIsReused {Not = 0, SafeModules = 1, Completely = 2}
|
export const enum StructureIsReused {Not = 0, SafeModules = 1, Completely = 2}
|
||||||
@ -16,3 +18,14 @@ export const enum StructureIsReused {Not = 0, SafeModules = 1, Completely = 2}
|
|||||||
export function tsStructureIsReused(program: ts.Program): StructureIsReused {
|
export function tsStructureIsReused(program: ts.Program): StructureIsReused {
|
||||||
return (program as any).structureIsReused;
|
return (program as any).structureIsReused;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createMessageDiagnostic(messageText: string): ts.Diagnostic&Diagnostic {
|
||||||
|
return {
|
||||||
|
file: undefined,
|
||||||
|
start: undefined,
|
||||||
|
length: undefined,
|
||||||
|
category: ts.DiagnosticCategory.Message, messageText,
|
||||||
|
code: DEFAULT_ERROR_CODE,
|
||||||
|
source: SOURCE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -336,18 +336,21 @@ describe('ngc transformer command-line', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function expectAllGeneratedFilesToExist() {
|
function expectAllGeneratedFilesToExist(enableSummariesForJit = true) {
|
||||||
modules.forEach(moduleName => {
|
modules.forEach(moduleName => {
|
||||||
if (/module|comp/.test(moduleName)) {
|
if (/module|comp/.test(moduleName)) {
|
||||||
shouldExist(moduleName + '.ngfactory.js');
|
shouldExist(moduleName + '.ngfactory.js');
|
||||||
shouldExist(moduleName + '.ngfactory.d.ts');
|
shouldExist(moduleName + '.ngfactory.d.ts');
|
||||||
shouldExist(moduleName + '.ngsummary.js');
|
|
||||||
shouldExist(moduleName + '.ngsummary.d.ts');
|
|
||||||
} else {
|
} else {
|
||||||
shouldNotExist(moduleName + '.ngfactory.js');
|
shouldNotExist(moduleName + '.ngfactory.js');
|
||||||
shouldNotExist(moduleName + '.ngfactory.d.ts');
|
shouldNotExist(moduleName + '.ngfactory.d.ts');
|
||||||
|
}
|
||||||
|
if (enableSummariesForJit) {
|
||||||
shouldExist(moduleName + '.ngsummary.js');
|
shouldExist(moduleName + '.ngsummary.js');
|
||||||
shouldExist(moduleName + '.ngsummary.d.ts');
|
shouldExist(moduleName + '.ngsummary.d.ts');
|
||||||
|
} else {
|
||||||
|
shouldNotExist(moduleName + '.ngsummary.js');
|
||||||
|
shouldNotExist(moduleName + '.ngsummary.d.ts');
|
||||||
}
|
}
|
||||||
shouldExist(moduleName + '.ngsummary.json');
|
shouldExist(moduleName + '.ngsummary.json');
|
||||||
shouldNotExist(moduleName + '.ngfactory.metadata.json');
|
shouldNotExist(moduleName + '.ngfactory.metadata.json');
|
||||||
@ -359,10 +362,11 @@ describe('ngc transformer command-line', () => {
|
|||||||
shouldExist('emulated.css.shim.ngstyle.d.ts');
|
shouldExist('emulated.css.shim.ngstyle.d.ts');
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should emit generated files from sources', () => {
|
it('should emit generated files from sources with summariesForJit', () => {
|
||||||
writeConfig(`{
|
writeConfig(`{
|
||||||
"extends": "./tsconfig-base.json",
|
"extends": "./tsconfig-base.json",
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
|
"enableSummariesForJit": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts"]
|
"include": ["src/**/*.ts"]
|
||||||
}`);
|
}`);
|
||||||
@ -370,7 +374,22 @@ describe('ngc transformer command-line', () => {
|
|||||||
expect(exitCode).toEqual(0);
|
expect(exitCode).toEqual(0);
|
||||||
outDir = path.resolve(basePath, 'built', 'src');
|
outDir = path.resolve(basePath, 'built', 'src');
|
||||||
expectJsDtsMetadataJsonToExist();
|
expectJsDtsMetadataJsonToExist();
|
||||||
expectAllGeneratedFilesToExist();
|
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', () => {
|
it('should emit generated files from libraries', () => {
|
||||||
@ -408,7 +427,8 @@ describe('ngc transformer command-line', () => {
|
|||||||
writeConfig(`{
|
writeConfig(`{
|
||||||
"extends": "./tsconfig-base.json",
|
"extends": "./tsconfig-base.json",
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
"skipTemplateCodegen": false
|
"skipTemplateCodegen": false,
|
||||||
|
"enableSummariesForJit": true
|
||||||
},
|
},
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "built"
|
"outDir": "built"
|
||||||
@ -880,7 +900,8 @@ describe('ngc transformer command-line', () => {
|
|||||||
write('tsconfig-ng.json', `{
|
write('tsconfig-ng.json', `{
|
||||||
"extends": "./tsconfig-base.json",
|
"extends": "./tsconfig-base.json",
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
"generateCodeForLibraries": true
|
"generateCodeForLibraries": true,
|
||||||
|
"enableSummariesForJit": true
|
||||||
},
|
},
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "."
|
"outDir": "."
|
||||||
@ -896,7 +917,8 @@ describe('ngc transformer command-line', () => {
|
|||||||
write('lib1/tsconfig-lib1.json', `{
|
write('lib1/tsconfig-lib1.json', `{
|
||||||
"extends": "../tsconfig-base.json",
|
"extends": "../tsconfig-base.json",
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
"generateCodeForLibraries": false
|
"generateCodeForLibraries": false,
|
||||||
|
"enableSummariesForJit": true
|
||||||
},
|
},
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
@ -919,7 +941,8 @@ describe('ngc transformer command-line', () => {
|
|||||||
write('lib2/tsconfig-lib2.json', `{
|
write('lib2/tsconfig-lib2.json', `{
|
||||||
"extends": "../tsconfig-base.json",
|
"extends": "../tsconfig-base.json",
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
"generateCodeForLibraries": false
|
"generateCodeForLibraries": false,
|
||||||
|
"enableSummariesForJit": true
|
||||||
},
|
},
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
@ -941,7 +964,8 @@ describe('ngc transformer command-line', () => {
|
|||||||
write('app/tsconfig-app.json', `{
|
write('app/tsconfig-app.json', `{
|
||||||
"extends": "../tsconfig-base.json",
|
"extends": "../tsconfig-base.json",
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
"generateCodeForLibraries": false
|
"generateCodeForLibraries": false,
|
||||||
|
"enableSummariesForJit": true
|
||||||
},
|
},
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
|
@ -86,10 +86,9 @@ describe('perform watch', () => {
|
|||||||
|
|
||||||
// trigger a single file change
|
// trigger a single file change
|
||||||
// -> all other files should be cached
|
// -> all other files should be cached
|
||||||
fs.unlinkSync(mainNgFactory);
|
|
||||||
host.triggerFileChange(FileChangeEvent.Change, utilTsPath);
|
host.triggerFileChange(FileChangeEvent.Change, utilTsPath);
|
||||||
|
expectNoDiagnostics(config.options, host.diagnostics);
|
||||||
|
|
||||||
expect(fs.existsSync(mainNgFactory)).toBe(true);
|
|
||||||
expect(fileExistsSpy !).not.toHaveBeenCalledWith(mainTsPath);
|
expect(fileExistsSpy !).not.toHaveBeenCalledWith(mainTsPath);
|
||||||
expect(fileExistsSpy !).toHaveBeenCalledWith(utilTsPath);
|
expect(fileExistsSpy !).toHaveBeenCalledWith(utilTsPath);
|
||||||
expect(getSourceFileSpy !).not.toHaveBeenCalledWith(mainTsPath, ts.ScriptTarget.ES5);
|
expect(getSourceFileSpy !).not.toHaveBeenCalledWith(mainTsPath, ts.ScriptTarget.ES5);
|
||||||
@ -97,11 +96,10 @@ describe('perform watch', () => {
|
|||||||
|
|
||||||
// trigger a folder change
|
// trigger a folder change
|
||||||
// -> nothing should be cached
|
// -> nothing should be cached
|
||||||
fs.unlinkSync(mainNgFactory);
|
|
||||||
host.triggerFileChange(
|
host.triggerFileChange(
|
||||||
FileChangeEvent.CreateDeleteDir, path.resolve(testSupport.basePath, 'src'));
|
FileChangeEvent.CreateDeleteDir, path.resolve(testSupport.basePath, 'src'));
|
||||||
|
expectNoDiagnostics(config.options, host.diagnostics);
|
||||||
|
|
||||||
expect(fs.existsSync(mainNgFactory)).toBe(true);
|
|
||||||
expect(fileExistsSpy !).toHaveBeenCalledWith(mainTsPath);
|
expect(fileExistsSpy !).toHaveBeenCalledWith(mainTsPath);
|
||||||
expect(fileExistsSpy !).toHaveBeenCalledWith(utilTsPath);
|
expect(fileExistsSpy !).toHaveBeenCalledWith(utilTsPath);
|
||||||
expect(getSourceFileSpy !).toHaveBeenCalledWith(mainTsPath, ts.ScriptTarget.ES5);
|
expect(getSourceFileSpy !).toHaveBeenCalledWith(mainTsPath, ts.ScriptTarget.ES5);
|
||||||
|
@ -110,8 +110,9 @@ export function setup(): TestSupport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function expectNoDiagnostics(options: ng.CompilerOptions, diags: ng.Diagnostics) {
|
export function expectNoDiagnostics(options: ng.CompilerOptions, diags: ng.Diagnostics) {
|
||||||
if (diags.length) {
|
const errorDiags = diags.filter(d => d.category !== ts.DiagnosticCategory.Message);
|
||||||
throw new Error(`Expected no diagnostics: ${ng.formatDiagnostics(options, diags)}`);
|
if (errorDiags.length) {
|
||||||
|
throw new Error(`Expected no diagnostics: ${ng.formatDiagnostics(options, errorDiags)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ describe('NgCompilerHost', () => {
|
|||||||
} = {}) {
|
} = {}) {
|
||||||
return new TsCompilerAotCompilerTypeCheckHostAdapter(
|
return new TsCompilerAotCompilerTypeCheckHostAdapter(
|
||||||
['/tmp/index.ts'], options, ngHost, new MetadataCollector(), codeGenerator,
|
['/tmp/index.ts'], options, ngHost, new MetadataCollector(), codeGenerator,
|
||||||
librarySummaries);
|
new Map(librarySummaries.map(entry => [entry.fileName, entry] as[string, LibrarySummary])));
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('fileNameToModuleName', () => {
|
describe('fileNameToModuleName', () => {
|
||||||
|
@ -57,17 +57,20 @@ describe('ng program', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function compile(
|
function compile(
|
||||||
oldProgram?: ng.Program, overrideOptions?: ng.CompilerOptions,
|
oldProgram?: ng.Program, overrideOptions?: ng.CompilerOptions, rootNames?: string[],
|
||||||
rootNames?: string[]): ng.Program {
|
host?: CompilerHost): ng.Program {
|
||||||
const options = testSupport.createCompilerOptions(overrideOptions);
|
const options = testSupport.createCompilerOptions(overrideOptions);
|
||||||
if (!rootNames) {
|
if (!rootNames) {
|
||||||
rootNames = [path.resolve(testSupport.basePath, 'src/index.ts')];
|
rootNames = [path.resolve(testSupport.basePath, 'src/index.ts')];
|
||||||
}
|
}
|
||||||
|
if (!host) {
|
||||||
|
host = ng.createCompilerHost({options});
|
||||||
|
}
|
||||||
const program = ng.createProgram({
|
const program = ng.createProgram({
|
||||||
rootNames: rootNames,
|
rootNames: rootNames,
|
||||||
options,
|
options,
|
||||||
host: ng.createCompilerHost({options}), oldProgram,
|
host,
|
||||||
|
oldProgram,
|
||||||
});
|
});
|
||||||
expectNoDiagnosticsInProgram(options, program);
|
expectNoDiagnosticsInProgram(options, program);
|
||||||
program.emit();
|
program.emit();
|
||||||
@ -153,6 +156,59 @@ describe('ng program', () => {
|
|||||||
.toBe(false);
|
.toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should only emit changed files', () => {
|
||||||
|
testSupport.writeFiles({
|
||||||
|
'src/index.ts': createModuleAndCompSource('comp', 'index.html'),
|
||||||
|
'src/index.html': `Start`
|
||||||
|
});
|
||||||
|
const options: ng.CompilerOptions = {declaration: false};
|
||||||
|
const host = ng.createCompilerHost({options});
|
||||||
|
const originalGetSourceFile = host.getSourceFile;
|
||||||
|
const fileCache = new Map<string, ts.SourceFile>();
|
||||||
|
host.getSourceFile = (fileName: string) => {
|
||||||
|
if (fileCache.has(fileName)) {
|
||||||
|
return fileCache.get(fileName);
|
||||||
|
}
|
||||||
|
const sf = originalGetSourceFile.call(host, fileName);
|
||||||
|
fileCache.set(fileName, sf);
|
||||||
|
return sf;
|
||||||
|
};
|
||||||
|
|
||||||
|
const written = new Map<string, string>();
|
||||||
|
host.writeFile = (fileName: string, data: string) => written.set(fileName, data);
|
||||||
|
|
||||||
|
// compile libraries
|
||||||
|
const p1 = compile(undefined, options, undefined, host);
|
||||||
|
|
||||||
|
// first compile without libraries
|
||||||
|
const p2 = compile(p1, options, undefined, host);
|
||||||
|
expect(written.has(path.resolve(testSupport.basePath, 'built/src/index.js'))).toBe(true);
|
||||||
|
let ngFactoryContent =
|
||||||
|
written.get(path.resolve(testSupport.basePath, 'built/src/index.ngfactory.js'));
|
||||||
|
expect(ngFactoryContent).toMatch(/Start/);
|
||||||
|
|
||||||
|
// no change -> no emit
|
||||||
|
written.clear();
|
||||||
|
const p3 = compile(p2, options, undefined, host);
|
||||||
|
expect(written.size).toBe(0);
|
||||||
|
|
||||||
|
// change a user file
|
||||||
|
written.clear();
|
||||||
|
fileCache.delete(path.resolve(testSupport.basePath, 'src/index.ts'));
|
||||||
|
const p4 = compile(p3, options, undefined, host);
|
||||||
|
expect(written.size).toBe(1);
|
||||||
|
expect(written.has(path.resolve(testSupport.basePath, 'built/src/index.js'))).toBe(true);
|
||||||
|
|
||||||
|
// change a file that is input to generated files
|
||||||
|
written.clear();
|
||||||
|
testSupport.writeFiles({'src/index.html': 'Hello'});
|
||||||
|
const p5 = compile(p4, options, undefined, host);
|
||||||
|
expect(written.size).toBe(1);
|
||||||
|
ngFactoryContent =
|
||||||
|
written.get(path.resolve(testSupport.basePath, 'built/src/index.ngfactory.js'));
|
||||||
|
expect(ngFactoryContent).toMatch(/Hello/);
|
||||||
|
});
|
||||||
|
|
||||||
it('should store library summaries on emit', () => {
|
it('should store library summaries on emit', () => {
|
||||||
compileLib('lib');
|
compileLib('lib');
|
||||||
testSupport.writeFiles({
|
testSupport.writeFiles({
|
||||||
@ -163,17 +219,19 @@ describe('ng program', () => {
|
|||||||
`
|
`
|
||||||
});
|
});
|
||||||
const p1 = compile();
|
const p1 = compile();
|
||||||
expect(p1.getLibrarySummaries().some(
|
expect(Array.from(p1.getLibrarySummaries().values())
|
||||||
sf => /node_modules\/lib\/index\.ngfactory\.d\.ts$/.test(sf.fileName)))
|
.some(sf => /node_modules\/lib\/index\.ngfactory\.d\.ts$/.test(sf.fileName)))
|
||||||
.toBe(true);
|
.toBe(true);
|
||||||
expect(p1.getLibrarySummaries().some(
|
expect(Array.from(p1.getLibrarySummaries().values())
|
||||||
sf => /node_modules\/lib\/index\.ngsummary\.json$/.test(sf.fileName)))
|
.some(sf => /node_modules\/lib\/index\.ngsummary\.json$/.test(sf.fileName)))
|
||||||
.toBe(true);
|
.toBe(true);
|
||||||
expect(
|
expect(Array.from(p1.getLibrarySummaries().values())
|
||||||
p1.getLibrarySummaries().some(sf => /node_modules\/lib\/index\.d\.ts$/.test(sf.fileName)))
|
.some(sf => /node_modules\/lib\/index\.d\.ts$/.test(sf.fileName)))
|
||||||
.toBe(true);
|
.toBe(true);
|
||||||
|
|
||||||
expect(p1.getLibrarySummaries().some(sf => /src\/main.*$/.test(sf.fileName))).toBe(false);
|
expect(Array.from(p1.getLibrarySummaries().values())
|
||||||
|
.some(sf => /src\/main.*$/.test(sf.fileName)))
|
||||||
|
.toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reuse the old ts program completely if nothing changed', () => {
|
it('should reuse the old ts program completely if nothing changed', () => {
|
||||||
@ -300,7 +358,10 @@ describe('ng program', () => {
|
|||||||
export * from './main';
|
export * from './main';
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
const options = testSupport.createCompilerOptions({allowEmptyCodegenFiles: true});
|
const options = testSupport.createCompilerOptions({
|
||||||
|
allowEmptyCodegenFiles: true,
|
||||||
|
enableSummariesForJit: true,
|
||||||
|
});
|
||||||
const host = ng.createCompilerHost({options});
|
const host = ng.createCompilerHost({options});
|
||||||
const written = new Map < string, {
|
const written = new Map < string, {
|
||||||
original: ts.SourceFile[]|undefined;
|
original: ts.SourceFile[]|undefined;
|
||||||
@ -384,11 +445,11 @@ describe('ng program', () => {
|
|||||||
testSupport.shouldExist('built/main.ngfactory.js');
|
testSupport.shouldExist('built/main.ngfactory.js');
|
||||||
testSupport.shouldExist('built/main.ngfactory.d.ts');
|
testSupport.shouldExist('built/main.ngfactory.d.ts');
|
||||||
testSupport.shouldExist('built/main.ngsummary.json');
|
testSupport.shouldExist('built/main.ngsummary.json');
|
||||||
testSupport.shouldNotExist('build/node_modules/lib/index.js');
|
testSupport.shouldNotExist('built/node_modules/lib/index.js');
|
||||||
testSupport.shouldNotExist('build/node_modules/lib/index.d.ts');
|
testSupport.shouldNotExist('built/node_modules/lib/index.d.ts');
|
||||||
testSupport.shouldNotExist('build/node_modules/lib/index.ngfactory.js');
|
testSupport.shouldNotExist('built/node_modules/lib/index.ngfactory.js');
|
||||||
testSupport.shouldNotExist('build/node_modules/lib/index.ngfactory.d.ts');
|
testSupport.shouldNotExist('built/node_modules/lib/index.ngfactory.d.ts');
|
||||||
testSupport.shouldNotExist('build/node_modules/lib/index.ngsummary.json');
|
testSupport.shouldNotExist('built/node_modules/lib/index.ngsummary.json');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('createSrcToOutPathMapper', () => {
|
describe('createSrcToOutPathMapper', () => {
|
||||||
|
@ -98,8 +98,10 @@ export class AotCompiler {
|
|||||||
if (this._options.allowEmptyCodegenFiles || file.directives.length || file.pipes.length ||
|
if (this._options.allowEmptyCodegenFiles || file.directives.length || file.pipes.length ||
|
||||||
file.injectables.length || file.ngModules.length || file.exportsNonSourceFiles) {
|
file.injectables.length || file.ngModules.length || file.exportsNonSourceFiles) {
|
||||||
genFileNames.push(ngfactoryFilePath(file.fileName, true));
|
genFileNames.push(ngfactoryFilePath(file.fileName, true));
|
||||||
|
if (this._options.enableSummariesForJit) {
|
||||||
genFileNames.push(summaryForJitFileName(file.fileName, true));
|
genFileNames.push(summaryForJitFileName(file.fileName, true));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const fileSuffix = splitTypescriptSuffix(file.fileName, true)[1];
|
const fileSuffix = splitTypescriptSuffix(file.fileName, true)[1];
|
||||||
file.directives.forEach((dirSymbol) => {
|
file.directives.forEach((dirSymbol) => {
|
||||||
const compMeta =
|
const compMeta =
|
||||||
@ -376,7 +378,9 @@ export class AotCompiler {
|
|||||||
metadata: this._metadataResolver.getInjectableSummary(ref) !.type
|
metadata: this._metadataResolver.getInjectableSummary(ref) !.type
|
||||||
}))
|
}))
|
||||||
];
|
];
|
||||||
const forJitOutputCtx = this._createOutputContext(summaryForJitFileName(srcFileName, true));
|
const forJitOutputCtx = this._options.enableSummariesForJit ?
|
||||||
|
this._createOutputContext(summaryForJitFileName(srcFileName, true)) :
|
||||||
|
null;
|
||||||
const {json, exportAs} = serializeSummaries(
|
const {json, exportAs} = serializeSummaries(
|
||||||
srcFileName, forJitOutputCtx, this._summaryResolver, this._symbolResolver, symbolSummaries,
|
srcFileName, forJitOutputCtx, this._summaryResolver, this._symbolResolver, symbolSummaries,
|
||||||
typeData);
|
typeData);
|
||||||
@ -387,11 +391,11 @@ export class AotCompiler {
|
|||||||
]));
|
]));
|
||||||
});
|
});
|
||||||
const summaryJson = new GeneratedFile(srcFileName, summaryFileName(srcFileName), json);
|
const summaryJson = new GeneratedFile(srcFileName, summaryFileName(srcFileName), json);
|
||||||
if (this._options.enableSummariesForJit) {
|
const result = [summaryJson];
|
||||||
return [summaryJson, this._codegenSourceModule(srcFileName, forJitOutputCtx)];
|
if (forJitOutputCtx) {
|
||||||
|
result.push(this._codegenSourceModule(srcFileName, forJitOutputCtx));
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
return [summaryJson];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _compileModule(outputCtx: OutputContext, ngModule: CompileNgModuleMetadata): void {
|
private _compileModule(outputCtx: OutputContext, ngModule: CompileNgModuleMetadata): void {
|
||||||
|
@ -14,7 +14,6 @@ export interface AotCompilerOptions {
|
|||||||
translations?: string;
|
translations?: string;
|
||||||
missingTranslation?: MissingTranslationStrategy;
|
missingTranslation?: MissingTranslationStrategy;
|
||||||
enableLegacyTemplate?: boolean;
|
enableLegacyTemplate?: boolean;
|
||||||
/** TODO(tbosch): remove this flag as it is always on in the new ngc */
|
|
||||||
enableSummariesForJit?: boolean;
|
enableSummariesForJit?: boolean;
|
||||||
preserveWhitespaces?: boolean;
|
preserveWhitespaces?: boolean;
|
||||||
fullTemplateTypeCheck?: boolean;
|
fullTemplateTypeCheck?: boolean;
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {sourceUrl} from '../compile_metadata';
|
import {sourceUrl} from '../compile_metadata';
|
||||||
import {Statement} from '../output/output_ast';
|
import {Statement, areAllEquivalent} from '../output/output_ast';
|
||||||
import {TypeScriptEmitter} from '../output/ts_emitter';
|
import {TypeScriptEmitter} from '../output/ts_emitter';
|
||||||
|
|
||||||
export class GeneratedFile {
|
export class GeneratedFile {
|
||||||
@ -24,6 +24,21 @@ export class GeneratedFile {
|
|||||||
this.stmts = sourceOrStmts;
|
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 {
|
export function toTypeScript(file: GeneratedFile, preamble: string = ''): string {
|
||||||
|
@ -15,14 +15,14 @@ import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolv
|
|||||||
import {summaryForJitFileName, summaryForJitName} from './util';
|
import {summaryForJitFileName, summaryForJitName} from './util';
|
||||||
|
|
||||||
export function serializeSummaries(
|
export function serializeSummaries(
|
||||||
srcFileName: string, forJitCtx: OutputContext, summaryResolver: SummaryResolver<StaticSymbol>,
|
srcFileName: string, forJitCtx: OutputContext | null,
|
||||||
symbolResolver: StaticSymbolResolver, symbols: ResolvedStaticSymbol[], types: {
|
summaryResolver: SummaryResolver<StaticSymbol>, symbolResolver: StaticSymbolResolver,
|
||||||
|
symbols: ResolvedStaticSymbol[], types: {
|
||||||
summary: CompileTypeSummary,
|
summary: CompileTypeSummary,
|
||||||
metadata: CompileNgModuleMetadata | CompileDirectiveMetadata | CompilePipeMetadata |
|
metadata: CompileNgModuleMetadata | CompileDirectiveMetadata | CompilePipeMetadata |
|
||||||
CompileTypeMetadata
|
CompileTypeMetadata
|
||||||
}[]): {json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} {
|
}[]): {json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} {
|
||||||
const toJsonSerializer = new ToJsonSerializer(symbolResolver, summaryResolver, srcFileName);
|
const toJsonSerializer = new ToJsonSerializer(symbolResolver, summaryResolver, srcFileName);
|
||||||
const forJitSerializer = new ForJitSerializer(forJitCtx, symbolResolver);
|
|
||||||
|
|
||||||
// for symbols, we use everything except for the class metadata itself
|
// for symbols, we use everything except for the class metadata itself
|
||||||
// (we keep the statics though), as the class metadata is contained in the
|
// (we keep the statics though), as the class metadata is contained in the
|
||||||
@ -33,18 +33,20 @@ export function serializeSummaries(
|
|||||||
|
|
||||||
// Add type summaries.
|
// Add type summaries.
|
||||||
types.forEach(({summary, metadata}) => {
|
types.forEach(({summary, metadata}) => {
|
||||||
forJitSerializer.addSourceType(summary, metadata);
|
|
||||||
toJsonSerializer.addSummary(
|
toJsonSerializer.addSummary(
|
||||||
{symbol: summary.type.reference, metadata: undefined, type: summary});
|
{symbol: summary.type.reference, metadata: undefined, type: summary});
|
||||||
});
|
});
|
||||||
|
const {json, exportAs} = toJsonSerializer.serialize();
|
||||||
|
if (forJitCtx) {
|
||||||
|
const forJitSerializer = new ForJitSerializer(forJitCtx, symbolResolver);
|
||||||
|
types.forEach(({summary, metadata}) => { forJitSerializer.addSourceType(summary, metadata); });
|
||||||
toJsonSerializer.unprocessedSymbolSummariesBySymbol.forEach((summary) => {
|
toJsonSerializer.unprocessedSymbolSummariesBySymbol.forEach((summary) => {
|
||||||
if (summaryResolver.isLibraryFile(summary.symbol.filePath) && summary.type) {
|
if (summaryResolver.isLibraryFile(summary.symbol.filePath) && summary.type) {
|
||||||
forJitSerializer.addLibType(summary.type);
|
forJitSerializer.addLibType(summary.type);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const {json, exportAs} = toJsonSerializer.serialize();
|
|
||||||
forJitSerializer.serialize(exportAs);
|
forJitSerializer.serialize(exportAs);
|
||||||
|
}
|
||||||
return {json, exportAs};
|
return {json, exportAs};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +104,27 @@ export enum BinaryOperator {
|
|||||||
BiggerEquals
|
BiggerEquals
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function nullSafeIsEquivalent<T extends{isEquivalent(other: T): boolean}>(
|
||||||
|
base: T | null, other: T | null) {
|
||||||
|
if (base == null || other == null) {
|
||||||
|
return base == other;
|
||||||
|
}
|
||||||
|
return base.isEquivalent(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function areAllEquivalent<T extends{isEquivalent(other: T): boolean}>(
|
||||||
|
base: T[], other: T[]) {
|
||||||
|
const len = base.length;
|
||||||
|
if (len !== other.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
if (!base[i].isEquivalent(other[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class Expression {
|
export abstract class Expression {
|
||||||
public type: Type|null;
|
public type: Type|null;
|
||||||
@ -116,6 +137,12 @@ export abstract class Expression {
|
|||||||
|
|
||||||
abstract visitExpression(visitor: ExpressionVisitor, context: any): any;
|
abstract visitExpression(visitor: ExpressionVisitor, context: any): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates whether this expression produces the same value as the given expression.
|
||||||
|
* Note: We don't check Types nor ParseSourceSpans nor function arguments.
|
||||||
|
*/
|
||||||
|
abstract isEquivalent(e: Expression): boolean;
|
||||||
|
|
||||||
prop(name: string, sourceSpan?: ParseSourceSpan|null): ReadPropExpr {
|
prop(name: string, sourceSpan?: ParseSourceSpan|null): ReadPropExpr {
|
||||||
return new ReadPropExpr(this, name, null, sourceSpan);
|
return new ReadPropExpr(this, name, null, sourceSpan);
|
||||||
}
|
}
|
||||||
@ -222,6 +249,10 @@ export class ReadVarExpr extends Expression {
|
|||||||
this.builtin = <BuiltinVar>name;
|
this.builtin = <BuiltinVar>name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof ReadVarExpr && this.name === e.name && this.builtin === e.builtin;
|
||||||
|
}
|
||||||
|
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitReadVarExpr(this, context);
|
return visitor.visitReadVarExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -242,6 +273,9 @@ export class WriteVarExpr extends Expression {
|
|||||||
super(type || value.type, sourceSpan);
|
super(type || value.type, sourceSpan);
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof WriteVarExpr && this.name === e.name && this.value.isEquivalent(e.value);
|
||||||
|
}
|
||||||
|
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitWriteVarExpr(this, context);
|
return visitor.visitWriteVarExpr(this, context);
|
||||||
@ -261,6 +295,10 @@ export class WriteKeyExpr extends Expression {
|
|||||||
super(type || value.type, sourceSpan);
|
super(type || value.type, sourceSpan);
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof WriteKeyExpr && this.receiver.isEquivalent(e.receiver) &&
|
||||||
|
this.index.isEquivalent(e.index) && this.value.isEquivalent(e.value);
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitWriteKeyExpr(this, context);
|
return visitor.visitWriteKeyExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -275,6 +313,10 @@ export class WritePropExpr extends Expression {
|
|||||||
super(type || value.type, sourceSpan);
|
super(type || value.type, sourceSpan);
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof WritePropExpr && this.receiver.isEquivalent(e.receiver) &&
|
||||||
|
this.name === e.name && this.value.isEquivalent(e.value);
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitWritePropExpr(this, context);
|
return visitor.visitWritePropExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -301,6 +343,10 @@ export class InvokeMethodExpr extends Expression {
|
|||||||
this.builtin = <BuiltinMethod>method;
|
this.builtin = <BuiltinMethod>method;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof InvokeMethodExpr && this.receiver.isEquivalent(e.receiver) &&
|
||||||
|
this.name === e.name && this.builtin === e.builtin && areAllEquivalent(this.args, e.args);
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitInvokeMethodExpr(this, context);
|
return visitor.visitInvokeMethodExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -313,6 +359,10 @@ export class InvokeFunctionExpr extends Expression {
|
|||||||
sourceSpan?: ParseSourceSpan|null) {
|
sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(type, sourceSpan);
|
super(type, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof InvokeFunctionExpr && this.fn.isEquivalent(e.fn) &&
|
||||||
|
areAllEquivalent(this.args, e.args);
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitInvokeFunctionExpr(this, context);
|
return visitor.visitInvokeFunctionExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -325,6 +375,10 @@ export class InstantiateExpr extends Expression {
|
|||||||
sourceSpan?: ParseSourceSpan|null) {
|
sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(type, sourceSpan);
|
super(type, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof InstantiateExpr && this.classExpr.isEquivalent(e.classExpr) &&
|
||||||
|
areAllEquivalent(this.args, e.args);
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitInstantiateExpr(this, context);
|
return visitor.visitInstantiateExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -332,9 +386,14 @@ export class InstantiateExpr extends Expression {
|
|||||||
|
|
||||||
|
|
||||||
export class LiteralExpr extends Expression {
|
export class LiteralExpr extends Expression {
|
||||||
constructor(public value: any, type?: Type|null, sourceSpan?: ParseSourceSpan|null) {
|
constructor(
|
||||||
|
public value: number|string|boolean|null|undefined, type?: Type|null,
|
||||||
|
sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(type, sourceSpan);
|
super(type, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof LiteralExpr && this.value === e.value;
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitLiteralExpr(this, context);
|
return visitor.visitLiteralExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -347,6 +406,10 @@ export class ExternalExpr extends Expression {
|
|||||||
sourceSpan?: ParseSourceSpan|null) {
|
sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(type, sourceSpan);
|
super(type, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof ExternalExpr && this.value.name === e.value.name &&
|
||||||
|
this.value.moduleName === e.value.moduleName && this.value.runtime === e.value.runtime;
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitExternalExpr(this, context);
|
return visitor.visitExternalExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -355,6 +418,7 @@ export class ExternalExpr extends Expression {
|
|||||||
export class ExternalReference {
|
export class ExternalReference {
|
||||||
constructor(public moduleName: string|null, public name: string|null, public runtime?: any|null) {
|
constructor(public moduleName: string|null, public name: string|null, public runtime?: any|null) {
|
||||||
}
|
}
|
||||||
|
// Note: no isEquivalent method here as we use this as an interface too.
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ConditionalExpr extends Expression {
|
export class ConditionalExpr extends Expression {
|
||||||
@ -365,6 +429,10 @@ export class ConditionalExpr extends Expression {
|
|||||||
super(type || trueCase.type, sourceSpan);
|
super(type || trueCase.type, sourceSpan);
|
||||||
this.trueCase = trueCase;
|
this.trueCase = trueCase;
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof ConditionalExpr && this.condition.isEquivalent(e.condition) &&
|
||||||
|
this.trueCase.isEquivalent(e.trueCase) && nullSafeIsEquivalent(this.falseCase, e.falseCase);
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitConditionalExpr(this, context);
|
return visitor.visitConditionalExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -375,6 +443,9 @@ export class NotExpr extends Expression {
|
|||||||
constructor(public condition: Expression, sourceSpan?: ParseSourceSpan|null) {
|
constructor(public condition: Expression, sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(BOOL_TYPE, sourceSpan);
|
super(BOOL_TYPE, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof NotExpr && this.condition.isEquivalent(e.condition);
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitNotExpr(this, context);
|
return visitor.visitNotExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -384,6 +455,9 @@ export class AssertNotNull extends Expression {
|
|||||||
constructor(public condition: Expression, sourceSpan?: ParseSourceSpan|null) {
|
constructor(public condition: Expression, sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(condition.type, sourceSpan);
|
super(condition.type, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof AssertNotNull && this.condition.isEquivalent(e.condition);
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitAssertNotNullExpr(this, context);
|
return visitor.visitAssertNotNullExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -393,6 +467,9 @@ export class CastExpr extends Expression {
|
|||||||
constructor(public value: Expression, type?: Type|null, sourceSpan?: ParseSourceSpan|null) {
|
constructor(public value: Expression, type?: Type|null, sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(type, sourceSpan);
|
super(type, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof CastExpr && this.value.isEquivalent(e.value);
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitCastExpr(this, context);
|
return visitor.visitCastExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -401,6 +478,8 @@ export class CastExpr extends Expression {
|
|||||||
|
|
||||||
export class FnParam {
|
export class FnParam {
|
||||||
constructor(public name: string, public type: Type|null = null) {}
|
constructor(public name: string, public type: Type|null = null) {}
|
||||||
|
|
||||||
|
isEquivalent(param: FnParam): boolean { return this.name === param.name; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -410,6 +489,10 @@ export class FunctionExpr extends Expression {
|
|||||||
sourceSpan?: ParseSourceSpan|null) {
|
sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(type, sourceSpan);
|
super(type, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof FunctionExpr && areAllEquivalent(this.params, e.params) &&
|
||||||
|
areAllEquivalent(this.statements, e.statements);
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitFunctionExpr(this, context);
|
return visitor.visitFunctionExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -429,6 +512,10 @@ export class BinaryOperatorExpr extends Expression {
|
|||||||
super(type || lhs.type, sourceSpan);
|
super(type || lhs.type, sourceSpan);
|
||||||
this.lhs = lhs;
|
this.lhs = lhs;
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof BinaryOperatorExpr && this.operator === e.operator &&
|
||||||
|
this.lhs.isEquivalent(e.lhs) && this.rhs.isEquivalent(e.rhs);
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitBinaryOperatorExpr(this, context);
|
return visitor.visitBinaryOperatorExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -441,6 +528,10 @@ export class ReadPropExpr extends Expression {
|
|||||||
sourceSpan?: ParseSourceSpan|null) {
|
sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(type, sourceSpan);
|
super(type, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof ReadPropExpr && this.receiver.isEquivalent(e.receiver) &&
|
||||||
|
this.name === e.name;
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitReadPropExpr(this, context);
|
return visitor.visitReadPropExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -456,6 +547,10 @@ export class ReadKeyExpr extends Expression {
|
|||||||
sourceSpan?: ParseSourceSpan|null) {
|
sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(type, sourceSpan);
|
super(type, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof ReadKeyExpr && this.receiver.isEquivalent(e.receiver) &&
|
||||||
|
this.index.isEquivalent(e.index);
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitReadKeyExpr(this, context);
|
return visitor.visitReadKeyExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -471,6 +566,9 @@ export class LiteralArrayExpr extends Expression {
|
|||||||
super(type, sourceSpan);
|
super(type, sourceSpan);
|
||||||
this.entries = entries;
|
this.entries = entries;
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof LiteralArrayExpr && areAllEquivalent(this.entries, e.entries);
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitLiteralArrayExpr(this, context);
|
return visitor.visitLiteralArrayExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -478,6 +576,9 @@ export class LiteralArrayExpr extends Expression {
|
|||||||
|
|
||||||
export class LiteralMapEntry {
|
export class LiteralMapEntry {
|
||||||
constructor(public key: string, public value: Expression, public quoted: boolean) {}
|
constructor(public key: string, public value: Expression, public quoted: boolean) {}
|
||||||
|
isEquivalent(e: LiteralMapEntry): boolean {
|
||||||
|
return this.key === e.key && this.value.isEquivalent(e.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LiteralMapExpr extends Expression {
|
export class LiteralMapExpr extends Expression {
|
||||||
@ -489,6 +590,9 @@ export class LiteralMapExpr extends Expression {
|
|||||||
this.valueType = type.valueType;
|
this.valueType = type.valueType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof LiteralMapExpr && areAllEquivalent(this.entries, e.entries);
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitLiteralMapExpr(this, context);
|
return visitor.visitLiteralMapExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -498,6 +602,9 @@ export class CommaExpr extends Expression {
|
|||||||
constructor(public parts: Expression[], sourceSpan?: ParseSourceSpan|null) {
|
constructor(public parts: Expression[], sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(parts[parts.length - 1].type, sourceSpan);
|
super(parts[parts.length - 1].type, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(e: Expression): boolean {
|
||||||
|
return e instanceof CommaExpr && areAllEquivalent(this.parts, e.parts);
|
||||||
|
}
|
||||||
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
visitExpression(visitor: ExpressionVisitor, context: any): any {
|
||||||
return visitor.visitCommaExpr(this, context);
|
return visitor.visitCommaExpr(this, context);
|
||||||
}
|
}
|
||||||
@ -547,6 +654,11 @@ export abstract class Statement {
|
|||||||
this.modifiers = modifiers || [];
|
this.modifiers = modifiers || [];
|
||||||
this.sourceSpan = sourceSpan || null;
|
this.sourceSpan = sourceSpan || null;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Calculates whether this statement produces the same value as the given statement.
|
||||||
|
* Note: We don't check Types nor ParseSourceSpans nor function arguments.
|
||||||
|
*/
|
||||||
|
abstract isEquivalent(stmt: Statement): boolean;
|
||||||
|
|
||||||
abstract visitStatement(visitor: StatementVisitor, context: any): any;
|
abstract visitStatement(visitor: StatementVisitor, context: any): any;
|
||||||
|
|
||||||
@ -562,7 +674,10 @@ export class DeclareVarStmt extends Statement {
|
|||||||
super(modifiers, sourceSpan);
|
super(modifiers, sourceSpan);
|
||||||
this.type = type || value.type;
|
this.type = type || value.type;
|
||||||
}
|
}
|
||||||
|
isEquivalent(stmt: Statement): boolean {
|
||||||
|
return stmt instanceof DeclareVarStmt && this.name === stmt.name &&
|
||||||
|
this.value.isEquivalent(stmt.value);
|
||||||
|
}
|
||||||
visitStatement(visitor: StatementVisitor, context: any): any {
|
visitStatement(visitor: StatementVisitor, context: any): any {
|
||||||
return visitor.visitDeclareVarStmt(this, context);
|
return visitor.visitDeclareVarStmt(this, context);
|
||||||
}
|
}
|
||||||
@ -576,6 +691,10 @@ export class DeclareFunctionStmt extends Statement {
|
|||||||
super(modifiers, sourceSpan);
|
super(modifiers, sourceSpan);
|
||||||
this.type = type || null;
|
this.type = type || null;
|
||||||
}
|
}
|
||||||
|
isEquivalent(stmt: Statement): boolean {
|
||||||
|
return stmt instanceof DeclareFunctionStmt && areAllEquivalent(this.params, stmt.params) &&
|
||||||
|
areAllEquivalent(this.statements, stmt.statements);
|
||||||
|
}
|
||||||
|
|
||||||
visitStatement(visitor: StatementVisitor, context: any): any {
|
visitStatement(visitor: StatementVisitor, context: any): any {
|
||||||
return visitor.visitDeclareFunctionStmt(this, context);
|
return visitor.visitDeclareFunctionStmt(this, context);
|
||||||
@ -586,6 +705,9 @@ export class ExpressionStatement extends Statement {
|
|||||||
constructor(public expr: Expression, sourceSpan?: ParseSourceSpan|null) {
|
constructor(public expr: Expression, sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(null, sourceSpan);
|
super(null, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(stmt: Statement): boolean {
|
||||||
|
return stmt instanceof ExpressionStatement && this.expr.isEquivalent(stmt.expr);
|
||||||
|
}
|
||||||
|
|
||||||
visitStatement(visitor: StatementVisitor, context: any): any {
|
visitStatement(visitor: StatementVisitor, context: any): any {
|
||||||
return visitor.visitExpressionStmt(this, context);
|
return visitor.visitExpressionStmt(this, context);
|
||||||
@ -597,6 +719,9 @@ export class ReturnStatement extends Statement {
|
|||||||
constructor(public value: Expression, sourceSpan?: ParseSourceSpan|null) {
|
constructor(public value: Expression, sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(null, sourceSpan);
|
super(null, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(stmt: Statement): boolean {
|
||||||
|
return stmt instanceof ReturnStatement && this.value.isEquivalent(stmt.value);
|
||||||
|
}
|
||||||
visitStatement(visitor: StatementVisitor, context: any): any {
|
visitStatement(visitor: StatementVisitor, context: any): any {
|
||||||
return visitor.visitReturnStmt(this, context);
|
return visitor.visitReturnStmt(this, context);
|
||||||
}
|
}
|
||||||
@ -617,6 +742,7 @@ export class ClassField extends AbstractClassPart {
|
|||||||
constructor(public name: string, type?: Type|null, modifiers: StmtModifier[]|null = null) {
|
constructor(public name: string, type?: Type|null, modifiers: StmtModifier[]|null = null) {
|
||||||
super(type, modifiers);
|
super(type, modifiers);
|
||||||
}
|
}
|
||||||
|
isEquivalent(f: ClassField) { return this.name === f.name; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -626,6 +752,9 @@ export class ClassMethod extends AbstractClassPart {
|
|||||||
type?: Type|null, modifiers: StmtModifier[]|null = null) {
|
type?: Type|null, modifiers: StmtModifier[]|null = null) {
|
||||||
super(type, modifiers);
|
super(type, modifiers);
|
||||||
}
|
}
|
||||||
|
isEquivalent(m: ClassMethod) {
|
||||||
|
return this.name === m.name && areAllEquivalent(this.body, m.body);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -635,6 +764,9 @@ export class ClassGetter extends AbstractClassPart {
|
|||||||
modifiers: StmtModifier[]|null = null) {
|
modifiers: StmtModifier[]|null = null) {
|
||||||
super(type, modifiers);
|
super(type, modifiers);
|
||||||
}
|
}
|
||||||
|
isEquivalent(m: ClassGetter) {
|
||||||
|
return this.name === m.name && areAllEquivalent(this.body, m.body);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -646,6 +778,14 @@ export class ClassStmt extends Statement {
|
|||||||
sourceSpan?: ParseSourceSpan|null) {
|
sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(modifiers, sourceSpan);
|
super(modifiers, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(stmt: Statement): boolean {
|
||||||
|
return stmt instanceof ClassStmt && this.name === stmt.name &&
|
||||||
|
nullSafeIsEquivalent(this.parent, stmt.parent) &&
|
||||||
|
areAllEquivalent(this.fields, stmt.fields) &&
|
||||||
|
areAllEquivalent(this.getters, stmt.getters) &&
|
||||||
|
this.constructorMethod.isEquivalent(stmt.constructorMethod) &&
|
||||||
|
areAllEquivalent(this.methods, stmt.methods);
|
||||||
|
}
|
||||||
visitStatement(visitor: StatementVisitor, context: any): any {
|
visitStatement(visitor: StatementVisitor, context: any): any {
|
||||||
return visitor.visitDeclareClassStmt(this, context);
|
return visitor.visitDeclareClassStmt(this, context);
|
||||||
}
|
}
|
||||||
@ -658,6 +798,11 @@ export class IfStmt extends Statement {
|
|||||||
public falseCase: Statement[] = [], sourceSpan?: ParseSourceSpan|null) {
|
public falseCase: Statement[] = [], sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(null, sourceSpan);
|
super(null, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(stmt: Statement): boolean {
|
||||||
|
return stmt instanceof IfStmt && this.condition.isEquivalent(stmt.condition) &&
|
||||||
|
areAllEquivalent(this.trueCase, stmt.trueCase) &&
|
||||||
|
areAllEquivalent(this.falseCase, stmt.falseCase);
|
||||||
|
}
|
||||||
visitStatement(visitor: StatementVisitor, context: any): any {
|
visitStatement(visitor: StatementVisitor, context: any): any {
|
||||||
return visitor.visitIfStmt(this, context);
|
return visitor.visitIfStmt(this, context);
|
||||||
}
|
}
|
||||||
@ -668,6 +813,7 @@ export class CommentStmt extends Statement {
|
|||||||
constructor(public comment: string, sourceSpan?: ParseSourceSpan|null) {
|
constructor(public comment: string, sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(null, sourceSpan);
|
super(null, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(stmt: Statement): boolean { return stmt instanceof CommentStmt; }
|
||||||
visitStatement(visitor: StatementVisitor, context: any): any {
|
visitStatement(visitor: StatementVisitor, context: any): any {
|
||||||
return visitor.visitCommentStmt(this, context);
|
return visitor.visitCommentStmt(this, context);
|
||||||
}
|
}
|
||||||
@ -680,6 +826,10 @@ export class TryCatchStmt extends Statement {
|
|||||||
sourceSpan?: ParseSourceSpan|null) {
|
sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(null, sourceSpan);
|
super(null, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(stmt: Statement): boolean {
|
||||||
|
return stmt instanceof TryCatchStmt && areAllEquivalent(this.bodyStmts, stmt.bodyStmts) &&
|
||||||
|
areAllEquivalent(this.catchStmts, stmt.catchStmts);
|
||||||
|
}
|
||||||
visitStatement(visitor: StatementVisitor, context: any): any {
|
visitStatement(visitor: StatementVisitor, context: any): any {
|
||||||
return visitor.visitTryCatchStmt(this, context);
|
return visitor.visitTryCatchStmt(this, context);
|
||||||
}
|
}
|
||||||
@ -690,6 +840,9 @@ export class ThrowStmt extends Statement {
|
|||||||
constructor(public error: Expression, sourceSpan?: ParseSourceSpan|null) {
|
constructor(public error: Expression, sourceSpan?: ParseSourceSpan|null) {
|
||||||
super(null, sourceSpan);
|
super(null, sourceSpan);
|
||||||
}
|
}
|
||||||
|
isEquivalent(stmt: ThrowStmt): boolean {
|
||||||
|
return stmt instanceof TryCatchStmt && this.error.isEquivalent(stmt.error);
|
||||||
|
}
|
||||||
visitStatement(visitor: StatementVisitor, context: any): any {
|
visitStatement(visitor: StatementVisitor, context: any): any {
|
||||||
return visitor.visitThrowStmt(this, context);
|
return visitor.visitThrowStmt(this, context);
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,7 @@ export function downgradeComponent(info: {
|
|||||||
facade.createComponent(projectableNodes);
|
facade.createComponent(projectableNodes);
|
||||||
facade.setupInputs(needsNgZone, info.propagateDigest);
|
facade.setupInputs(needsNgZone, info.propagateDigest);
|
||||||
facade.setupOutputs();
|
facade.setupOutputs();
|
||||||
facade.registerCleanup(needsNgZone);
|
facade.registerCleanup();
|
||||||
|
|
||||||
injectorPromise.resolve(facade.getInjector());
|
injectorPromise.resolve(facade.getInjector());
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ export class DowngradeComponentAdapter {
|
|||||||
private componentRef: ComponentRef<any>;
|
private componentRef: ComponentRef<any>;
|
||||||
private component: any;
|
private component: any;
|
||||||
private changeDetector: ChangeDetectorRef;
|
private changeDetector: ChangeDetectorRef;
|
||||||
private appRef: ApplicationRef;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private element: angular.IAugmentedJQuery, private attrs: angular.IAttributes,
|
private element: angular.IAugmentedJQuery, private attrs: angular.IAttributes,
|
||||||
@ -35,7 +34,6 @@ export class DowngradeComponentAdapter {
|
|||||||
private componentFactory: ComponentFactory<any>,
|
private componentFactory: ComponentFactory<any>,
|
||||||
private wrapCallback: <T>(cb: () => T) => () => T) {
|
private wrapCallback: <T>(cb: () => T) => () => T) {
|
||||||
this.componentScope = scope.$new();
|
this.componentScope = scope.$new();
|
||||||
this.appRef = parentInjector.get(ApplicationRef);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compileContents(): Node[][] {
|
compileContents(): Node[][] {
|
||||||
@ -140,8 +138,7 @@ export class DowngradeComponentAdapter {
|
|||||||
(<OnChanges>this.component).ngOnChanges(inputChanges !);
|
(<OnChanges>this.component).ngOnChanges(inputChanges !);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If opted out of propagating digests, invoke change detection
|
// If opted out of propagating digests, invoke change detection when inputs change.
|
||||||
// when inputs change
|
|
||||||
if (!propagateDigest) {
|
if (!propagateDigest) {
|
||||||
detectChanges();
|
detectChanges();
|
||||||
}
|
}
|
||||||
@ -152,9 +149,16 @@ export class DowngradeComponentAdapter {
|
|||||||
this.componentScope.$watch(this.wrapCallback(detectChanges));
|
this.componentScope.$watch(this.wrapCallback(detectChanges));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach the view so that it will be dirty-checked.
|
// If necessary, attach the view so that it will be dirty-checked.
|
||||||
if (needsNgZone) {
|
// (Allow time for the initial input values to be set and `ngOnChanges()` to be called.)
|
||||||
this.appRef.attachView(this.componentRef.hostView);
|
if (needsNgZone || !propagateDigest) {
|
||||||
|
let unwatch: Function|null = this.componentScope.$watch(() => {
|
||||||
|
unwatch !();
|
||||||
|
unwatch = null;
|
||||||
|
|
||||||
|
const appRef = this.parentInjector.get<ApplicationRef>(ApplicationRef);
|
||||||
|
appRef.attachView(this.componentRef.hostView);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,15 +206,14 @@ export class DowngradeComponentAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerCleanup(needsNgZone: boolean) {
|
registerCleanup() {
|
||||||
|
const destroyComponentRef = this.wrapCallback(() => this.componentRef.destroy());
|
||||||
|
|
||||||
this.element.on !('$destroy', () => {
|
this.element.on !('$destroy', () => {
|
||||||
this.componentScope.$destroy();
|
this.componentScope.$destroy();
|
||||||
this.componentRef.injector.get(TestabilityRegistry)
|
this.componentRef.injector.get(TestabilityRegistry)
|
||||||
.unregisterApplication(this.componentRef.location.nativeElement);
|
.unregisterApplication(this.componentRef.location.nativeElement);
|
||||||
this.componentRef.destroy();
|
destroyComponentRef();
|
||||||
if (needsNgZone) {
|
|
||||||
this.appRef.detachView(this.componentRef.hostView);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ export function main() {
|
|||||||
let $compile = undefined as any;
|
let $compile = undefined as any;
|
||||||
let $parse = undefined as any;
|
let $parse = undefined as any;
|
||||||
let componentFactory: ComponentFactory<any>; // testbed
|
let componentFactory: ComponentFactory<any>; // testbed
|
||||||
let wrapCallback = undefined as any;
|
let wrapCallback = (cb: any) => cb;
|
||||||
|
|
||||||
content = `
|
content = `
|
||||||
<h1> new component </h1>
|
<h1> new component </h1>
|
||||||
@ -183,7 +183,7 @@ export function main() {
|
|||||||
expect(registry.getAllTestabilities().length).toEqual(0);
|
expect(registry.getAllTestabilities().length).toEqual(0);
|
||||||
adapter.createComponent([]);
|
adapter.createComponent([]);
|
||||||
expect(registry.getAllTestabilities().length).toEqual(1);
|
expect(registry.getAllTestabilities().length).toEqual(1);
|
||||||
adapter.registerCleanup(true);
|
adapter.registerCleanup();
|
||||||
element.remove !();
|
element.remove !();
|
||||||
expect(registry.getAllTestabilities().length).toEqual(0);
|
expect(registry.getAllTestabilities().length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {ChangeDetectorRef, Compiler, Component, ComponentFactoryResolver, EventEmitter, Injector, Input, NgModule, NgModuleRef, OnChanges, OnDestroy, SimpleChanges, destroyPlatform} from '@angular/core';
|
import {ChangeDetectorRef, Compiler, Component, ComponentFactoryResolver, EventEmitter, Injector, Input, NgModule, NgModuleRef, OnChanges, OnDestroy, SimpleChanges, destroyPlatform} from '@angular/core';
|
||||||
import {async} from '@angular/core/testing';
|
import {async, fakeAsync, tick} from '@angular/core/testing';
|
||||||
import {BrowserModule} from '@angular/platform-browser';
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||||
import * as angular from '@angular/upgrade/src/common/angular1';
|
import * as angular from '@angular/upgrade/src/common/angular1';
|
||||||
@ -274,6 +274,42 @@ export function main() {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should still run normal Angular change-detection regardless of `propagateDigest`',
|
||||||
|
fakeAsync(() => {
|
||||||
|
let ng2Component: Ng2Component;
|
||||||
|
|
||||||
|
@Component({selector: 'ng2', template: '{{ value }}'})
|
||||||
|
class Ng2Component {
|
||||||
|
value = 'foo';
|
||||||
|
constructor() { setTimeout(() => this.value = 'bar', 1000); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [BrowserModule, UpgradeModule],
|
||||||
|
declarations: [Ng2Component],
|
||||||
|
entryComponents: [Ng2Component]
|
||||||
|
})
|
||||||
|
class Ng2Module {
|
||||||
|
ngDoBootstrap() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ng1Module =
|
||||||
|
angular.module('ng1', [])
|
||||||
|
.directive(
|
||||||
|
'ng2A', downgradeComponent({component: Ng2Component, propagateDigest: true}))
|
||||||
|
.directive(
|
||||||
|
'ng2B', downgradeComponent({component: Ng2Component, propagateDigest: false}));
|
||||||
|
|
||||||
|
const element = html('<ng2-a></ng2-a> | <ng2-b></ng2-b>');
|
||||||
|
|
||||||
|
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
|
||||||
|
expect(element.textContent).toBe('foo | foo');
|
||||||
|
|
||||||
|
tick(1000);
|
||||||
|
expect(element.textContent).toBe('bar | bar');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should initialize inputs in time for `ngOnChanges`', async(() => {
|
it('should initialize inputs in time for `ngOnChanges`', async(() => {
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ng2',
|
selector: 'ng2',
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Component, Inject, Injector, Input, NgModule, NgZone, OnChanges, StaticProvider, destroyPlatform} from '@angular/core';
|
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ApplicationRef, Component, DoCheck, Inject, Injector, Input, NgModule, NgZone, OnChanges, OnDestroy, OnInit, StaticProvider, ViewRef, destroyPlatform} from '@angular/core';
|
||||||
import {async, fakeAsync, tick} from '@angular/core/testing';
|
import {async, fakeAsync, tick} from '@angular/core/testing';
|
||||||
import {BrowserModule} from '@angular/platform-browser';
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||||
@ -16,7 +16,7 @@ import {$ROOT_SCOPE, INJECTOR_KEY, LAZY_MODULE_REF} from '@angular/upgrade/src/c
|
|||||||
import {LazyModuleRef} from '@angular/upgrade/src/common/util';
|
import {LazyModuleRef} from '@angular/upgrade/src/common/util';
|
||||||
import {downgradeComponent, downgradeModule} from '@angular/upgrade/static';
|
import {downgradeComponent, downgradeModule} from '@angular/upgrade/static';
|
||||||
|
|
||||||
import {html} from '../test_helpers';
|
import {html, multiTrim} from '../test_helpers';
|
||||||
|
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
@ -170,6 +170,42 @@ export function main() {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should destroy components inside the Angular zone', async(() => {
|
||||||
|
let destroyedInTheZone = false;
|
||||||
|
|
||||||
|
@Component({selector: 'ng2', template: ''})
|
||||||
|
class Ng2Component implements OnDestroy {
|
||||||
|
ngOnDestroy() { destroyedInTheZone = NgZone.isInAngularZone(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Ng2Component],
|
||||||
|
entryComponents: [Ng2Component],
|
||||||
|
imports: [BrowserModule],
|
||||||
|
})
|
||||||
|
class Ng2Module {
|
||||||
|
ngDoBootstrap() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
||||||
|
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
||||||
|
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
||||||
|
const ng1Module =
|
||||||
|
angular.module('ng1', [lazyModuleName])
|
||||||
|
.directive(
|
||||||
|
'ng2', downgradeComponent({component: Ng2Component, propagateDigest}));
|
||||||
|
|
||||||
|
const element = html('<ng2 ng-if="!hideNg2"></ng2>');
|
||||||
|
const $injector = angular.bootstrap(element, [ng1Module.name]);
|
||||||
|
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
|
||||||
|
|
||||||
|
// Wait for the module to be bootstrapped.
|
||||||
|
setTimeout(() => {
|
||||||
|
$rootScope.$apply('hideNg2 = true');
|
||||||
|
expect(destroyedInTheZone).toBe(true);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should propagate input changes inside the Angular zone', async(() => {
|
it('should propagate input changes inside the Angular zone', async(() => {
|
||||||
let ng2Component: Ng2Component;
|
let ng2Component: Ng2Component;
|
||||||
|
|
||||||
@ -270,6 +306,210 @@ export function main() {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should run the lifecycle hooks in the correct order', async(() => {
|
||||||
|
const logs: string[] = [];
|
||||||
|
let rootScope: angular.IRootScopeService;
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ng2',
|
||||||
|
template: `
|
||||||
|
{{ value }}
|
||||||
|
<button (click)="value = 'qux'"></button>
|
||||||
|
<ng-content></ng-content>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class Ng2Component implements AfterContentChecked,
|
||||||
|
AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy,
|
||||||
|
OnInit {
|
||||||
|
@Input() value = 'foo';
|
||||||
|
|
||||||
|
ngAfterContentChecked() { this.log('AfterContentChecked'); }
|
||||||
|
ngAfterContentInit() { this.log('AfterContentInit'); }
|
||||||
|
ngAfterViewChecked() { this.log('AfterViewChecked'); }
|
||||||
|
ngAfterViewInit() { this.log('AfterViewInit'); }
|
||||||
|
ngDoCheck() { this.log('DoCheck'); }
|
||||||
|
ngOnChanges() { this.log('OnChanges'); }
|
||||||
|
ngOnDestroy() { this.log('OnDestroy'); }
|
||||||
|
ngOnInit() { this.log('OnInit'); }
|
||||||
|
|
||||||
|
private log(hook: string) { logs.push(`${hook}(${this.value})`); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Ng2Component],
|
||||||
|
entryComponents: [Ng2Component],
|
||||||
|
imports: [BrowserModule],
|
||||||
|
})
|
||||||
|
class Ng2Module {
|
||||||
|
ngDoBootstrap() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
||||||
|
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
||||||
|
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
||||||
|
const ng1Module =
|
||||||
|
angular.module('ng1', [lazyModuleName])
|
||||||
|
.directive('ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
|
||||||
|
.run(($rootScope: angular.IRootScopeService) => {
|
||||||
|
rootScope = $rootScope;
|
||||||
|
rootScope.value = 'bar';
|
||||||
|
});
|
||||||
|
|
||||||
|
const element =
|
||||||
|
html('<div><ng2 value="{{ value }}" ng-if="!hideNg2">Content</ng2></div>');
|
||||||
|
angular.bootstrap(element, [ng1Module.name]);
|
||||||
|
|
||||||
|
setTimeout(() => { // Wait for the module to be bootstrapped.
|
||||||
|
setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs.
|
||||||
|
const button = element.querySelector('button') !;
|
||||||
|
|
||||||
|
// Once initialized.
|
||||||
|
expect(multiTrim(element.textContent)).toBe('bar Content');
|
||||||
|
expect(logs).toEqual([
|
||||||
|
// `ngOnChanges()` call triggered directly through the `inputChanges` $watcher.
|
||||||
|
'OnChanges(bar)',
|
||||||
|
// Initial CD triggered directly through the `detectChanges()` or `inputChanges`
|
||||||
|
// $watcher (for `propagateDigest` true/false respectively).
|
||||||
|
'OnInit(bar)',
|
||||||
|
'DoCheck(bar)',
|
||||||
|
'AfterContentInit(bar)',
|
||||||
|
'AfterContentChecked(bar)',
|
||||||
|
'AfterViewInit(bar)',
|
||||||
|
'AfterViewChecked(bar)',
|
||||||
|
...(propagateDigest ?
|
||||||
|
[
|
||||||
|
// CD triggered directly through the `detectChanges()` $watcher (2nd
|
||||||
|
// $digest).
|
||||||
|
'DoCheck(bar)',
|
||||||
|
'AfterContentChecked(bar)',
|
||||||
|
'AfterViewChecked(bar)',
|
||||||
|
] :
|
||||||
|
[]),
|
||||||
|
// CD triggered due to entering/leaving the NgZone (in `downgradeFn()`).
|
||||||
|
'DoCheck(bar)',
|
||||||
|
'AfterContentChecked(bar)',
|
||||||
|
'AfterViewChecked(bar)',
|
||||||
|
]);
|
||||||
|
logs.length = 0;
|
||||||
|
|
||||||
|
// Change inputs and run `$digest`.
|
||||||
|
rootScope.$apply('value = "baz"');
|
||||||
|
expect(multiTrim(element.textContent)).toBe('baz Content');
|
||||||
|
expect(logs).toEqual([
|
||||||
|
// `ngOnChanges()` call triggered directly through the `inputChanges` $watcher.
|
||||||
|
'OnChanges(baz)',
|
||||||
|
// `propagateDigest: true` (3 CD runs):
|
||||||
|
// - CD triggered due to entering/leaving the NgZone (in `inputChanges`
|
||||||
|
// $watcher).
|
||||||
|
// - CD triggered directly through the `detectChanges()` $watcher.
|
||||||
|
// - CD triggered due to entering/leaving the NgZone (in `detectChanges`
|
||||||
|
// $watcher).
|
||||||
|
// `propagateDigest: false` (2 CD runs):
|
||||||
|
// - CD triggered directly through the `inputChanges` $watcher.
|
||||||
|
// - CD triggered due to entering/leaving the NgZone (in `inputChanges`
|
||||||
|
// $watcher).
|
||||||
|
'DoCheck(baz)',
|
||||||
|
'AfterContentChecked(baz)',
|
||||||
|
'AfterViewChecked(baz)',
|
||||||
|
'DoCheck(baz)',
|
||||||
|
'AfterContentChecked(baz)',
|
||||||
|
'AfterViewChecked(baz)',
|
||||||
|
...(propagateDigest ?
|
||||||
|
[
|
||||||
|
'DoCheck(baz)',
|
||||||
|
'AfterContentChecked(baz)',
|
||||||
|
'AfterViewChecked(baz)',
|
||||||
|
] :
|
||||||
|
[]),
|
||||||
|
]);
|
||||||
|
logs.length = 0;
|
||||||
|
|
||||||
|
// Run `$digest` (without changing inputs).
|
||||||
|
rootScope.$digest();
|
||||||
|
expect(multiTrim(element.textContent)).toBe('baz Content');
|
||||||
|
expect(logs).toEqual(
|
||||||
|
propagateDigest ?
|
||||||
|
[
|
||||||
|
// CD triggered directly through the `detectChanges()` $watcher.
|
||||||
|
'DoCheck(baz)',
|
||||||
|
'AfterContentChecked(baz)',
|
||||||
|
'AfterViewChecked(baz)',
|
||||||
|
// CD triggered due to entering/leaving the NgZone (in the above $watcher).
|
||||||
|
'DoCheck(baz)',
|
||||||
|
'AfterContentChecked(baz)',
|
||||||
|
'AfterViewChecked(baz)',
|
||||||
|
] :
|
||||||
|
[]);
|
||||||
|
logs.length = 0;
|
||||||
|
|
||||||
|
// Trigger change detection (without changing inputs).
|
||||||
|
button.click();
|
||||||
|
expect(multiTrim(element.textContent)).toBe('qux Content');
|
||||||
|
expect(logs).toEqual([
|
||||||
|
'DoCheck(qux)',
|
||||||
|
'AfterContentChecked(qux)',
|
||||||
|
'AfterViewChecked(qux)',
|
||||||
|
]);
|
||||||
|
logs.length = 0;
|
||||||
|
|
||||||
|
// Destroy the component.
|
||||||
|
rootScope.$apply('hideNg2 = true');
|
||||||
|
expect(logs).toEqual([
|
||||||
|
'OnDestroy(qux)',
|
||||||
|
]);
|
||||||
|
logs.length = 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should detach hostViews from the ApplicationRef once destroyed', async(() => {
|
||||||
|
let ng2Component: Ng2Component;
|
||||||
|
|
||||||
|
@Component({selector: 'ng2', template: ''})
|
||||||
|
class Ng2Component {
|
||||||
|
constructor(public appRef: ApplicationRef) {
|
||||||
|
ng2Component = this;
|
||||||
|
spyOn(appRef, 'attachView').and.callThrough();
|
||||||
|
spyOn(appRef, 'detachView').and.callThrough();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Ng2Component],
|
||||||
|
entryComponents: [Ng2Component],
|
||||||
|
imports: [BrowserModule],
|
||||||
|
})
|
||||||
|
class Ng2Module {
|
||||||
|
ngDoBootstrap() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bootstrapFn = (extraProviders: StaticProvider[]) =>
|
||||||
|
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
||||||
|
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
||||||
|
const ng1Module =
|
||||||
|
angular.module('ng1', [lazyModuleName])
|
||||||
|
.directive(
|
||||||
|
'ng2', downgradeComponent({component: Ng2Component, propagateDigest}));
|
||||||
|
|
||||||
|
const element = html('<ng2 ng-if="!hideNg2"></ng2>');
|
||||||
|
const $injector = angular.bootstrap(element, [ng1Module.name]);
|
||||||
|
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
|
||||||
|
|
||||||
|
setTimeout(() => { // Wait for the module to be bootstrapped.
|
||||||
|
setTimeout(() => { // Wait for the hostView to be attached (during the `$digest`).
|
||||||
|
const hostView: ViewRef =
|
||||||
|
(ng2Component.appRef.attachView as jasmine.Spy).calls.mostRecent().args[0];
|
||||||
|
|
||||||
|
expect(hostView.destroyed).toBe(false);
|
||||||
|
|
||||||
|
$rootScope.$apply('hideNg2 = true');
|
||||||
|
|
||||||
|
expect(hostView.destroyed).toBe(true);
|
||||||
|
expect(ng2Component.appRef.detachView).toHaveBeenCalledWith(hostView);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
it('should only retrieve the Angular zone once (and cache it for later use)',
|
it('should only retrieve the Angular zone once (and cache it for later use)',
|
||||||
fakeAsync(() => {
|
fakeAsync(() => {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {PlatformRef, Type} from '@angular/core';
|
import {NgZone, PlatformRef, Type} from '@angular/core';
|
||||||
import * as angular from '@angular/upgrade/src/common/angular1';
|
import * as angular from '@angular/upgrade/src/common/angular1';
|
||||||
import {$ROOT_SCOPE} from '@angular/upgrade/src/common/constants';
|
import {$ROOT_SCOPE} from '@angular/upgrade/src/common/constants';
|
||||||
import {UpgradeModule} from '@angular/upgrade/static';
|
import {UpgradeModule} from '@angular/upgrade/static';
|
||||||
@ -18,11 +18,19 @@ export function bootstrap(
|
|||||||
// We bootstrap the Angular module first; then when it is ready (async) we bootstrap the AngularJS
|
// We bootstrap the Angular module first; then when it is ready (async) we bootstrap the AngularJS
|
||||||
// module on the bootstrap element (also ensuring that AngularJS errors will fail the test).
|
// module on the bootstrap element (also ensuring that AngularJS errors will fail the test).
|
||||||
return platform.bootstrapModule(Ng2Module).then(ref => {
|
return platform.bootstrapModule(Ng2Module).then(ref => {
|
||||||
|
const ngZone = ref.injector.get<NgZone>(NgZone);
|
||||||
const upgrade = ref.injector.get(UpgradeModule);
|
const upgrade = ref.injector.get(UpgradeModule);
|
||||||
const failHardModule: any = ($provide: angular.IProvideService) => {
|
const failHardModule: any = ($provide: angular.IProvideService) => {
|
||||||
$provide.value('$exceptionHandler', (err: any) => { throw err; });
|
$provide.value('$exceptionHandler', (err: any) => { throw err; });
|
||||||
};
|
};
|
||||||
upgrade.bootstrap(element, [failHardModule, ng1Module.name]);
|
|
||||||
|
// The `bootstrap()` helper is used for convenience in tests, so that we don't have to inject
|
||||||
|
// and call `upgrade.bootstrap()` on every Angular module.
|
||||||
|
// In order to closer emulate what happens in real application, ensure AngularJS is bootstrapped
|
||||||
|
// inside the Angular zone.
|
||||||
|
//
|
||||||
|
ngZone.run(() => upgrade.bootstrap(element, [failHardModule, ng1Module.name]));
|
||||||
|
|
||||||
return upgrade;
|
return upgrade;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user