refactor(compiler-cli): cleanup API for transformer based ngc
This is in preparation for watch mode.
This commit is contained in:
@ -20,7 +20,7 @@ export {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Sp
|
|||||||
export * from './src/transformers/api';
|
export * from './src/transformers/api';
|
||||||
export * from './src/transformers/entry_points';
|
export * from './src/transformers/entry_points';
|
||||||
|
|
||||||
export {performCompilation} from './src/perform-compile';
|
export {performCompilation, readConfiguration, formatDiagnostics, calcProjectFileAndBasePath, createNgCompilerOptions} from './src/perform-compile';
|
||||||
|
|
||||||
// TODO(hansl): moving to Angular 4 need to update this API.
|
// TODO(hansl): moving to Angular 4 need to update this API.
|
||||||
export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api';
|
export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api';
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, GeneratedFile, NgAnalyzedModules, ParseSourceSpan, Statement, StaticReflector, TypeScriptEmitter, createAotCompiler} from '@angular/compiler';
|
import {AotCompiler, AotCompilerHost, AotCompilerOptions, EmitterVisitorContext, GeneratedFile, NgAnalyzedModules, ParseSourceSpan, Statement, StaticReflector, TypeScriptEmitter, createAotCompiler} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {Diagnostic, DiagnosticCategory} from '../transformers/api';
|
import {Diagnostic} from '../transformers/api';
|
||||||
|
|
||||||
interface FactoryInfo {
|
interface FactoryInfo {
|
||||||
source: ts.SourceFile;
|
source: ts.SourceFile;
|
||||||
@ -143,7 +143,7 @@ export class TypeChecker {
|
|||||||
const diagnosticsList = diagnosticsFor(fileName);
|
const diagnosticsList = diagnosticsFor(fileName);
|
||||||
diagnosticsList.push({
|
diagnosticsList.push({
|
||||||
message: diagnosticMessageToString(diagnostic.messageText),
|
message: diagnosticMessageToString(diagnostic.messageText),
|
||||||
category: diagnosticCategoryConverter(diagnostic.category), span
|
category: diagnostic.category, span
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,11 +166,6 @@ function diagnosticMessageToString(message: ts.DiagnosticMessageChain | string):
|
|||||||
return ts.flattenDiagnosticMessageText(message, '\n');
|
return ts.flattenDiagnosticMessageText(message, '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function diagnosticCategoryConverter(kind: ts.DiagnosticCategory) {
|
|
||||||
// The diagnostics kind matches ts.DiagnosticCategory. Review this code if this changes.
|
|
||||||
return kind as any as DiagnosticCategory;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createFactoryInfo(emitter: TypeScriptEmitter, file: GeneratedFile): FactoryInfo {
|
function createFactoryInfo(emitter: TypeScriptEmitter, file: GeneratedFile): FactoryInfo {
|
||||||
const {sourceText, context} =
|
const {sourceText, context} =
|
||||||
emitter.emitStatementsAndContext(file.srcFileUrl, file.genFileUrl, file.stmts !);
|
emitter.emitStatementsAndContext(file.srcFileUrl, file.genFileUrl, file.stmts !);
|
||||||
|
@ -14,15 +14,87 @@ import * as ts from 'typescript';
|
|||||||
import * as tsc from '@angular/tsc-wrapped';
|
import * as tsc from '@angular/tsc-wrapped';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as ngc from './ngc';
|
import * as api from './transformers/api';
|
||||||
|
import * as ngc from './transformers/entry_points';
|
||||||
|
import {performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration} from './perform-compile';
|
||||||
|
|
||||||
import {isSyntaxError} from '@angular/compiler';
|
import {isSyntaxError} from '@angular/compiler';
|
||||||
|
|
||||||
import {readConfiguration} from './perform-compile';
|
|
||||||
|
|
||||||
import {CodeGenerator} from './codegen';
|
import {CodeGenerator} from './codegen';
|
||||||
|
|
||||||
function codegen(
|
export function main(
|
||||||
|
args: string[], consoleError: (s: string) => void = console.error): Promise<number> {
|
||||||
|
const parsedArgs = require('minimist')(args);
|
||||||
|
const {rootNames, options, errors: configErrors} = readCommandLineAndConfiguration(parsedArgs);
|
||||||
|
if (configErrors.length) {
|
||||||
|
return Promise.resolve(reportErrorsAndExit(options, configErrors, consoleError));
|
||||||
|
}
|
||||||
|
if (options.disableTransformerPipeline) {
|
||||||
|
return disabledTransformerPipelineNgcMain(parsedArgs, consoleError);
|
||||||
|
}
|
||||||
|
const {diagnostics: compileDiags} = performCompilation(rootNames, options);
|
||||||
|
return Promise.resolve(reportErrorsAndExit(options, compileDiags, consoleError));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mainSync(
|
||||||
|
args: string[], consoleError: (s: string) => void = console.error): number {
|
||||||
|
const parsedArgs = require('minimist')(args);
|
||||||
|
const {rootNames, options, errors: configErrors} = readCommandLineAndConfiguration(parsedArgs);
|
||||||
|
if (configErrors.length) {
|
||||||
|
return reportErrorsAndExit(options, configErrors, consoleError);
|
||||||
|
}
|
||||||
|
const {diagnostics: compileDiags} = performCompilation(rootNames, options);
|
||||||
|
return reportErrorsAndExit(options, compileDiags, consoleError);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readCommandLineAndConfiguration(args: any): ParsedConfiguration {
|
||||||
|
const project = args.p || args.project || '.';
|
||||||
|
const allDiagnostics: Diagnostics = [];
|
||||||
|
const config = readConfiguration(project);
|
||||||
|
const options = mergeCommandLineParams(args, config.options);
|
||||||
|
return {rootNames: config.rootNames, options, errors: config.errors};
|
||||||
|
}
|
||||||
|
|
||||||
|
function reportErrorsAndExit(
|
||||||
|
options: api.CompilerOptions, allDiagnostics: Diagnostics,
|
||||||
|
consoleError: (s: string) => void = console.error): number {
|
||||||
|
const exitCode = allDiagnostics.some(d => d.category === ts.DiagnosticCategory.Error) ? 1 : 0;
|
||||||
|
if (allDiagnostics.length) {
|
||||||
|
consoleError(formatDiagnostics(options, allDiagnostics));
|
||||||
|
}
|
||||||
|
return exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeCommandLineParams(
|
||||||
|
cliArgs: {[k: string]: string}, options: api.CompilerOptions): api.CompilerOptions {
|
||||||
|
// TODO: also merge in tsc command line parameters by calling
|
||||||
|
// ts.readCommandLine.
|
||||||
|
if (cliArgs.i18nFile) options.i18nInFile = cliArgs.i18nFile;
|
||||||
|
if (cliArgs.i18nFormat) options.i18nInFormat = cliArgs.i18nFormat;
|
||||||
|
if (cliArgs.locale) options.i18nInLocale = cliArgs.locale;
|
||||||
|
const mt = cliArgs.missingTranslation;
|
||||||
|
if (mt === 'error' || mt === 'warning' || mt === 'ignore') {
|
||||||
|
options.i18nInMissingTranslations = mt;
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
function disabledTransformerPipelineNgcMain(
|
||||||
|
args: any, consoleError: (s: string) => void = console.error): Promise<number> {
|
||||||
|
const cliOptions = new tsc.NgcCliOptions(args);
|
||||||
|
const project = args.p || args.project || '.';
|
||||||
|
return tsc.main(project, cliOptions, disabledTransformerPipelineCodegen)
|
||||||
|
.then(() => 0)
|
||||||
|
.catch(e => {
|
||||||
|
if (e instanceof tsc.UserError || isSyntaxError(e)) {
|
||||||
|
consoleError(e.message);
|
||||||
|
} else {
|
||||||
|
consoleError(e.stack);
|
||||||
|
}
|
||||||
|
return Promise.resolve(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function disabledTransformerPipelineCodegen(
|
||||||
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program,
|
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program,
|
||||||
host: ts.CompilerHost) {
|
host: ts.CompilerHost) {
|
||||||
if (ngOptions.enableSummariesForJit === undefined) {
|
if (ngOptions.enableSummariesForJit === undefined) {
|
||||||
@ -32,38 +104,8 @@ function codegen(
|
|||||||
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen();
|
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function main(
|
|
||||||
args: any, consoleError: (s: string) => void = console.error): Promise<number> {
|
|
||||||
const project = args.p || args.project || '.';
|
|
||||||
const cliOptions = new tsc.NgcCliOptions(args);
|
|
||||||
|
|
||||||
return tsc.main(project, cliOptions, codegen).then(() => 0).catch(e => {
|
|
||||||
if (e instanceof tsc.UserError || isSyntaxError(e)) {
|
|
||||||
consoleError(e.message);
|
|
||||||
return Promise.resolve(1);
|
|
||||||
} else {
|
|
||||||
consoleError(e.stack);
|
|
||||||
consoleError('Compilation failed');
|
|
||||||
return Promise.resolve(1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// CLI entry point
|
// CLI entry point
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
const parsedArgs = require('minimist')(args);
|
main(args).then((exitCode: number) => process.exitCode = exitCode);
|
||||||
const project = parsedArgs.p || parsedArgs.project || '.';
|
|
||||||
|
|
||||||
const projectDir = fs.lstatSync(project).isFile() ? path.dirname(project) : project;
|
|
||||||
|
|
||||||
// file names in tsconfig are resolved relative to this absolute path
|
|
||||||
const basePath = path.resolve(process.cwd(), projectDir);
|
|
||||||
const {ngOptions} = readConfiguration(project, basePath);
|
|
||||||
|
|
||||||
if (ngOptions.disableTransformerPipeline) {
|
|
||||||
main(parsedArgs).then((exitCode: number) => process.exit(exitCode));
|
|
||||||
} else {
|
|
||||||
process.exit(ngc.main(args, s => console.error(s)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,68 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Must be imported first, because Angular decorators throw on load.
|
|
||||||
import 'reflect-metadata';
|
|
||||||
|
|
||||||
import {isSyntaxError} from '@angular/compiler';
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
import {performCompilation, readConfiguration, throwOnDiagnostics} from './perform-compile';
|
|
||||||
import {CompilerOptions} from './transformers/api';
|
|
||||||
|
|
||||||
export function main(
|
|
||||||
args: string[], consoleError: (s: string) => void = console.error,
|
|
||||||
checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics): number {
|
|
||||||
try {
|
|
||||||
const parsedArgs = require('minimist')(args);
|
|
||||||
const project = parsedArgs.p || parsedArgs.project || '.';
|
|
||||||
|
|
||||||
const projectDir = fs.lstatSync(project).isFile() ? path.dirname(project) : project;
|
|
||||||
|
|
||||||
// file names in tsconfig are resolved relative to this absolute path
|
|
||||||
const basePath = path.resolve(process.cwd(), projectDir);
|
|
||||||
const {parsed, ngOptions} = readConfiguration(project, basePath, checkFunc);
|
|
||||||
|
|
||||||
// CLI arguments can override the i18n options
|
|
||||||
const ngcOptions = mergeCommandLine(parsedArgs, ngOptions);
|
|
||||||
|
|
||||||
const res = performCompilation(
|
|
||||||
basePath, parsed.fileNames, parsed.options, ngcOptions, consoleError, checkFunc);
|
|
||||||
|
|
||||||
return res.errorCode;
|
|
||||||
} catch (e) {
|
|
||||||
if (isSyntaxError(e)) {
|
|
||||||
consoleError(e.message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
consoleError(e.stack);
|
|
||||||
consoleError('Compilation failed');
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge command line parameters
|
|
||||||
function mergeCommandLine(
|
|
||||||
parsedArgs: {[k: string]: string}, options: CompilerOptions): CompilerOptions {
|
|
||||||
if (parsedArgs.i18nFile) options.i18nInFile = parsedArgs.i18nFile;
|
|
||||||
if (parsedArgs.i18nFormat) options.i18nInFormat = parsedArgs.i18nFormat;
|
|
||||||
if (parsedArgs.locale) options.i18nInLocale = parsedArgs.locale;
|
|
||||||
const mt = parsedArgs.missingTranslation;
|
|
||||||
if (mt === 'error' || mt === 'warning' || mt === 'ignore') {
|
|
||||||
options.i18nInMissingTranslations = mt;
|
|
||||||
}
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
// CLI entry point
|
|
||||||
if (require.main === module) {
|
|
||||||
process.exit(main(process.argv.slice(2), s => console.error(s)));
|
|
||||||
}
|
|
@ -17,24 +17,25 @@ import * as ng from './transformers/entry_points';
|
|||||||
|
|
||||||
const TS_EXT = /\.ts$/;
|
const TS_EXT = /\.ts$/;
|
||||||
|
|
||||||
export type Diagnostics = ts.Diagnostic[] | api.Diagnostic[];
|
export type Diagnostics = Array<ts.Diagnostic|api.Diagnostic>;
|
||||||
|
|
||||||
function isTsDiagnostics(diagnostics: any): diagnostics is ts.Diagnostic[] {
|
function isTsDiagnostic(diagnostic: any): diagnostic is ts.Diagnostic {
|
||||||
return diagnostics && diagnostics[0] && (diagnostics[0].file || diagnostics[0].messageText);
|
return diagnostic && (diagnostic.file || diagnostic.messageText);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDiagnostics(cwd: string, diags: Diagnostics): string {
|
export function formatDiagnostics(options: api.CompilerOptions, diags: Diagnostics): string {
|
||||||
if (diags && diags.length) {
|
if (diags && diags.length) {
|
||||||
if (isTsDiagnostics(diags)) {
|
const tsFormatHost: ts.FormatDiagnosticsHost = {
|
||||||
return ts.formatDiagnostics(diags, {
|
getCurrentDirectory: () => options.basePath || process.cwd(),
|
||||||
getCurrentDirectory: () => cwd,
|
getCanonicalFileName: fileName => fileName,
|
||||||
getCanonicalFileName: fileName => fileName,
|
getNewLine: () => ts.sys.newLine
|
||||||
getNewLine: () => ts.sys.newLine
|
};
|
||||||
});
|
return diags
|
||||||
} else {
|
.map(d => {
|
||||||
return diags
|
if (isTsDiagnostic(d)) {
|
||||||
.map(d => {
|
return ts.formatDiagnostics([d], tsFormatHost);
|
||||||
let res = api.DiagnosticCategory[d.category];
|
} else {
|
||||||
|
let res = ts.DiagnosticCategory[d.category];
|
||||||
if (d.span) {
|
if (d.span) {
|
||||||
res +=
|
res +=
|
||||||
` at ${d.span.start.file.url}(${d.span.start.line + 1},${d.span.start.col + 1})`;
|
` at ${d.span.start.file.url}(${d.span.start.line + 1},${d.span.start.col + 1})`;
|
||||||
@ -45,134 +46,133 @@ function formatDiagnostics(cwd: string, diags: Diagnostics): string {
|
|||||||
res += `: ${d.message}\n`;
|
res += `: ${d.message}\n`;
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
})
|
}
|
||||||
.join();
|
})
|
||||||
}
|
.join();
|
||||||
} else
|
} else
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export interface ParsedConfiguration {
|
||||||
* Throw a syntax error exception with a message formatted for output
|
options: api.CompilerOptions;
|
||||||
* if the args parameter contains diagnostics errors.
|
rootNames: string[];
|
||||||
*
|
errors: Diagnostics;
|
||||||
* @param cwd The directory to report error as relative to.
|
}
|
||||||
* @param args A list of potentially empty diagnostic errors.
|
|
||||||
*/
|
export function calcProjectFileAndBasePath(project: string):
|
||||||
export function throwOnDiagnostics(cwd: string, ...args: Diagnostics[]) {
|
{projectFile: string, basePath: string} {
|
||||||
if (args.some(diags => !!(diags && diags[0]))) {
|
const projectIsDir = fs.lstatSync(project).isDirectory();
|
||||||
throw syntaxError(args.map(diags => {
|
const projectFile = projectIsDir ? path.join(project, 'tsconfig.json') : project;
|
||||||
if (diags && diags[0]) {
|
const projectDir = projectIsDir ? project : path.dirname(project);
|
||||||
return formatDiagnostics(cwd, diags);
|
const basePath = path.resolve(process.cwd(), projectDir);
|
||||||
}
|
return {projectFile, basePath};
|
||||||
})
|
}
|
||||||
.filter(message => !!message)
|
|
||||||
.join(''));
|
export function createNgCompilerOptions(
|
||||||
}
|
basePath: string, config: any, tsOptions: ts.CompilerOptions): api.CompilerOptions {
|
||||||
|
return {...tsOptions, ...config.angularCompilerOptions, genDir: basePath, basePath};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readConfiguration(
|
export function readConfiguration(
|
||||||
project: string, basePath: string,
|
project: string, existingOptions?: ts.CompilerOptions): ParsedConfiguration {
|
||||||
checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics,
|
try {
|
||||||
existingOptions?: ts.CompilerOptions) {
|
const {projectFile, basePath} = calcProjectFileAndBasePath(project);
|
||||||
// Allow a directory containing tsconfig.json as the project value
|
|
||||||
// Note, TS@next returns an empty array, while earlier versions throw
|
|
||||||
const projectFile =
|
|
||||||
fs.lstatSync(project).isDirectory() ? path.join(project, 'tsconfig.json') : project;
|
|
||||||
let {config, error} = ts.readConfigFile(projectFile, ts.sys.readFile);
|
|
||||||
|
|
||||||
if (error) checkFunc(basePath, [error]);
|
let {config, error} = ts.readConfigFile(projectFile, ts.sys.readFile);
|
||||||
const parseConfigHost = {
|
|
||||||
useCaseSensitiveFileNames: true,
|
|
||||||
fileExists: fs.existsSync,
|
|
||||||
readDirectory: ts.sys.readDirectory,
|
|
||||||
readFile: ts.sys.readFile
|
|
||||||
};
|
|
||||||
const parsed = ts.parseJsonConfigFileContent(config, parseConfigHost, basePath, existingOptions);
|
|
||||||
|
|
||||||
checkFunc(basePath, parsed.errors);
|
if (error) {
|
||||||
|
return {errors: [error], rootNames: [], options: {}};
|
||||||
|
}
|
||||||
|
const parseConfigHost = {
|
||||||
|
useCaseSensitiveFileNames: true,
|
||||||
|
fileExists: fs.existsSync,
|
||||||
|
readDirectory: ts.sys.readDirectory,
|
||||||
|
readFile: ts.sys.readFile
|
||||||
|
};
|
||||||
|
const parsed =
|
||||||
|
ts.parseJsonConfigFileContent(config, parseConfigHost, basePath, existingOptions);
|
||||||
|
const rootNames = parsed.fileNames.map(f => path.normalize(f));
|
||||||
|
|
||||||
// Default codegen goes to the current directory
|
const options = createNgCompilerOptions(basePath, config, parsed.options);
|
||||||
// Parsed options are already converted to absolute paths
|
return {rootNames, options, errors: parsed.errors};
|
||||||
const ngOptions = config.angularCompilerOptions || {};
|
} catch (e) {
|
||||||
// Ignore the genDir option
|
const errors: Diagnostics = [{
|
||||||
ngOptions.genDir = basePath;
|
category: ts.DiagnosticCategory.Error,
|
||||||
|
message: e.stack,
|
||||||
return {parsed, ngOptions};
|
}];
|
||||||
|
return {errors, rootNames: [], options: {}};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an object with two properties:
|
|
||||||
* - `errorCode` is 0 when the compilation was successful,
|
|
||||||
* - `result` is an `EmitResult` when the errorCode is 0, `undefined` otherwise.
|
|
||||||
*/
|
|
||||||
export function performCompilation(
|
export function performCompilation(
|
||||||
basePath: string, files: string[], options: ts.CompilerOptions, ngOptions: api.CompilerOptions,
|
rootNames: string[], options: api.CompilerOptions, host?: api.CompilerHost,
|
||||||
consoleError: (s: string) => void = console.error,
|
oldProgram?: api.Program): {
|
||||||
checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics,
|
program?: api.Program,
|
||||||
tsCompilerHost?: ts.CompilerHost): {errorCode: number, result?: api.EmitResult} {
|
emitResult?: api.EmitResult,
|
||||||
|
diagnostics: Diagnostics,
|
||||||
|
} {
|
||||||
const [major, minor] = ts.version.split('.');
|
const [major, minor] = ts.version.split('.');
|
||||||
|
|
||||||
if (+major < 2 || (+major === 2 && +minor < 3)) {
|
if (Number(major) < 2 || (Number(major) === 2 && Number(minor) < 3)) {
|
||||||
throw new Error('Must use TypeScript > 2.3 to have transformer support');
|
throw new Error('Must use TypeScript > 2.3 to have transformer support');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allDiagnostics: Diagnostics = [];
|
||||||
|
|
||||||
|
function checkDiagnostics(diags: Diagnostics | undefined) {
|
||||||
|
if (diags) {
|
||||||
|
allDiagnostics.push(...diags);
|
||||||
|
return diags.every(d => d.category !== ts.DiagnosticCategory.Error);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let program: api.Program|undefined;
|
||||||
|
let emitResult: api.EmitResult|undefined;
|
||||||
try {
|
try {
|
||||||
ngOptions.basePath = basePath;
|
if (!host) {
|
||||||
ngOptions.genDir = basePath;
|
host = ng.createNgCompilerHost({options});
|
||||||
|
|
||||||
let host = tsCompilerHost || ts.createCompilerHost(options, true);
|
|
||||||
host.realpath = p => p;
|
|
||||||
|
|
||||||
const rootFileNames = files.map(f => path.normalize(f));
|
|
||||||
|
|
||||||
const addGeneratedFileName = (fileName: string) => {
|
|
||||||
if (fileName.startsWith(basePath) && TS_EXT.exec(fileName)) {
|
|
||||||
rootFileNames.push(fileName);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (ngOptions.flatModuleOutFile && !ngOptions.skipMetadataEmit) {
|
|
||||||
const {host: bundleHost, indexName, errors} =
|
|
||||||
createBundleIndexHost(ngOptions, rootFileNames, host);
|
|
||||||
if (errors) checkFunc(basePath, errors);
|
|
||||||
if (indexName) addGeneratedFileName(indexName);
|
|
||||||
host = bundleHost;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ngHostOptions = {...options, ...ngOptions};
|
program = ng.createProgram({rootNames, host, options, oldProgram});
|
||||||
const ngHost = ng.createHost({tsHost: host, options: ngHostOptions});
|
|
||||||
|
|
||||||
const ngProgram =
|
|
||||||
ng.createProgram({rootNames: rootFileNames, host: ngHost, options: ngHostOptions});
|
|
||||||
|
|
||||||
|
let shouldEmit = true;
|
||||||
// Check parameter diagnostics
|
// Check parameter diagnostics
|
||||||
checkFunc(basePath, ngProgram.getTsOptionDiagnostics(), ngProgram.getNgOptionDiagnostics());
|
shouldEmit = shouldEmit && checkDiagnostics([
|
||||||
|
...program !.getTsOptionDiagnostics(), ...program !.getNgOptionDiagnostics()
|
||||||
|
]);
|
||||||
|
|
||||||
// Check syntactic diagnostics
|
// Check syntactic diagnostics
|
||||||
checkFunc(basePath, ngProgram.getTsSyntacticDiagnostics());
|
shouldEmit = shouldEmit && checkDiagnostics(program !.getTsSyntacticDiagnostics());
|
||||||
|
|
||||||
// Check TypeScript semantic and Angular structure diagnostics
|
// Check TypeScript semantic and Angular structure diagnostics
|
||||||
checkFunc(
|
shouldEmit =
|
||||||
basePath, ngProgram.getTsSemanticDiagnostics(), ngProgram.getNgStructuralDiagnostics());
|
shouldEmit &&
|
||||||
|
checkDiagnostics(
|
||||||
|
[...program !.getTsSemanticDiagnostics(), ...program !.getNgStructuralDiagnostics()]);
|
||||||
|
|
||||||
// Check Angular semantic diagnostics
|
// Check Angular semantic diagnostics
|
||||||
checkFunc(basePath, ngProgram.getNgSemanticDiagnostics());
|
shouldEmit = shouldEmit && checkDiagnostics(program !.getNgSemanticDiagnostics());
|
||||||
|
|
||||||
const result = ngProgram.emit({
|
if (shouldEmit) {
|
||||||
emitFlags: api.EmitFlags.Default |
|
const emitResult = program !.emit({
|
||||||
((ngOptions.skipMetadataEmit || ngOptions.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata)
|
emitFlags: api.EmitFlags.Default |
|
||||||
});
|
((options.skipMetadataEmit || options.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata)
|
||||||
|
});
|
||||||
checkFunc(basePath, result.diagnostics);
|
allDiagnostics.push(...emitResult.diagnostics);
|
||||||
|
|
||||||
return {errorCode: 0, result};
|
|
||||||
} catch (e) {
|
|
||||||
if (isSyntaxError(e)) {
|
|
||||||
consoleError(e.message);
|
|
||||||
return {errorCode: 1};
|
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
throw e;
|
let errMsg: string;
|
||||||
|
if (isSyntaxError(e)) {
|
||||||
|
// don't report the stack for syntax errors as they are well known errors.
|
||||||
|
errMsg = e.message;
|
||||||
|
} else {
|
||||||
|
errMsg = e.stack;
|
||||||
|
}
|
||||||
|
allDiagnostics.push({
|
||||||
|
category: ts.DiagnosticCategory.Error,
|
||||||
|
message: errMsg,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
return {program, emitResult, diagnostics: allDiagnostics};
|
||||||
}
|
}
|
@ -9,16 +9,10 @@
|
|||||||
import {ParseSourceSpan} from '@angular/compiler';
|
import {ParseSourceSpan} from '@angular/compiler';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
export enum DiagnosticCategory {
|
|
||||||
Warning = 0,
|
|
||||||
Error = 1,
|
|
||||||
Message = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Diagnostic {
|
export interface Diagnostic {
|
||||||
message: string;
|
message: string;
|
||||||
span?: ParseSourceSpan;
|
span?: ParseSourceSpan;
|
||||||
category: DiagnosticCategory;
|
category: ts.DiagnosticCategory;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompilerOptions extends ts.CompilerOptions {
|
export interface CompilerOptions extends ts.CompilerOptions {
|
||||||
|
@ -13,8 +13,9 @@ import {createModuleFilenameResolver} from './module_filename_resolver';
|
|||||||
export {createProgram} from './program';
|
export {createProgram} from './program';
|
||||||
export {createModuleFilenameResolver};
|
export {createModuleFilenameResolver};
|
||||||
|
|
||||||
export function createHost({tsHost, options}: {tsHost: ts.CompilerHost, options: CompilerOptions}):
|
export function createNgCompilerHost(
|
||||||
CompilerHost {
|
{options, tsHost = ts.createCompilerHost(options, true)}:
|
||||||
|
{options: CompilerOptions, tsHost?: ts.CompilerHost}): CompilerHost {
|
||||||
const resolver = createModuleFilenameResolver(tsHost, options);
|
const resolver = createModuleFilenameResolver(tsHost, options);
|
||||||
|
|
||||||
const host = Object.create(tsHost);
|
const host = Object.create(tsHost);
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import {AotCompiler, AotCompilerOptions, GeneratedFile, NgAnalyzedModules, createAotCompiler, getParseErrors, isSyntaxError, toTypeScript} from '@angular/compiler';
|
import {AotCompiler, AotCompilerOptions, GeneratedFile, NgAnalyzedModules, createAotCompiler, getParseErrors, isSyntaxError, toTypeScript} from '@angular/compiler';
|
||||||
import {MissingTranslationStrategy} from '@angular/core';
|
import {MissingTranslationStrategy} from '@angular/core';
|
||||||
|
import {createBundleIndexHost} from '@angular/tsc-wrapped';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as tsickle from 'tsickle';
|
import * as tsickle from 'tsickle';
|
||||||
@ -16,7 +17,7 @@ import * as ts from 'typescript';
|
|||||||
import {CompilerHost as AotCompilerHost} from '../compiler_host';
|
import {CompilerHost as AotCompilerHost} from '../compiler_host';
|
||||||
import {TypeChecker} from '../diagnostics/check_types';
|
import {TypeChecker} from '../diagnostics/check_types';
|
||||||
|
|
||||||
import {CompilerHost, CompilerOptions, Diagnostic, DiagnosticCategory, EmitFlags, EmitResult, Program} from './api';
|
import {CompilerHost, CompilerOptions, Diagnostic, EmitFlags, EmitResult, Program} from './api';
|
||||||
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
|
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';
|
||||||
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
|
import {getAngularEmitterTransformFactory} from './node_emitter_transform';
|
||||||
|
|
||||||
@ -31,8 +32,6 @@ const emptyModules: NgAnalyzedModules = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class AngularCompilerProgram implements Program {
|
class AngularCompilerProgram implements Program {
|
||||||
// Initialized in the constructor
|
|
||||||
private oldTsProgram: ts.Program|undefined;
|
|
||||||
private tsProgram: ts.Program;
|
private tsProgram: ts.Program;
|
||||||
private aotCompilerHost: AotCompilerHost;
|
private aotCompilerHost: AotCompilerHost;
|
||||||
private compiler: AotCompiler;
|
private compiler: AotCompiler;
|
||||||
@ -49,12 +48,26 @@ class AngularCompilerProgram implements Program {
|
|||||||
private _generatedFileDiagnostics: Diagnostic[]|undefined;
|
private _generatedFileDiagnostics: Diagnostic[]|undefined;
|
||||||
private _typeChecker: TypeChecker|undefined;
|
private _typeChecker: TypeChecker|undefined;
|
||||||
private _semanticDiagnostics: Diagnostic[]|undefined;
|
private _semanticDiagnostics: Diagnostic[]|undefined;
|
||||||
|
private _optionsDiagnostics: Diagnostic[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private rootNames: string[], private options: CompilerOptions, private host: CompilerHost,
|
private rootNames: string[], private options: CompilerOptions, private host: CompilerHost,
|
||||||
private oldProgram?: Program) {
|
private oldProgram?: Program) {
|
||||||
this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined;
|
if (options.flatModuleOutFile && !options.skipMetadataEmit) {
|
||||||
this.tsProgram = ts.createProgram(rootNames, options, host, this.oldTsProgram);
|
const {host: bundleHost, indexName, errors} = createBundleIndexHost(options, rootNames, host);
|
||||||
|
if (errors) {
|
||||||
|
// TODO(tbosch): once we move MetadataBundler from tsc_wrapped into compiler_cli,
|
||||||
|
// directly create ng.Diagnostic instead of using ts.Diagnostic here.
|
||||||
|
this._optionsDiagnostics.push(
|
||||||
|
...errors.map(e => ({category: e.category, message: e.messageText as string})));
|
||||||
|
} else {
|
||||||
|
rootNames.push(indexName !);
|
||||||
|
this.host = host = bundleHost;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined;
|
||||||
|
this.tsProgram = ts.createProgram(rootNames, options, host, oldTsProgram);
|
||||||
this.srcNames =
|
this.srcNames =
|
||||||
this.tsProgram.getSourceFiles()
|
this.tsProgram.getSourceFiles()
|
||||||
.map(sf => sf.fileName)
|
.map(sf => sf.fileName)
|
||||||
@ -78,7 +91,7 @@ class AngularCompilerProgram implements Program {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken): Diagnostic[] {
|
getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken): Diagnostic[] {
|
||||||
return getNgOptionDiagnostics(this.options);
|
return [...this._optionsDiagnostics, ...getNgOptionDiagnostics(this.options)];
|
||||||
}
|
}
|
||||||
|
|
||||||
getTsSyntacticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken):
|
getTsSyntacticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken):
|
||||||
@ -237,11 +250,11 @@ class AngularCompilerProgram implements Program {
|
|||||||
this._structuralDiagnostics =
|
this._structuralDiagnostics =
|
||||||
parserErrors.map<Diagnostic>(e => ({
|
parserErrors.map<Diagnostic>(e => ({
|
||||||
message: e.contextualMessage(),
|
message: e.contextualMessage(),
|
||||||
category: DiagnosticCategory.Error,
|
category: ts.DiagnosticCategory.Error,
|
||||||
span: e.span
|
span: e.span
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
this._structuralDiagnostics = [{message: e.message, category: DiagnosticCategory.Error}];
|
this._structuralDiagnostics = [{message: e.message, category: ts.DiagnosticCategory.Error}];
|
||||||
}
|
}
|
||||||
this._analyzedModules = emptyModules;
|
this._analyzedModules = emptyModules;
|
||||||
return emptyModules;
|
return emptyModules;
|
||||||
@ -272,7 +285,8 @@ class AngularCompilerProgram implements Program {
|
|||||||
return this.options.skipTemplateCodegen ? [] : result;
|
return this.options.skipTemplateCodegen ? [] : result;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (isSyntaxError(e)) {
|
if (isSyntaxError(e)) {
|
||||||
this._generatedFileDiagnostics = [{message: e.message, category: DiagnosticCategory.Error}];
|
this._generatedFileDiagnostics =
|
||||||
|
[{message: e.message, category: ts.DiagnosticCategory.Error}];
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
@ -395,7 +409,7 @@ function getNgOptionDiagnostics(options: CompilerOptions): Diagnostic[] {
|
|||||||
return [{
|
return [{
|
||||||
message:
|
message:
|
||||||
'Angular compiler options "annotationsAs" only supports "static fields" and "decorators"',
|
'Angular compiler options "annotationsAs" only supports "static fields" and "decorators"',
|
||||||
category: DiagnosticCategory.Error
|
category: ts.DiagnosticCategory.Error
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,16 +18,22 @@ function getNgRootDir() {
|
|||||||
return moduleFilename.substr(0, distIndex);
|
return moduleFilename.substr(0, distIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('compiler-cli', () => {
|
describe('compiler-cli with disableTransformerPipeline', () => {
|
||||||
let basePath: string;
|
let basePath: string;
|
||||||
let outDir: string;
|
let outDir: string;
|
||||||
let write: (fileName: string, content: string) => void;
|
let write: (fileName: string, content: string) => void;
|
||||||
|
let errorSpy: jasmine.Spy&((s: string) => void);
|
||||||
|
|
||||||
function writeConfig(tsconfig: string = '{"extends": "./tsconfig-base.json"}') {
|
function writeConfig(tsconfig: string = '{"extends": "./tsconfig-base.json"}') {
|
||||||
write('tsconfig.json', tsconfig);
|
const json = JSON.parse(tsconfig);
|
||||||
|
// Note: 'extends' does not work for "angularCompilerOptions" yet.
|
||||||
|
const ngOptions = json['angularCompilerOptions'] = json['angularCompilerOptions'] || {};
|
||||||
|
ngOptions['disableTransformerPipeline'] = true;
|
||||||
|
write('tsconfig.json', JSON.stringify(json));
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
errorSpy = jasmine.createSpy('consoleError');
|
||||||
basePath = makeTempDir();
|
basePath = makeTempDir();
|
||||||
write = (fileName: string, content: string) => {
|
write = (fileName: string, content: string) => {
|
||||||
fs.writeFileSync(path.join(basePath, fileName), content, {encoding: 'utf-8'});
|
fs.writeFileSync(path.join(basePath, fileName), content, {encoding: 'utf-8'});
|
||||||
@ -58,13 +64,9 @@ describe('compiler-cli', () => {
|
|||||||
writeConfig();
|
writeConfig();
|
||||||
write('test.ts', 'export const A = 1;');
|
write('test.ts', 'export const A = 1;');
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
main(['-p', basePath], errorSpy)
|
||||||
|
|
||||||
spyOn(mockConsole, 'error');
|
|
||||||
|
|
||||||
main({p: basePath}, mockConsole.error)
|
|
||||||
.then((exitCode) => {
|
.then((exitCode) => {
|
||||||
expect(mockConsole.error).not.toHaveBeenCalled();
|
expect(errorSpy).not.toHaveBeenCalled();
|
||||||
expect(exitCode).toEqual(0);
|
expect(exitCode).toEqual(0);
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
@ -76,16 +78,11 @@ describe('compiler-cli', () => {
|
|||||||
"extends": "./tsconfig-base.json",
|
"extends": "./tsconfig-base.json",
|
||||||
"files": ["test.ts"]
|
"files": ["test.ts"]
|
||||||
}`);
|
}`);
|
||||||
const mockConsole = {error: (s: string) => {}};
|
|
||||||
|
|
||||||
spyOn(mockConsole, 'error');
|
main(['-p', basePath], errorSpy)
|
||||||
|
|
||||||
main({p: basePath}, mockConsole.error)
|
|
||||||
.then((exitCode) => {
|
.then((exitCode) => {
|
||||||
expect(mockConsole.error)
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
.toHaveBeenCalledWith(
|
`Error File '` + path.join(basePath, 'test.ts') + `' not found.`);
|
||||||
`Error File '` + path.join(basePath, 'test.ts') + `' not found.`);
|
|
||||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
|
||||||
expect(exitCode).toEqual(1);
|
expect(exitCode).toEqual(1);
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
@ -96,16 +93,10 @@ describe('compiler-cli', () => {
|
|||||||
writeConfig();
|
writeConfig();
|
||||||
write('test.ts', 'foo;');
|
write('test.ts', 'foo;');
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
main(['-p', basePath], errorSpy)
|
||||||
|
|
||||||
spyOn(mockConsole, 'error');
|
|
||||||
|
|
||||||
main({p: basePath}, mockConsole.error)
|
|
||||||
.then((exitCode) => {
|
.then((exitCode) => {
|
||||||
expect(mockConsole.error)
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
.toHaveBeenCalledWith(
|
'Error at ' + path.join(basePath, 'test.ts') + `:1:1: Cannot find name 'foo'.`);
|
||||||
'Error at ' + path.join(basePath, 'test.ts') + `:1:1: Cannot find name 'foo'.`);
|
|
||||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
|
||||||
expect(exitCode).toEqual(1);
|
expect(exitCode).toEqual(1);
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
@ -116,17 +107,11 @@ describe('compiler-cli', () => {
|
|||||||
writeConfig();
|
writeConfig();
|
||||||
write('test.ts', `import {MyClass} from './not-exist-deps';`);
|
write('test.ts', `import {MyClass} from './not-exist-deps';`);
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
main(['-p', basePath], errorSpy)
|
||||||
|
|
||||||
spyOn(mockConsole, 'error');
|
|
||||||
|
|
||||||
main({p: basePath}, mockConsole.error)
|
|
||||||
.then((exitCode) => {
|
.then((exitCode) => {
|
||||||
expect(mockConsole.error)
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
.toHaveBeenCalledWith(
|
'Error at ' + path.join(basePath, 'test.ts') +
|
||||||
'Error at ' + path.join(basePath, 'test.ts') +
|
`:1:23: Cannot find module './not-exist-deps'.`);
|
||||||
`:1:23: Cannot find module './not-exist-deps'.`);
|
|
||||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
|
||||||
expect(exitCode).toEqual(1);
|
expect(exitCode).toEqual(1);
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
@ -138,17 +123,11 @@ describe('compiler-cli', () => {
|
|||||||
write('empty-deps.ts', 'export const A = 1;');
|
write('empty-deps.ts', 'export const A = 1;');
|
||||||
write('test.ts', `import {MyClass} from './empty-deps';`);
|
write('test.ts', `import {MyClass} from './empty-deps';`);
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
main(['-p', basePath], errorSpy)
|
||||||
|
|
||||||
spyOn(mockConsole, 'error');
|
|
||||||
|
|
||||||
main({p: basePath}, mockConsole.error)
|
|
||||||
.then((exitCode) => {
|
.then((exitCode) => {
|
||||||
expect(mockConsole.error)
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
.toHaveBeenCalledWith(
|
'Error at ' + path.join(basePath, 'test.ts') + `:1:9: Module '"` +
|
||||||
'Error at ' + path.join(basePath, 'test.ts') + `:1:9: Module '"` +
|
path.join(basePath, 'empty-deps') + `"' has no exported member 'MyClass'.`);
|
||||||
path.join(basePath, 'empty-deps') + `"' has no exported member 'MyClass'.`);
|
|
||||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
|
||||||
expect(exitCode).toEqual(1);
|
expect(exitCode).toEqual(1);
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
@ -163,18 +142,12 @@ describe('compiler-cli', () => {
|
|||||||
A();
|
A();
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
main(['-p', basePath], errorSpy)
|
||||||
|
|
||||||
spyOn(mockConsole, 'error');
|
|
||||||
|
|
||||||
main({p: basePath}, mockConsole.error)
|
|
||||||
.then((exitCode) => {
|
.then((exitCode) => {
|
||||||
expect(mockConsole.error)
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
.toHaveBeenCalledWith(
|
'Error at ' + path.join(basePath, 'test.ts') +
|
||||||
'Error at ' + path.join(basePath, 'test.ts') +
|
':3:7: Cannot invoke an expression whose type lacks a call signature. ' +
|
||||||
':3:7: Cannot invoke an expression whose type lacks a call signature. ' +
|
'Type \'String\' has no compatible call signatures.');
|
||||||
'Type \'String\' has no compatible call signatures.');
|
|
||||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
|
||||||
expect(exitCode).toEqual(1);
|
expect(exitCode).toEqual(1);
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
@ -184,14 +157,11 @@ describe('compiler-cli', () => {
|
|||||||
it('should print the stack trace on compiler internal errors', (done) => {
|
it('should print the stack trace on compiler internal errors', (done) => {
|
||||||
write('test.ts', 'export const A = 1;');
|
write('test.ts', 'export const A = 1;');
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
main(['-p', 'not-exist'], errorSpy)
|
||||||
|
|
||||||
spyOn(mockConsole, 'error');
|
|
||||||
|
|
||||||
main({p: 'not-exist'}, mockConsole.error)
|
|
||||||
.then((exitCode) => {
|
.then((exitCode) => {
|
||||||
expect(mockConsole.error).toHaveBeenCalled();
|
expect(errorSpy).toHaveBeenCalled();
|
||||||
expect(mockConsole.error).toHaveBeenCalledWith('Compilation failed');
|
expect(errorSpy.calls.mostRecent().args[0]).toContain('no such file or directory');
|
||||||
|
expect(errorSpy.calls.mostRecent().args[0]).toContain('at Error (native)');
|
||||||
expect(exitCode).toEqual(1);
|
expect(exitCode).toEqual(1);
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
@ -215,7 +185,7 @@ describe('compiler-cli', () => {
|
|||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
main({p: basePath})
|
main(['-p', basePath], errorSpy)
|
||||||
.then((exitCode) => {
|
.then((exitCode) => {
|
||||||
expect(exitCode).toEqual(0);
|
expect(exitCode).toEqual(0);
|
||||||
|
|
||||||
@ -244,11 +214,7 @@ describe('compiler-cli', () => {
|
|||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
main(['-p', basePath], errorSpy)
|
||||||
|
|
||||||
const errorSpy = spyOn(mockConsole, 'error');
|
|
||||||
|
|
||||||
main({p: basePath}, mockConsole.error)
|
|
||||||
.then((exitCode) => {
|
.then((exitCode) => {
|
||||||
expect(errorSpy).toHaveBeenCalledTimes(1);
|
expect(errorSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(errorSpy.calls.mostRecent().args[0])
|
expect(errorSpy.calls.mostRecent().args[0])
|
||||||
@ -280,7 +246,7 @@ describe('compiler-cli', () => {
|
|||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
main({p: basePath})
|
main(['-p', basePath], errorSpy)
|
||||||
.then((exitCode) => {
|
.then((exitCode) => {
|
||||||
expect(exitCode).toEqual(0);
|
expect(exitCode).toEqual(0);
|
||||||
|
|
||||||
@ -307,7 +273,7 @@ describe('compiler-cli', () => {
|
|||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
main({p: basePath})
|
main(['-p', basePath], errorSpy)
|
||||||
.then((exitCode) => {
|
.then((exitCode) => {
|
||||||
expect(exitCode).toEqual(0);
|
expect(exitCode).toEqual(0);
|
||||||
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngsummary.js'))).toBe(false);
|
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngsummary.js'))).toBe(false);
|
||||||
@ -333,7 +299,7 @@ describe('compiler-cli', () => {
|
|||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
main({p: basePath})
|
main(['-p', basePath], errorSpy)
|
||||||
.then((exitCode) => {
|
.then((exitCode) => {
|
||||||
expect(exitCode).toEqual(0);
|
expect(exitCode).toEqual(0);
|
||||||
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngsummary.js'))).toBe(true);
|
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngsummary.js'))).toBe(true);
|
||||||
|
@ -11,8 +11,7 @@ import * as fs from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {main} from '../src/ngc';
|
import {mainSync} from '../src/main';
|
||||||
import {performCompilation, readConfiguration} from '../src/perform-compile';
|
|
||||||
|
|
||||||
function getNgRootDir() {
|
function getNgRootDir() {
|
||||||
const moduleFilename = module.filename.replace(/\\/g, '/');
|
const moduleFilename = module.filename.replace(/\\/g, '/');
|
||||||
@ -20,16 +19,18 @@ function getNgRootDir() {
|
|||||||
return moduleFilename.substr(0, distIndex);
|
return moduleFilename.substr(0, distIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ngc command-line', () => {
|
describe('ngc transformer command-line', () => {
|
||||||
let basePath: string;
|
let basePath: string;
|
||||||
let outDir: string;
|
let outDir: string;
|
||||||
let write: (fileName: string, content: string) => void;
|
let write: (fileName: string, content: string) => void;
|
||||||
|
let errorSpy: jasmine.Spy&((s: string) => void);
|
||||||
|
|
||||||
function writeConfig(tsconfig: string = '{"extends": "./tsconfig-base.json"}') {
|
function writeConfig(tsconfig: string = '{"extends": "./tsconfig-base.json"}') {
|
||||||
write('tsconfig.json', tsconfig);
|
write('tsconfig.json', tsconfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
errorSpy = jasmine.createSpy('consoleError');
|
||||||
basePath = makeTempDir();
|
basePath = makeTempDir();
|
||||||
write = (fileName: string, content: string) => {
|
write = (fileName: string, content: string) => {
|
||||||
const dir = path.dirname(fileName);
|
const dir = path.dirname(fileName);
|
||||||
@ -66,35 +67,9 @@ describe('ngc command-line', () => {
|
|||||||
writeConfig();
|
writeConfig();
|
||||||
write('test.ts', 'export const A = 1;');
|
write('test.ts', 'export const A = 1;');
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||||
|
expect(errorSpy).not.toHaveBeenCalled();
|
||||||
spyOn(mockConsole, 'error');
|
expect(exitCode).toBe(0);
|
||||||
|
|
||||||
const result = main(['-p', basePath], mockConsole.error);
|
|
||||||
expect(mockConsole.error).not.toHaveBeenCalled();
|
|
||||||
expect(result).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to be called without a config file by passing options explicitly', () => {
|
|
||||||
write('test.ts', 'export const A = 1;');
|
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
|
||||||
|
|
||||||
spyOn(mockConsole, 'error');
|
|
||||||
|
|
||||||
expect(
|
|
||||||
() => performCompilation(
|
|
||||||
basePath, [path.join(basePath, 'test.ts')], {
|
|
||||||
experimentalDecorators: true,
|
|
||||||
skipLibCheck: true,
|
|
||||||
types: [],
|
|
||||||
outDir: path.join(basePath, 'built'),
|
|
||||||
declaration: true,
|
|
||||||
module: ts.ModuleKind.ES2015,
|
|
||||||
moduleResolution: ts.ModuleResolutionKind.NodeJs,
|
|
||||||
},
|
|
||||||
{}))
|
|
||||||
.not.toThrow();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not print the stack trace if user input file does not exist', () => {
|
it('should not print the stack trace if user input file does not exist', () => {
|
||||||
@ -102,16 +77,11 @@ describe('ngc command-line', () => {
|
|||||||
"extends": "./tsconfig-base.json",
|
"extends": "./tsconfig-base.json",
|
||||||
"files": ["test.ts"]
|
"files": ["test.ts"]
|
||||||
}`);
|
}`);
|
||||||
const mockConsole = {error: (s: string) => {}};
|
|
||||||
|
|
||||||
spyOn(mockConsole, 'error');
|
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||||
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
const exitCode = main(['-p', basePath], mockConsole.error);
|
`error TS6053: File '` + path.join(basePath, 'test.ts') + `' not found.` +
|
||||||
expect(mockConsole.error)
|
'\n');
|
||||||
.toHaveBeenCalledWith(
|
|
||||||
`error TS6053: File '` + path.join(basePath, 'test.ts') + `' not found.` +
|
|
||||||
'\n');
|
|
||||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
|
||||||
expect(exitCode).toEqual(1);
|
expect(exitCode).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -119,16 +89,10 @@ describe('ngc command-line', () => {
|
|||||||
writeConfig();
|
writeConfig();
|
||||||
write('test.ts', 'foo;');
|
write('test.ts', 'foo;');
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||||
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
spyOn(mockConsole, 'error');
|
`test.ts(1,1): error TS2304: Cannot find name 'foo'.` +
|
||||||
|
'\n');
|
||||||
const exitCode = main(['-p', basePath], mockConsole.error);
|
|
||||||
expect(mockConsole.error)
|
|
||||||
.toHaveBeenCalledWith(
|
|
||||||
`test.ts(1,1): error TS2304: Cannot find name 'foo'.` +
|
|
||||||
'\n');
|
|
||||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
|
||||||
expect(exitCode).toEqual(1);
|
expect(exitCode).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -136,16 +100,10 @@ describe('ngc command-line', () => {
|
|||||||
writeConfig();
|
writeConfig();
|
||||||
write('test.ts', `import {MyClass} from './not-exist-deps';`);
|
write('test.ts', `import {MyClass} from './not-exist-deps';`);
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||||
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
spyOn(mockConsole, 'error');
|
`test.ts(1,23): error TS2307: Cannot find module './not-exist-deps'.` +
|
||||||
|
'\n');
|
||||||
const exitCode = main(['-p', basePath], mockConsole.error);
|
|
||||||
expect(mockConsole.error)
|
|
||||||
.toHaveBeenCalledWith(
|
|
||||||
`test.ts(1,23): error TS2307: Cannot find module './not-exist-deps'.` +
|
|
||||||
'\n');
|
|
||||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
|
||||||
expect(exitCode).toEqual(1);
|
expect(exitCode).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -154,17 +112,11 @@ describe('ngc command-line', () => {
|
|||||||
write('empty-deps.ts', 'export const A = 1;');
|
write('empty-deps.ts', 'export const A = 1;');
|
||||||
write('test.ts', `import {MyClass} from './empty-deps';`);
|
write('test.ts', `import {MyClass} from './empty-deps';`);
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||||
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
spyOn(mockConsole, 'error');
|
`test.ts(1,9): error TS2305: Module '"` + path.join(basePath, 'empty-deps') +
|
||||||
|
`"' has no exported member 'MyClass'.` +
|
||||||
const exitCode = main(['-p', basePath], mockConsole.error);
|
'\n');
|
||||||
expect(mockConsole.error)
|
|
||||||
.toHaveBeenCalledWith(
|
|
||||||
`test.ts(1,9): error TS2305: Module '"` + path.join(basePath, 'empty-deps') +
|
|
||||||
`"' has no exported member 'MyClass'.` +
|
|
||||||
'\n');
|
|
||||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
|
||||||
expect(exitCode).toEqual(1);
|
expect(exitCode).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -176,30 +128,21 @@ describe('ngc command-line', () => {
|
|||||||
A();
|
A();
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||||
|
expect(errorSpy).toHaveBeenCalledWith(
|
||||||
spyOn(mockConsole, 'error');
|
'test.ts(3,7): error TS2349: Cannot invoke an expression whose type lacks a call signature. ' +
|
||||||
|
'Type \'String\' has no compatible call signatures.\n');
|
||||||
const exitCode = main(['-p', basePath], mockConsole.error);
|
|
||||||
expect(mockConsole.error)
|
|
||||||
.toHaveBeenCalledWith(
|
|
||||||
'test.ts(3,7): error TS2349: Cannot invoke an expression whose type lacks a call signature. ' +
|
|
||||||
'Type \'String\' has no compatible call signatures.\n');
|
|
||||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
|
||||||
expect(exitCode).toEqual(1);
|
expect(exitCode).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should print the stack trace on compiler internal errors', () => {
|
it('should print the stack trace on compiler internal errors', () => {
|
||||||
write('test.ts', 'export const A = 1;');
|
write('test.ts', 'export const A = 1;');
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
const exitCode = mainSync(['-p', 'not-exist'], errorSpy);
|
||||||
|
expect(errorSpy).toHaveBeenCalledTimes(1);
|
||||||
spyOn(mockConsole, 'error');
|
expect(errorSpy.calls.mostRecent().args[0]).toContain('no such file or directory');
|
||||||
|
expect(errorSpy.calls.mostRecent().args[0]).toContain('at Error (native)');
|
||||||
const exitCode = main(['-p', 'not-exist'], mockConsole.error);
|
expect(exitCode).toEqual(1);
|
||||||
expect(mockConsole.error).toHaveBeenCalled();
|
|
||||||
expect(mockConsole.error).toHaveBeenCalledWith('Compilation failed');
|
|
||||||
expect(exitCode).toEqual(2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('compile ngfactory files', () => {
|
describe('compile ngfactory files', () => {
|
||||||
@ -218,11 +161,7 @@ describe('ngc command-line', () => {
|
|||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||||
|
|
||||||
const errorSpy = spyOn(mockConsole, 'error');
|
|
||||||
|
|
||||||
const exitCode = main(['-p', basePath], mockConsole.error);
|
|
||||||
expect(errorSpy).toHaveBeenCalledTimes(1);
|
expect(errorSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(errorSpy.calls.mostRecent().args[0])
|
expect(errorSpy.calls.mostRecent().args[0])
|
||||||
.toContain('Error at ng://' + path.join(basePath, 'mymodule.ts.MyComp.html'));
|
.toContain('Error at ng://' + path.join(basePath, 'mymodule.ts.MyComp.html'));
|
||||||
@ -253,11 +192,7 @@ describe('ngc command-line', () => {
|
|||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||||
|
|
||||||
const errorSpy = spyOn(mockConsole, 'error');
|
|
||||||
|
|
||||||
const exitCode = main(['-p', basePath], mockConsole.error);
|
|
||||||
expect(errorSpy).toHaveBeenCalledTimes(1);
|
expect(errorSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(errorSpy.calls.mostRecent().args[0])
|
expect(errorSpy.calls.mostRecent().args[0])
|
||||||
.toContain('Error at ng://' + path.join(basePath, 'my.component.html(1,5):'));
|
.toContain('Error at ng://' + path.join(basePath, 'my.component.html(1,5):'));
|
||||||
@ -282,7 +217,7 @@ describe('ngc command-line', () => {
|
|||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const exitCode = main(['-p', basePath]);
|
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||||
expect(exitCode).toEqual(0);
|
expect(exitCode).toEqual(0);
|
||||||
|
|
||||||
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngfactory.js'))).toBe(true);
|
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngfactory.js'))).toBe(true);
|
||||||
@ -307,7 +242,7 @@ describe('ngc command-line', () => {
|
|||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]);
|
const exitCode = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
|
||||||
expect(exitCode).toEqual(0);
|
expect(exitCode).toEqual(0);
|
||||||
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngfactory.js'))).toBe(true);
|
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngfactory.js'))).toBe(true);
|
||||||
expect(fs.existsSync(path.resolve(
|
expect(fs.existsSync(path.resolve(
|
||||||
@ -332,8 +267,7 @@ describe('ngc command-line', () => {
|
|||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||||
const exitCode = main(['-p', basePath], mockConsole.error);
|
|
||||||
expect(exitCode).toEqual(0);
|
expect(exitCode).toEqual(0);
|
||||||
|
|
||||||
const mymodulejs = path.resolve(outDir, 'mymodule.js');
|
const mymodulejs = path.resolve(outDir, 'mymodule.js');
|
||||||
@ -362,8 +296,7 @@ describe('ngc command-line', () => {
|
|||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||||
const exitCode = main(['-p', basePath], mockConsole.error);
|
|
||||||
expect(exitCode).toEqual(0);
|
expect(exitCode).toEqual(0);
|
||||||
|
|
||||||
const mymodulejs = path.resolve(outDir, 'mymodule.js');
|
const mymodulejs = path.resolve(outDir, 'mymodule.js');
|
||||||
@ -392,8 +325,7 @@ describe('ngc command-line', () => {
|
|||||||
export class MyModule {}
|
export class MyModule {}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const mockConsole = {error: (s: string) => {}};
|
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||||
const exitCode = main(['-p', basePath], mockConsole.error);
|
|
||||||
expect(exitCode).toEqual(0);
|
expect(exitCode).toEqual(0);
|
||||||
|
|
||||||
const mymodulejs = path.resolve(outDir, 'mymodule.js');
|
const mymodulejs = path.resolve(outDir, 'mymodule.js');
|
||||||
@ -411,9 +343,9 @@ describe('ngc command-line', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function compile(): number {
|
function compile(): number {
|
||||||
const errors: string[] = [];
|
errorSpy.calls.reset();
|
||||||
const result = main(['-p', path.join(basePath, 'tsconfig.json')], s => errors.push(s));
|
const result = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
|
||||||
expect(errors).toEqual([]);
|
expect(errorSpy).not.toHaveBeenCalled();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -607,68 +539,12 @@ describe('ngc command-line', () => {
|
|||||||
export class FlatModule {
|
export class FlatModule {
|
||||||
}`);
|
}`);
|
||||||
|
|
||||||
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]);
|
const exitCode = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
|
||||||
expect(exitCode).toEqual(0);
|
expect(exitCode).toEqual(0);
|
||||||
shouldExist('index.js');
|
shouldExist('index.js');
|
||||||
shouldExist('index.metadata.json');
|
shouldExist('index.metadata.json');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to build a flat module passing explicit options', () => {
|
|
||||||
write('public-api.ts', `
|
|
||||||
export * from './src/flat.component';
|
|
||||||
export * from './src/flat.module';`);
|
|
||||||
write('src/flat.component.html', '<div>flat module component</div>');
|
|
||||||
write('src/flat.component.ts', `
|
|
||||||
import {Component} from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'flat-comp',
|
|
||||||
templateUrl: 'flat.component.html',
|
|
||||||
})
|
|
||||||
export class FlatComponent {
|
|
||||||
}`);
|
|
||||||
write('src/flat.module.ts', `
|
|
||||||
import {NgModule} from '@angular/core';
|
|
||||||
|
|
||||||
import {FlatComponent} from './flat.component';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [
|
|
||||||
FlatComponent,
|
|
||||||
],
|
|
||||||
exports: [
|
|
||||||
FlatComponent,
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class FlatModule {
|
|
||||||
}`);
|
|
||||||
|
|
||||||
const emitResult = performCompilation(
|
|
||||||
basePath, [path.join(basePath, 'public-api.ts')], {
|
|
||||||
target: ts.ScriptTarget.ES5,
|
|
||||||
experimentalDecorators: true,
|
|
||||||
noImplicitAny: true,
|
|
||||||
moduleResolution: ts.ModuleResolutionKind.NodeJs,
|
|
||||||
rootDir: basePath,
|
|
||||||
declaration: true,
|
|
||||||
lib: ['lib.es2015.d.ts', 'lib.dom.d.ts'],
|
|
||||||
baseUrl: basePath,
|
|
||||||
outDir: path.join(basePath, 'built'),
|
|
||||||
typeRoots: [path.join(basePath, 'node_modules/@types')]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
genDir: 'ng',
|
|
||||||
flatModuleId: 'flat_module',
|
|
||||||
flatModuleOutFile: 'index.js',
|
|
||||||
skipTemplateCodegen: true
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
expect(emitResult.errorCode).toEqual(0);
|
|
||||||
shouldExist('index.js');
|
|
||||||
shouldExist('index.metadata.json');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a third-party library', () => {
|
describe('with a third-party library', () => {
|
||||||
const writeGenConfig = (skipCodegen: boolean) => {
|
const writeGenConfig = (skipCodegen: boolean) => {
|
||||||
writeConfig(`{
|
writeConfig(`{
|
||||||
@ -756,7 +632,7 @@ describe('ngc command-line', () => {
|
|||||||
it('should honor skip code generation', () => {
|
it('should honor skip code generation', () => {
|
||||||
// First ensure that we skip code generation when requested;.
|
// First ensure that we skip code generation when requested;.
|
||||||
writeGenConfig(/* skipCodegen */ true);
|
writeGenConfig(/* skipCodegen */ true);
|
||||||
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]);
|
const exitCode = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
|
||||||
expect(exitCode).toEqual(0);
|
expect(exitCode).toEqual(0);
|
||||||
modules.forEach(moduleName => {
|
modules.forEach(moduleName => {
|
||||||
shouldExist(moduleName + '.js');
|
shouldExist(moduleName + '.js');
|
||||||
@ -772,7 +648,7 @@ describe('ngc command-line', () => {
|
|||||||
it('should produce factories', () => {
|
it('should produce factories', () => {
|
||||||
// First ensure that we skip code generation when requested;.
|
// First ensure that we skip code generation when requested;.
|
||||||
writeGenConfig(/* skipCodegen */ false);
|
writeGenConfig(/* skipCodegen */ false);
|
||||||
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')]);
|
const exitCode = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
|
||||||
expect(exitCode).toEqual(0);
|
expect(exitCode).toEqual(0);
|
||||||
modules.forEach(moduleName => {
|
modules.forEach(moduleName => {
|
||||||
shouldExist(moduleName + '.js');
|
shouldExist(moduleName + '.js');
|
||||||
@ -823,7 +699,7 @@ describe('ngc command-line', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should compile without error', () => {
|
it('should compile without error', () => {
|
||||||
expect(main(['-p', path.join(basePath, 'tsconfig.json')])).toBe(0);
|
expect(mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy)).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -897,7 +773,7 @@ describe('ngc command-line', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to compile library 1', () => {
|
it('should be able to compile library 1', () => {
|
||||||
expect(main(['-p', path.join(basePath, 'lib1')])).toBe(0);
|
expect(mainSync(['-p', path.join(basePath, 'lib1')], errorSpy)).toBe(0);
|
||||||
shouldExist('lib1/module.js');
|
shouldExist('lib1/module.js');
|
||||||
shouldExist('lib1/module.ngsummary.json');
|
shouldExist('lib1/module.ngsummary.json');
|
||||||
shouldExist('lib1/module.ngsummary.js');
|
shouldExist('lib1/module.ngsummary.js');
|
||||||
@ -907,8 +783,8 @@ describe('ngc command-line', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to compile library 2', () => {
|
it('should be able to compile library 2', () => {
|
||||||
expect(main(['-p', path.join(basePath, 'lib1')])).toBe(0);
|
expect(mainSync(['-p', path.join(basePath, 'lib1')], errorSpy)).toBe(0);
|
||||||
expect(main(['-p', path.join(basePath, 'lib2')])).toBe(0);
|
expect(mainSync(['-p', path.join(basePath, 'lib2')], errorSpy)).toBe(0);
|
||||||
shouldExist('lib2/module.js');
|
shouldExist('lib2/module.js');
|
||||||
shouldExist('lib2/module.ngsummary.json');
|
shouldExist('lib2/module.ngsummary.json');
|
||||||
shouldExist('lib2/module.ngsummary.js');
|
shouldExist('lib2/module.ngsummary.js');
|
||||||
@ -919,12 +795,12 @@ describe('ngc command-line', () => {
|
|||||||
|
|
||||||
describe('building an application', () => {
|
describe('building an application', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
expect(main(['-p', path.join(basePath, 'lib1')])).toBe(0);
|
expect(mainSync(['-p', path.join(basePath, 'lib1')], errorSpy)).toBe(0);
|
||||||
expect(main(['-p', path.join(basePath, 'lib2')])).toBe(0);
|
expect(mainSync(['-p', path.join(basePath, 'lib2')], errorSpy)).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should build without error', () => {
|
it('should build without error', () => {
|
||||||
expect(main(['-p', path.join(basePath, 'app')])).toBe(0);
|
expect(mainSync(['-p', path.join(basePath, 'app')], errorSpy)).toBe(0);
|
||||||
shouldExist('app/main.js');
|
shouldExist('app/main.js');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -32,7 +32,6 @@
|
|||||||
"files": [
|
"files": [
|
||||||
"index.ts",
|
"index.ts",
|
||||||
"src/main.ts",
|
"src/main.ts",
|
||||||
"src/ngc.ts",
|
|
||||||
"src/extract_i18n.ts",
|
"src/extract_i18n.ts",
|
||||||
"../../node_modules/@types/node/index.d.ts",
|
"../../node_modules/@types/node/index.d.ts",
|
||||||
"../../node_modules/@types/jasmine/index.d.ts",
|
"../../node_modules/@types/jasmine/index.d.ts",
|
||||||
|
@ -120,51 +120,40 @@ export class MetadataWriterHost extends DelegatingHost {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SyntheticIndexHost extends DelegatingHost {
|
export function createSyntheticIndexHost<H extends ts.CompilerHost>(
|
||||||
private normalSyntheticIndexName: string;
|
delegate: H, syntheticIndex: {name: string, content: string, metadata: string}): H {
|
||||||
private indexContent: string;
|
const normalSyntheticIndexName = normalize(syntheticIndex.name);
|
||||||
private indexMetadata: string;
|
const indexContent = syntheticIndex.content;
|
||||||
|
const indexMetadata = syntheticIndex.metadata;
|
||||||
|
|
||||||
constructor(
|
const newHost = Object.create(delegate);
|
||||||
delegate: ts.CompilerHost,
|
newHost.fileExists = (fileName: string): boolean => {
|
||||||
syntheticIndex: {name: string, content: string, metadata: string}) {
|
return normalize(fileName) == normalSyntheticIndexName || delegate.fileExists(fileName);
|
||||||
super(delegate);
|
};
|
||||||
this.normalSyntheticIndexName = normalize(syntheticIndex.name);
|
|
||||||
this.indexContent = syntheticIndex.content;
|
|
||||||
this.indexMetadata = syntheticIndex.metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
fileExists = (fileName: string):
|
newHost.readFile = (fileName: string) => {
|
||||||
boolean => {
|
return normalize(fileName) == normalSyntheticIndexName ? indexContent :
|
||||||
return normalize(fileName) == this.normalSyntheticIndexName ||
|
delegate.readFile(fileName);
|
||||||
this.delegate.fileExists(fileName);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
readFile =
|
newHost.getSourceFile =
|
||||||
(fileName: string) => {
|
(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) => {
|
||||||
return normalize(fileName) == this.normalSyntheticIndexName ?
|
if (normalize(fileName) == normalSyntheticIndexName) {
|
||||||
this.indexContent :
|
return ts.createSourceFile(fileName, indexContent, languageVersion, true);
|
||||||
this.delegate.readFile(fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
getSourceFile =
|
|
||||||
(fileName: string, languageVersion: ts.ScriptTarget,
|
|
||||||
onError?: (message: string) => void) => {
|
|
||||||
if (normalize(fileName) == this.normalSyntheticIndexName) {
|
|
||||||
return ts.createSourceFile(fileName, this.indexContent, languageVersion, true);
|
|
||||||
}
|
}
|
||||||
return this.delegate.getSourceFile(fileName, languageVersion, onError);
|
return delegate.getSourceFile(fileName, languageVersion, onError);
|
||||||
}
|
};
|
||||||
|
|
||||||
writeFile: ts.WriteFileCallback =
|
newHost.writeFile =
|
||||||
(fileName: string, data: string, writeByteOrderMark: boolean,
|
(fileName: string, data: string, writeByteOrderMark: boolean,
|
||||||
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
|
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
|
||||||
this.delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
||||||
if (fileName.match(DTS) && sourceFiles && sourceFiles.length == 1 &&
|
if (fileName.match(DTS) && sourceFiles && sourceFiles.length == 1 &&
|
||||||
normalize(sourceFiles[0].fileName) == this.normalSyntheticIndexName) {
|
normalize(sourceFiles[0].fileName) == normalSyntheticIndexName) {
|
||||||
// If we are writing the synthetic index, write the metadata along side.
|
// If we are writing the synthetic index, write the metadata along side.
|
||||||
const metadataName = fileName.replace(DTS, '.metadata.json');
|
const metadataName = fileName.replace(DTS, '.metadata.json');
|
||||||
writeFileSync(metadataName, this.indexMetadata, {encoding: 'utf8'});
|
writeFileSync(metadataName, indexMetadata, {encoding: 'utf8'});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
return newHost;
|
||||||
}
|
}
|
@ -13,7 +13,7 @@ import * as ts from 'typescript';
|
|||||||
|
|
||||||
import {CompilerHostAdapter, MetadataBundler} from './bundler';
|
import {CompilerHostAdapter, MetadataBundler} from './bundler';
|
||||||
import {CliOptions} from './cli_options';
|
import {CliOptions} from './cli_options';
|
||||||
import {MetadataWriterHost, SyntheticIndexHost} from './compiler_host';
|
import {MetadataWriterHost, createSyntheticIndexHost} from './compiler_host';
|
||||||
import {privateEntriesToIndex} from './index_writer';
|
import {privateEntriesToIndex} from './index_writer';
|
||||||
import NgOptions from './options';
|
import NgOptions from './options';
|
||||||
import {check, tsc} from './tsc';
|
import {check, tsc} from './tsc';
|
||||||
@ -33,9 +33,9 @@ export interface CodegenExtension {
|
|||||||
host: ts.CompilerHost): Promise<string[]>;
|
host: ts.CompilerHost): Promise<string[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createBundleIndexHost(
|
export function createBundleIndexHost<H extends ts.CompilerHost>(
|
||||||
ngOptions: NgOptions, rootFiles: string[],
|
ngOptions: NgOptions, rootFiles: string[],
|
||||||
host: ts.CompilerHost): {host: ts.CompilerHost, indexName?: string, errors?: ts.Diagnostic[]} {
|
host: H): {host: H, indexName?: string, errors?: ts.Diagnostic[]} {
|
||||||
const files = rootFiles.filter(f => !DTS.test(f));
|
const files = rootFiles.filter(f => !DTS.test(f));
|
||||||
if (files.length != 1) {
|
if (files.length != 1) {
|
||||||
return {
|
return {
|
||||||
@ -61,7 +61,7 @@ export function createBundleIndexHost(
|
|||||||
path.join(path.dirname(indexModule), ngOptions.flatModuleOutFile !.replace(JS_EXT, '.ts'));
|
path.join(path.dirname(indexModule), ngOptions.flatModuleOutFile !.replace(JS_EXT, '.ts'));
|
||||||
const libraryIndex = `./${path.basename(indexModule)}`;
|
const libraryIndex = `./${path.basename(indexModule)}`;
|
||||||
const content = privateEntriesToIndex(libraryIndex, metadataBundle.privates);
|
const content = privateEntriesToIndex(libraryIndex, metadataBundle.privates);
|
||||||
host = new SyntheticIndexHost(host, {name, content, metadata});
|
host = createSyntheticIndexHost(host, {name, content, metadata});
|
||||||
return {host, indexName: name};
|
return {host, indexName: name};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,26 +9,25 @@
|
|||||||
// TODO(chuckj): Remove the requirement for a fake 'reflect` implementation from
|
// TODO(chuckj): Remove the requirement for a fake 'reflect` implementation from
|
||||||
// the compiler
|
// the compiler
|
||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
import {performCompilation} from '@angular/compiler-cli';
|
|
||||||
|
import {calcProjectFileAndBasePath, createNgCompilerOptions, formatDiagnostics, performCompilation} from '@angular/compiler-cli';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
// Note, the tsc_wrapped module comes from rules_typescript, not from @angular/tsc-wrapped
|
// Note, the tsc_wrapped module comes from rules_typescript, not from @angular/tsc-wrapped
|
||||||
import {parseTsconfig} from 'tsc_wrapped';
|
import {parseTsconfig} from 'tsc_wrapped';
|
||||||
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
function main(args: string[]) {
|
function main(args: string[]) {
|
||||||
const [{options, bazelOpts, files, config}] = parseTsconfig(args[1]);
|
const project = args[1];
|
||||||
const ngOptions: {expectedOut: string[]} = (config as any).angularCompilerOptions;
|
const [{options: tsOptions, bazelOpts, files, config}] = parseTsconfig(project);
|
||||||
|
const {basePath} = calcProjectFileAndBasePath(project);
|
||||||
|
const ngOptions = createNgCompilerOptions(basePath, config, tsOptions);
|
||||||
|
|
||||||
const parsedArgs = require('minimist')(args);
|
const {diagnostics} = performCompilation(files, ngOptions);
|
||||||
const project = parsedArgs.p || parsedArgs.project || '.';
|
if (diagnostics.length) {
|
||||||
|
console.error(formatDiagnostics(ngOptions, diagnostics));
|
||||||
const projectDir = fs.lstatSync(project).isFile() ? path.dirname(project) : project;
|
}
|
||||||
|
return diagnostics.some(d => d.category === ts.DiagnosticCategory.Error) ? 1 : 0;
|
||||||
// file names in tsconfig are resolved relative to this absolute path
|
|
||||||
const basePath = path.resolve(process.cwd(), projectDir);
|
|
||||||
const result = performCompilation(basePath, files, options, ngOptions, undefined);
|
|
||||||
|
|
||||||
return result.errorCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
|
Reference in New Issue
Block a user