refactor(compiler-cli): cleanup API for transformer based ngc
This is in preparation for watch mode.
This commit is contained in:
parent
cac130eff9
commit
27d901a51d
|
@ -20,7 +20,7 @@ export {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Sp
|
|||
export * from './src/transformers/api';
|
||||
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.
|
||||
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 * as ts from 'typescript';
|
||||
|
||||
import {Diagnostic, DiagnosticCategory} from '../transformers/api';
|
||||
import {Diagnostic} from '../transformers/api';
|
||||
|
||||
interface FactoryInfo {
|
||||
source: ts.SourceFile;
|
||||
|
@ -143,7 +143,7 @@ export class TypeChecker {
|
|||
const diagnosticsList = diagnosticsFor(fileName);
|
||||
diagnosticsList.push({
|
||||
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');
|
||||
}
|
||||
|
||||
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 {
|
||||
const {sourceText, context} =
|
||||
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 fs from 'fs';
|
||||
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 {readConfiguration} from './perform-compile';
|
||||
|
||||
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,
|
||||
host: ts.CompilerHost) {
|
||||
if (ngOptions.enableSummariesForJit === undefined) {
|
||||
|
@ -32,38 +104,8 @@ function 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
|
||||
if (require.main === module) {
|
||||
const args = process.argv.slice(2);
|
||||
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 {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)));
|
||||
}
|
||||
main(args).then((exitCode: number) => process.exitCode = exitCode);
|
||||
}
|
||||
|
|
|
@ -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$/;
|
||||
|
||||
export type Diagnostics = ts.Diagnostic[] | api.Diagnostic[];
|
||||
export type Diagnostics = Array<ts.Diagnostic|api.Diagnostic>;
|
||||
|
||||
function isTsDiagnostics(diagnostics: any): diagnostics is ts.Diagnostic[] {
|
||||
return diagnostics && diagnostics[0] && (diagnostics[0].file || diagnostics[0].messageText);
|
||||
function isTsDiagnostic(diagnostic: any): diagnostic is ts.Diagnostic {
|
||||
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 (isTsDiagnostics(diags)) {
|
||||
return ts.formatDiagnostics(diags, {
|
||||
getCurrentDirectory: () => cwd,
|
||||
getCanonicalFileName: fileName => fileName,
|
||||
getNewLine: () => ts.sys.newLine
|
||||
});
|
||||
} else {
|
||||
return diags
|
||||
.map(d => {
|
||||
let res = api.DiagnosticCategory[d.category];
|
||||
const tsFormatHost: ts.FormatDiagnosticsHost = {
|
||||
getCurrentDirectory: () => options.basePath || process.cwd(),
|
||||
getCanonicalFileName: fileName => fileName,
|
||||
getNewLine: () => ts.sys.newLine
|
||||
};
|
||||
return diags
|
||||
.map(d => {
|
||||
if (isTsDiagnostic(d)) {
|
||||
return ts.formatDiagnostics([d], tsFormatHost);
|
||||
} else {
|
||||
let res = ts.DiagnosticCategory[d.category];
|
||||
if (d.span) {
|
||||
res +=
|
||||
` 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`;
|
||||
}
|
||||
return res;
|
||||
})
|
||||
.join();
|
||||
}
|
||||
}
|
||||
})
|
||||
.join();
|
||||
} else
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw a syntax error exception with a message formatted for output
|
||||
* if the args parameter contains diagnostics errors.
|
||||
*
|
||||
* @param cwd The directory to report error as relative to.
|
||||
* @param args A list of potentially empty diagnostic errors.
|
||||
*/
|
||||
export function throwOnDiagnostics(cwd: string, ...args: Diagnostics[]) {
|
||||
if (args.some(diags => !!(diags && diags[0]))) {
|
||||
throw syntaxError(args.map(diags => {
|
||||
if (diags && diags[0]) {
|
||||
return formatDiagnostics(cwd, diags);
|
||||
}
|
||||
})
|
||||
.filter(message => !!message)
|
||||
.join(''));
|
||||
}
|
||||
export interface ParsedConfiguration {
|
||||
options: api.CompilerOptions;
|
||||
rootNames: string[];
|
||||
errors: Diagnostics;
|
||||
}
|
||||
|
||||
export function calcProjectFileAndBasePath(project: string):
|
||||
{projectFile: string, basePath: string} {
|
||||
const projectIsDir = fs.lstatSync(project).isDirectory();
|
||||
const projectFile = projectIsDir ? path.join(project, 'tsconfig.json') : project;
|
||||
const projectDir = projectIsDir ? project : path.dirname(project);
|
||||
const basePath = path.resolve(process.cwd(), projectDir);
|
||||
return {projectFile, basePath};
|
||||
}
|
||||
|
||||
export function createNgCompilerOptions(
|
||||
basePath: string, config: any, tsOptions: ts.CompilerOptions): api.CompilerOptions {
|
||||
return {...tsOptions, ...config.angularCompilerOptions, genDir: basePath, basePath};
|
||||
}
|
||||
|
||||
export function readConfiguration(
|
||||
project: string, basePath: string,
|
||||
checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics,
|
||||
existingOptions?: ts.CompilerOptions) {
|
||||
// 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);
|
||||
project: string, existingOptions?: ts.CompilerOptions): ParsedConfiguration {
|
||||
try {
|
||||
const {projectFile, basePath} = calcProjectFileAndBasePath(project);
|
||||
|
||||
if (error) checkFunc(basePath, [error]);
|
||||
const parseConfigHost = {
|
||||
useCaseSensitiveFileNames: true,
|
||||
fileExists: fs.existsSync,
|
||||
readDirectory: ts.sys.readDirectory,
|
||||
readFile: ts.sys.readFile
|
||||
};
|
||||
const parsed = ts.parseJsonConfigFileContent(config, parseConfigHost, basePath, existingOptions);
|
||||
let {config, error} = ts.readConfigFile(projectFile, ts.sys.readFile);
|
||||
|
||||
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
|
||||
// Parsed options are already converted to absolute paths
|
||||
const ngOptions = config.angularCompilerOptions || {};
|
||||
// Ignore the genDir option
|
||||
ngOptions.genDir = basePath;
|
||||
|
||||
return {parsed, ngOptions};
|
||||
const options = createNgCompilerOptions(basePath, config, parsed.options);
|
||||
return {rootNames, options, errors: parsed.errors};
|
||||
} catch (e) {
|
||||
const errors: Diagnostics = [{
|
||||
category: ts.DiagnosticCategory.Error,
|
||||
message: e.stack,
|
||||
}];
|
||||
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(
|
||||
basePath: string, files: string[], options: ts.CompilerOptions, ngOptions: api.CompilerOptions,
|
||||
consoleError: (s: string) => void = console.error,
|
||||
checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics,
|
||||
tsCompilerHost?: ts.CompilerHost): {errorCode: number, result?: api.EmitResult} {
|
||||
rootNames: string[], options: api.CompilerOptions, host?: api.CompilerHost,
|
||||
oldProgram?: api.Program): {
|
||||
program?: api.Program,
|
||||
emitResult?: api.EmitResult,
|
||||
diagnostics: Diagnostics,
|
||||
} {
|
||||
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');
|
||||
}
|
||||
|
||||
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 {
|
||||
ngOptions.basePath = basePath;
|
||||
ngOptions.genDir = basePath;
|
||||
|
||||
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;
|
||||
if (!host) {
|
||||
host = ng.createNgCompilerHost({options});
|
||||
}
|
||||
|
||||
const ngHostOptions = {...options, ...ngOptions};
|
||||
const ngHost = ng.createHost({tsHost: host, options: ngHostOptions});
|
||||
|
||||
const ngProgram =
|
||||
ng.createProgram({rootNames: rootFileNames, host: ngHost, options: ngHostOptions});
|
||||
program = ng.createProgram({rootNames, host, options, oldProgram});
|
||||
|
||||
let shouldEmit = true;
|
||||
// Check parameter diagnostics
|
||||
checkFunc(basePath, ngProgram.getTsOptionDiagnostics(), ngProgram.getNgOptionDiagnostics());
|
||||
shouldEmit = shouldEmit && checkDiagnostics([
|
||||
...program !.getTsOptionDiagnostics(), ...program !.getNgOptionDiagnostics()
|
||||
]);
|
||||
|
||||
// Check syntactic diagnostics
|
||||
checkFunc(basePath, ngProgram.getTsSyntacticDiagnostics());
|
||||
shouldEmit = shouldEmit && checkDiagnostics(program !.getTsSyntacticDiagnostics());
|
||||
|
||||
// Check TypeScript semantic and Angular structure diagnostics
|
||||
checkFunc(
|
||||
basePath, ngProgram.getTsSemanticDiagnostics(), ngProgram.getNgStructuralDiagnostics());
|
||||
shouldEmit =
|
||||
shouldEmit &&
|
||||
checkDiagnostics(
|
||||
[...program !.getTsSemanticDiagnostics(), ...program !.getNgStructuralDiagnostics()]);
|
||||
|
||||
// Check Angular semantic diagnostics
|
||||
checkFunc(basePath, ngProgram.getNgSemanticDiagnostics());
|
||||
shouldEmit = shouldEmit && checkDiagnostics(program !.getNgSemanticDiagnostics());
|
||||
|
||||
const result = ngProgram.emit({
|
||||
emitFlags: api.EmitFlags.Default |
|
||||
((ngOptions.skipMetadataEmit || ngOptions.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata)
|
||||
});
|
||||
|
||||
checkFunc(basePath, result.diagnostics);
|
||||
|
||||
return {errorCode: 0, result};
|
||||
} catch (e) {
|
||||
if (isSyntaxError(e)) {
|
||||
consoleError(e.message);
|
||||
return {errorCode: 1};
|
||||
if (shouldEmit) {
|
||||
const emitResult = program !.emit({
|
||||
emitFlags: api.EmitFlags.Default |
|
||||
((options.skipMetadataEmit || options.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata)
|
||||
});
|
||||
allDiagnostics.push(...emitResult.diagnostics);
|
||||
}
|
||||
|
||||
throw e;
|
||||
} catch (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 * as ts from 'typescript';
|
||||
|
||||
export enum DiagnosticCategory {
|
||||
Warning = 0,
|
||||
Error = 1,
|
||||
Message = 2,
|
||||
}
|
||||
|
||||
export interface Diagnostic {
|
||||
message: string;
|
||||
span?: ParseSourceSpan;
|
||||
category: DiagnosticCategory;
|
||||
category: ts.DiagnosticCategory;
|
||||
}
|
||||
|
||||
export interface CompilerOptions extends ts.CompilerOptions {
|
||||
|
|
|
@ -13,8 +13,9 @@ import {createModuleFilenameResolver} from './module_filename_resolver';
|
|||
export {createProgram} from './program';
|
||||
export {createModuleFilenameResolver};
|
||||
|
||||
export function createHost({tsHost, options}: {tsHost: ts.CompilerHost, options: CompilerOptions}):
|
||||
CompilerHost {
|
||||
export function createNgCompilerHost(
|
||||
{options, tsHost = ts.createCompilerHost(options, true)}:
|
||||
{options: CompilerOptions, tsHost?: ts.CompilerHost}): CompilerHost {
|
||||
const resolver = createModuleFilenameResolver(tsHost, options);
|
||||
|
||||
const host = Object.create(tsHost);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import {AotCompiler, AotCompilerOptions, GeneratedFile, NgAnalyzedModules, createAotCompiler, getParseErrors, isSyntaxError, toTypeScript} from '@angular/compiler';
|
||||
import {MissingTranslationStrategy} from '@angular/core';
|
||||
import {createBundleIndexHost} from '@angular/tsc-wrapped';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as tsickle from 'tsickle';
|
||||
|
@ -16,7 +17,7 @@ import * as ts from 'typescript';
|
|||
import {CompilerHost as AotCompilerHost} from '../compiler_host';
|
||||
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 {getAngularEmitterTransformFactory} from './node_emitter_transform';
|
||||
|
||||
|
@ -31,8 +32,6 @@ const emptyModules: NgAnalyzedModules = {
|
|||
};
|
||||
|
||||
class AngularCompilerProgram implements Program {
|
||||
// Initialized in the constructor
|
||||
private oldTsProgram: ts.Program|undefined;
|
||||
private tsProgram: ts.Program;
|
||||
private aotCompilerHost: AotCompilerHost;
|
||||
private compiler: AotCompiler;
|
||||
|
@ -49,12 +48,26 @@ class AngularCompilerProgram implements Program {
|
|||
private _generatedFileDiagnostics: Diagnostic[]|undefined;
|
||||
private _typeChecker: TypeChecker|undefined;
|
||||
private _semanticDiagnostics: Diagnostic[]|undefined;
|
||||
private _optionsDiagnostics: Diagnostic[] = [];
|
||||
|
||||
constructor(
|
||||
private rootNames: string[], private options: CompilerOptions, private host: CompilerHost,
|
||||
private oldProgram?: Program) {
|
||||
this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined;
|
||||
this.tsProgram = ts.createProgram(rootNames, options, host, this.oldTsProgram);
|
||||
if (options.flatModuleOutFile && !options.skipMetadataEmit) {
|
||||
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.tsProgram.getSourceFiles()
|
||||
.map(sf => sf.fileName)
|
||||
|
@ -78,7 +91,7 @@ class AngularCompilerProgram implements Program {
|
|||
}
|
||||
|
||||
getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken): Diagnostic[] {
|
||||
return getNgOptionDiagnostics(this.options);
|
||||
return [...this._optionsDiagnostics, ...getNgOptionDiagnostics(this.options)];
|
||||
}
|
||||
|
||||
getTsSyntacticDiagnostics(sourceFile?: ts.SourceFile, cancellationToken?: ts.CancellationToken):
|
||||
|
@ -237,11 +250,11 @@ class AngularCompilerProgram implements Program {
|
|||
this._structuralDiagnostics =
|
||||
parserErrors.map<Diagnostic>(e => ({
|
||||
message: e.contextualMessage(),
|
||||
category: DiagnosticCategory.Error,
|
||||
category: ts.DiagnosticCategory.Error,
|
||||
span: e.span
|
||||
}));
|
||||
} else {
|
||||
this._structuralDiagnostics = [{message: e.message, category: DiagnosticCategory.Error}];
|
||||
this._structuralDiagnostics = [{message: e.message, category: ts.DiagnosticCategory.Error}];
|
||||
}
|
||||
this._analyzedModules = emptyModules;
|
||||
return emptyModules;
|
||||
|
@ -272,7 +285,8 @@ class AngularCompilerProgram implements Program {
|
|||
return this.options.skipTemplateCodegen ? [] : result;
|
||||
} catch (e) {
|
||||
if (isSyntaxError(e)) {
|
||||
this._generatedFileDiagnostics = [{message: e.message, category: DiagnosticCategory.Error}];
|
||||
this._generatedFileDiagnostics =
|
||||
[{message: e.message, category: ts.DiagnosticCategory.Error}];
|
||||
return [];
|
||||
}
|
||||
throw e;
|
||||
|
@ -395,7 +409,7 @@ function getNgOptionDiagnostics(options: CompilerOptions): Diagnostic[] {
|
|||
return [{
|
||||
message:
|
||||
'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);
|
||||
}
|
||||
|
||||
describe('compiler-cli', () => {
|
||||
describe('compiler-cli with disableTransformerPipeline', () => {
|
||||
let basePath: string;
|
||||
let outDir: string;
|
||||
let write: (fileName: string, content: string) => void;
|
||||
let errorSpy: jasmine.Spy&((s: string) => void);
|
||||
|
||||
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(() => {
|
||||
errorSpy = jasmine.createSpy('consoleError');
|
||||
basePath = makeTempDir();
|
||||
write = (fileName: string, content: string) => {
|
||||
fs.writeFileSync(path.join(basePath, fileName), content, {encoding: 'utf-8'});
|
||||
|
@ -58,13 +64,9 @@ describe('compiler-cli', () => {
|
|||
writeConfig();
|
||||
write('test.ts', 'export const A = 1;');
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
main({p: basePath}, mockConsole.error)
|
||||
main(['-p', basePath], errorSpy)
|
||||
.then((exitCode) => {
|
||||
expect(mockConsole.error).not.toHaveBeenCalled();
|
||||
expect(errorSpy).not.toHaveBeenCalled();
|
||||
expect(exitCode).toEqual(0);
|
||||
done();
|
||||
})
|
||||
|
@ -76,16 +78,11 @@ describe('compiler-cli', () => {
|
|||
"extends": "./tsconfig-base.json",
|
||||
"files": ["test.ts"]
|
||||
}`);
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
main({p: basePath}, mockConsole.error)
|
||||
main(['-p', basePath], errorSpy)
|
||||
.then((exitCode) => {
|
||||
expect(mockConsole.error)
|
||||
.toHaveBeenCalledWith(
|
||||
`Error File '` + path.join(basePath, 'test.ts') + `' not found.`);
|
||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
`Error File '` + path.join(basePath, 'test.ts') + `' not found.`);
|
||||
expect(exitCode).toEqual(1);
|
||||
done();
|
||||
})
|
||||
|
@ -96,16 +93,10 @@ describe('compiler-cli', () => {
|
|||
writeConfig();
|
||||
write('test.ts', 'foo;');
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
main({p: basePath}, mockConsole.error)
|
||||
main(['-p', basePath], errorSpy)
|
||||
.then((exitCode) => {
|
||||
expect(mockConsole.error)
|
||||
.toHaveBeenCalledWith(
|
||||
'Error at ' + path.join(basePath, 'test.ts') + `:1:1: Cannot find name 'foo'.`);
|
||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
'Error at ' + path.join(basePath, 'test.ts') + `:1:1: Cannot find name 'foo'.`);
|
||||
expect(exitCode).toEqual(1);
|
||||
done();
|
||||
})
|
||||
|
@ -116,17 +107,11 @@ describe('compiler-cli', () => {
|
|||
writeConfig();
|
||||
write('test.ts', `import {MyClass} from './not-exist-deps';`);
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
main({p: basePath}, mockConsole.error)
|
||||
main(['-p', basePath], errorSpy)
|
||||
.then((exitCode) => {
|
||||
expect(mockConsole.error)
|
||||
.toHaveBeenCalledWith(
|
||||
'Error at ' + path.join(basePath, 'test.ts') +
|
||||
`:1:23: Cannot find module './not-exist-deps'.`);
|
||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
'Error at ' + path.join(basePath, 'test.ts') +
|
||||
`:1:23: Cannot find module './not-exist-deps'.`);
|
||||
expect(exitCode).toEqual(1);
|
||||
done();
|
||||
})
|
||||
|
@ -138,17 +123,11 @@ describe('compiler-cli', () => {
|
|||
write('empty-deps.ts', 'export const A = 1;');
|
||||
write('test.ts', `import {MyClass} from './empty-deps';`);
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
main({p: basePath}, mockConsole.error)
|
||||
main(['-p', basePath], errorSpy)
|
||||
.then((exitCode) => {
|
||||
expect(mockConsole.error)
|
||||
.toHaveBeenCalledWith(
|
||||
'Error at ' + path.join(basePath, 'test.ts') + `:1:9: Module '"` +
|
||||
path.join(basePath, 'empty-deps') + `"' has no exported member 'MyClass'.`);
|
||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
'Error at ' + path.join(basePath, 'test.ts') + `:1:9: Module '"` +
|
||||
path.join(basePath, 'empty-deps') + `"' has no exported member 'MyClass'.`);
|
||||
expect(exitCode).toEqual(1);
|
||||
done();
|
||||
})
|
||||
|
@ -163,18 +142,12 @@ describe('compiler-cli', () => {
|
|||
A();
|
||||
`);
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
main({p: basePath}, mockConsole.error)
|
||||
main(['-p', basePath], errorSpy)
|
||||
.then((exitCode) => {
|
||||
expect(mockConsole.error)
|
||||
.toHaveBeenCalledWith(
|
||||
'Error at ' + path.join(basePath, 'test.ts') +
|
||||
':3:7: Cannot invoke an expression whose type lacks a call signature. ' +
|
||||
'Type \'String\' has no compatible call signatures.');
|
||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
'Error at ' + path.join(basePath, 'test.ts') +
|
||||
':3:7: Cannot invoke an expression whose type lacks a call signature. ' +
|
||||
'Type \'String\' has no compatible call signatures.');
|
||||
expect(exitCode).toEqual(1);
|
||||
done();
|
||||
})
|
||||
|
@ -184,14 +157,11 @@ describe('compiler-cli', () => {
|
|||
it('should print the stack trace on compiler internal errors', (done) => {
|
||||
write('test.ts', 'export const A = 1;');
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
main({p: 'not-exist'}, mockConsole.error)
|
||||
main(['-p', 'not-exist'], errorSpy)
|
||||
.then((exitCode) => {
|
||||
expect(mockConsole.error).toHaveBeenCalled();
|
||||
expect(mockConsole.error).toHaveBeenCalledWith('Compilation failed');
|
||||
expect(errorSpy).toHaveBeenCalled();
|
||||
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);
|
||||
done();
|
||||
})
|
||||
|
@ -215,7 +185,7 @@ describe('compiler-cli', () => {
|
|||
export class MyModule {}
|
||||
`);
|
||||
|
||||
main({p: basePath})
|
||||
main(['-p', basePath], errorSpy)
|
||||
.then((exitCode) => {
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
|
@ -244,11 +214,7 @@ describe('compiler-cli', () => {
|
|||
export class MyModule {}
|
||||
`);
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
const errorSpy = spyOn(mockConsole, 'error');
|
||||
|
||||
main({p: basePath}, mockConsole.error)
|
||||
main(['-p', basePath], errorSpy)
|
||||
.then((exitCode) => {
|
||||
expect(errorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(errorSpy.calls.mostRecent().args[0])
|
||||
|
@ -280,7 +246,7 @@ describe('compiler-cli', () => {
|
|||
export class MyModule {}
|
||||
`);
|
||||
|
||||
main({p: basePath})
|
||||
main(['-p', basePath], errorSpy)
|
||||
.then((exitCode) => {
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
|
@ -307,7 +273,7 @@ describe('compiler-cli', () => {
|
|||
export class MyModule {}
|
||||
`);
|
||||
|
||||
main({p: basePath})
|
||||
main(['-p', basePath], errorSpy)
|
||||
.then((exitCode) => {
|
||||
expect(exitCode).toEqual(0);
|
||||
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngsummary.js'))).toBe(false);
|
||||
|
@ -333,7 +299,7 @@ describe('compiler-cli', () => {
|
|||
export class MyModule {}
|
||||
`);
|
||||
|
||||
main({p: basePath})
|
||||
main(['-p', basePath], errorSpy)
|
||||
.then((exitCode) => {
|
||||
expect(exitCode).toEqual(0);
|
||||
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 ts from 'typescript';
|
||||
|
||||
import {main} from '../src/ngc';
|
||||
import {performCompilation, readConfiguration} from '../src/perform-compile';
|
||||
import {mainSync} from '../src/main';
|
||||
|
||||
function getNgRootDir() {
|
||||
const moduleFilename = module.filename.replace(/\\/g, '/');
|
||||
|
@ -20,16 +19,18 @@ function getNgRootDir() {
|
|||
return moduleFilename.substr(0, distIndex);
|
||||
}
|
||||
|
||||
describe('ngc command-line', () => {
|
||||
describe('ngc transformer command-line', () => {
|
||||
let basePath: string;
|
||||
let outDir: string;
|
||||
let write: (fileName: string, content: string) => void;
|
||||
let errorSpy: jasmine.Spy&((s: string) => void);
|
||||
|
||||
function writeConfig(tsconfig: string = '{"extends": "./tsconfig-base.json"}') {
|
||||
write('tsconfig.json', tsconfig);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
errorSpy = jasmine.createSpy('consoleError');
|
||||
basePath = makeTempDir();
|
||||
write = (fileName: string, content: string) => {
|
||||
const dir = path.dirname(fileName);
|
||||
|
@ -66,35 +67,9 @@ describe('ngc command-line', () => {
|
|||
writeConfig();
|
||||
write('test.ts', 'export const A = 1;');
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
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();
|
||||
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||
expect(errorSpy).not.toHaveBeenCalled();
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
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",
|
||||
"files": ["test.ts"]
|
||||
}`);
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
const exitCode = main(['-p', basePath], mockConsole.error);
|
||||
expect(mockConsole.error)
|
||||
.toHaveBeenCalledWith(
|
||||
`error TS6053: File '` + path.join(basePath, 'test.ts') + `' not found.` +
|
||||
'\n');
|
||||
expect(mockConsole.error).not.toHaveBeenCalledWith('Compilation failed');
|
||||
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
`error TS6053: File '` + path.join(basePath, 'test.ts') + `' not found.` +
|
||||
'\n');
|
||||
expect(exitCode).toEqual(1);
|
||||
});
|
||||
|
||||
|
@ -119,16 +89,10 @@ describe('ngc command-line', () => {
|
|||
writeConfig();
|
||||
write('test.ts', 'foo;');
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
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');
|
||||
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
`test.ts(1,1): error TS2304: Cannot find name 'foo'.` +
|
||||
'\n');
|
||||
expect(exitCode).toEqual(1);
|
||||
});
|
||||
|
||||
|
@ -136,16 +100,10 @@ describe('ngc command-line', () => {
|
|||
writeConfig();
|
||||
write('test.ts', `import {MyClass} from './not-exist-deps';`);
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
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');
|
||||
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
`test.ts(1,23): error TS2307: Cannot find module './not-exist-deps'.` +
|
||||
'\n');
|
||||
expect(exitCode).toEqual(1);
|
||||
});
|
||||
|
||||
|
@ -154,17 +112,11 @@ describe('ngc command-line', () => {
|
|||
write('empty-deps.ts', 'export const A = 1;');
|
||||
write('test.ts', `import {MyClass} from './empty-deps';`);
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
const exitCode = main(['-p', basePath], mockConsole.error);
|
||||
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');
|
||||
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
`test.ts(1,9): error TS2305: Module '"` + path.join(basePath, 'empty-deps') +
|
||||
`"' has no exported member 'MyClass'.` +
|
||||
'\n');
|
||||
expect(exitCode).toEqual(1);
|
||||
});
|
||||
|
||||
|
@ -176,30 +128,21 @@ describe('ngc command-line', () => {
|
|||
A();
|
||||
`);
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
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');
|
||||
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||
expect(errorSpy).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(exitCode).toEqual(1);
|
||||
});
|
||||
|
||||
it('should print the stack trace on compiler internal errors', () => {
|
||||
write('test.ts', 'export const A = 1;');
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
spyOn(mockConsole, 'error');
|
||||
|
||||
const exitCode = main(['-p', 'not-exist'], mockConsole.error);
|
||||
expect(mockConsole.error).toHaveBeenCalled();
|
||||
expect(mockConsole.error).toHaveBeenCalledWith('Compilation failed');
|
||||
expect(exitCode).toEqual(2);
|
||||
const exitCode = mainSync(['-p', 'not-exist'], errorSpy);
|
||||
expect(errorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(errorSpy.calls.mostRecent().args[0]).toContain('no such file or directory');
|
||||
expect(errorSpy.calls.mostRecent().args[0]).toContain('at Error (native)');
|
||||
expect(exitCode).toEqual(1);
|
||||
});
|
||||
|
||||
describe('compile ngfactory files', () => {
|
||||
|
@ -218,11 +161,7 @@ describe('ngc command-line', () => {
|
|||
export class MyModule {}
|
||||
`);
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
const errorSpy = spyOn(mockConsole, 'error');
|
||||
|
||||
const exitCode = main(['-p', basePath], mockConsole.error);
|
||||
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||
expect(errorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(errorSpy.calls.mostRecent().args[0])
|
||||
.toContain('Error at ng://' + path.join(basePath, 'mymodule.ts.MyComp.html'));
|
||||
|
@ -253,11 +192,7 @@ describe('ngc command-line', () => {
|
|||
export class MyModule {}
|
||||
`);
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
|
||||
const errorSpy = spyOn(mockConsole, 'error');
|
||||
|
||||
const exitCode = main(['-p', basePath], mockConsole.error);
|
||||
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||
expect(errorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(errorSpy.calls.mostRecent().args[0])
|
||||
.toContain('Error at ng://' + path.join(basePath, 'my.component.html(1,5):'));
|
||||
|
@ -282,7 +217,7 @@ describe('ngc command-line', () => {
|
|||
export class MyModule {}
|
||||
`);
|
||||
|
||||
const exitCode = main(['-p', basePath]);
|
||||
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngfactory.js'))).toBe(true);
|
||||
|
@ -307,7 +242,7 @@ describe('ngc command-line', () => {
|
|||
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(fs.existsSync(path.resolve(outDir, 'mymodule.ngfactory.js'))).toBe(true);
|
||||
expect(fs.existsSync(path.resolve(
|
||||
|
@ -332,8 +267,7 @@ describe('ngc command-line', () => {
|
|||
export class MyModule {}
|
||||
`);
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
const exitCode = main(['-p', basePath], mockConsole.error);
|
||||
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
const mymodulejs = path.resolve(outDir, 'mymodule.js');
|
||||
|
@ -362,8 +296,7 @@ describe('ngc command-line', () => {
|
|||
export class MyModule {}
|
||||
`);
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
const exitCode = main(['-p', basePath], mockConsole.error);
|
||||
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
const mymodulejs = path.resolve(outDir, 'mymodule.js');
|
||||
|
@ -392,8 +325,7 @@ describe('ngc command-line', () => {
|
|||
export class MyModule {}
|
||||
`);
|
||||
|
||||
const mockConsole = {error: (s: string) => {}};
|
||||
const exitCode = main(['-p', basePath], mockConsole.error);
|
||||
const exitCode = mainSync(['-p', basePath], errorSpy);
|
||||
expect(exitCode).toEqual(0);
|
||||
|
||||
const mymodulejs = path.resolve(outDir, 'mymodule.js');
|
||||
|
@ -411,9 +343,9 @@ describe('ngc command-line', () => {
|
|||
});
|
||||
|
||||
function compile(): number {
|
||||
const errors: string[] = [];
|
||||
const result = main(['-p', path.join(basePath, 'tsconfig.json')], s => errors.push(s));
|
||||
expect(errors).toEqual([]);
|
||||
errorSpy.calls.reset();
|
||||
const result = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
|
||||
expect(errorSpy).not.toHaveBeenCalled();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -607,68 +539,12 @@ describe('ngc command-line', () => {
|
|||
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);
|
||||
shouldExist('index.js');
|
||||
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', () => {
|
||||
const writeGenConfig = (skipCodegen: boolean) => {
|
||||
writeConfig(`{
|
||||
|
@ -756,7 +632,7 @@ describe('ngc command-line', () => {
|
|||
it('should honor skip code generation', () => {
|
||||
// First ensure that we skip code generation when requested;.
|
||||
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);
|
||||
modules.forEach(moduleName => {
|
||||
shouldExist(moduleName + '.js');
|
||||
|
@ -772,7 +648,7 @@ describe('ngc command-line', () => {
|
|||
it('should produce factories', () => {
|
||||
// First ensure that we skip code generation when requested;.
|
||||
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);
|
||||
modules.forEach(moduleName => {
|
||||
shouldExist(moduleName + '.js');
|
||||
|
@ -823,7 +699,7 @@ describe('ngc command-line', () => {
|
|||
});
|
||||
|
||||
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', () => {
|
||||
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.ngsummary.json');
|
||||
shouldExist('lib1/module.ngsummary.js');
|
||||
|
@ -907,8 +783,8 @@ describe('ngc command-line', () => {
|
|||
});
|
||||
|
||||
it('should be able to compile library 2', () => {
|
||||
expect(main(['-p', path.join(basePath, 'lib1')])).toBe(0);
|
||||
expect(main(['-p', path.join(basePath, 'lib2')])).toBe(0);
|
||||
expect(mainSync(['-p', path.join(basePath, 'lib1')], errorSpy)).toBe(0);
|
||||
expect(mainSync(['-p', path.join(basePath, 'lib2')], errorSpy)).toBe(0);
|
||||
shouldExist('lib2/module.js');
|
||||
shouldExist('lib2/module.ngsummary.json');
|
||||
shouldExist('lib2/module.ngsummary.js');
|
||||
|
@ -919,12 +795,12 @@ describe('ngc command-line', () => {
|
|||
|
||||
describe('building an application', () => {
|
||||
beforeEach(() => {
|
||||
expect(main(['-p', path.join(basePath, 'lib1')])).toBe(0);
|
||||
expect(main(['-p', path.join(basePath, 'lib2')])).toBe(0);
|
||||
expect(mainSync(['-p', path.join(basePath, 'lib1')], errorSpy)).toBe(0);
|
||||
expect(mainSync(['-p', path.join(basePath, 'lib2')], errorSpy)).toBe(0);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -32,7 +32,6 @@
|
|||
"files": [
|
||||
"index.ts",
|
||||
"src/main.ts",
|
||||
"src/ngc.ts",
|
||||
"src/extract_i18n.ts",
|
||||
"../../node_modules/@types/node/index.d.ts",
|
||||
"../../node_modules/@types/jasmine/index.d.ts",
|
||||
|
|
|
@ -120,51 +120,40 @@ export class MetadataWriterHost extends DelegatingHost {
|
|||
}
|
||||
}
|
||||
|
||||
export class SyntheticIndexHost extends DelegatingHost {
|
||||
private normalSyntheticIndexName: string;
|
||||
private indexContent: string;
|
||||
private indexMetadata: string;
|
||||
export function createSyntheticIndexHost<H extends ts.CompilerHost>(
|
||||
delegate: H, syntheticIndex: {name: string, content: string, metadata: string}): H {
|
||||
const normalSyntheticIndexName = normalize(syntheticIndex.name);
|
||||
const indexContent = syntheticIndex.content;
|
||||
const indexMetadata = syntheticIndex.metadata;
|
||||
|
||||
constructor(
|
||||
delegate: ts.CompilerHost,
|
||||
syntheticIndex: {name: string, content: string, metadata: string}) {
|
||||
super(delegate);
|
||||
this.normalSyntheticIndexName = normalize(syntheticIndex.name);
|
||||
this.indexContent = syntheticIndex.content;
|
||||
this.indexMetadata = syntheticIndex.metadata;
|
||||
}
|
||||
const newHost = Object.create(delegate);
|
||||
newHost.fileExists = (fileName: string): boolean => {
|
||||
return normalize(fileName) == normalSyntheticIndexName || delegate.fileExists(fileName);
|
||||
};
|
||||
|
||||
fileExists = (fileName: string):
|
||||
boolean => {
|
||||
return normalize(fileName) == this.normalSyntheticIndexName ||
|
||||
this.delegate.fileExists(fileName);
|
||||
}
|
||||
newHost.readFile = (fileName: string) => {
|
||||
return normalize(fileName) == normalSyntheticIndexName ? indexContent :
|
||||
delegate.readFile(fileName);
|
||||
};
|
||||
|
||||
readFile =
|
||||
(fileName: string) => {
|
||||
return normalize(fileName) == this.normalSyntheticIndexName ?
|
||||
this.indexContent :
|
||||
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);
|
||||
newHost.getSourceFile =
|
||||
(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) => {
|
||||
if (normalize(fileName) == normalSyntheticIndexName) {
|
||||
return ts.createSourceFile(fileName, indexContent, languageVersion, true);
|
||||
}
|
||||
return this.delegate.getSourceFile(fileName, languageVersion, onError);
|
||||
}
|
||||
return delegate.getSourceFile(fileName, languageVersion, onError);
|
||||
};
|
||||
|
||||
writeFile: ts.WriteFileCallback =
|
||||
(fileName: string, data: string, writeByteOrderMark: boolean,
|
||||
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
|
||||
this.delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
||||
if (fileName.match(DTS) && sourceFiles && sourceFiles.length == 1 &&
|
||||
normalize(sourceFiles[0].fileName) == this.normalSyntheticIndexName) {
|
||||
// If we are writing the synthetic index, write the metadata along side.
|
||||
const metadataName = fileName.replace(DTS, '.metadata.json');
|
||||
writeFileSync(metadataName, this.indexMetadata, {encoding: 'utf8'});
|
||||
}
|
||||
}
|
||||
newHost.writeFile =
|
||||
(fileName: string, data: string, writeByteOrderMark: boolean,
|
||||
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
|
||||
delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
||||
if (fileName.match(DTS) && sourceFiles && sourceFiles.length == 1 &&
|
||||
normalize(sourceFiles[0].fileName) == normalSyntheticIndexName) {
|
||||
// If we are writing the synthetic index, write the metadata along side.
|
||||
const metadataName = fileName.replace(DTS, '.metadata.json');
|
||||
writeFileSync(metadataName, indexMetadata, {encoding: 'utf8'});
|
||||
}
|
||||
};
|
||||
return newHost;
|
||||
}
|
|
@ -13,7 +13,7 @@ import * as ts from 'typescript';
|
|||
|
||||
import {CompilerHostAdapter, MetadataBundler} from './bundler';
|
||||
import {CliOptions} from './cli_options';
|
||||
import {MetadataWriterHost, SyntheticIndexHost} from './compiler_host';
|
||||
import {MetadataWriterHost, createSyntheticIndexHost} from './compiler_host';
|
||||
import {privateEntriesToIndex} from './index_writer';
|
||||
import NgOptions from './options';
|
||||
import {check, tsc} from './tsc';
|
||||
|
@ -33,9 +33,9 @@ export interface CodegenExtension {
|
|||
host: ts.CompilerHost): Promise<string[]>;
|
||||
}
|
||||
|
||||
export function createBundleIndexHost(
|
||||
export function createBundleIndexHost<H extends ts.CompilerHost>(
|
||||
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));
|
||||
if (files.length != 1) {
|
||||
return {
|
||||
|
@ -61,7 +61,7 @@ export function createBundleIndexHost(
|
|||
path.join(path.dirname(indexModule), ngOptions.flatModuleOutFile !.replace(JS_EXT, '.ts'));
|
||||
const libraryIndex = `./${path.basename(indexModule)}`;
|
||||
const content = privateEntriesToIndex(libraryIndex, metadataBundle.privates);
|
||||
host = new SyntheticIndexHost(host, {name, content, metadata});
|
||||
host = createSyntheticIndexHost(host, {name, content, metadata});
|
||||
return {host, indexName: name};
|
||||
}
|
||||
|
||||
|
|
|
@ -9,26 +9,25 @@
|
|||
// TODO(chuckj): Remove the requirement for a fake 'reflect` implementation from
|
||||
// the compiler
|
||||
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 path from 'path';
|
||||
// Note, the tsc_wrapped module comes from rules_typescript, not from @angular/tsc-wrapped
|
||||
import {parseTsconfig} from 'tsc_wrapped';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
function main(args: string[]) {
|
||||
const [{options, bazelOpts, files, config}] = parseTsconfig(args[1]);
|
||||
const ngOptions: {expectedOut: string[]} = (config as any).angularCompilerOptions;
|
||||
const project = args[1];
|
||||
const [{options: tsOptions, bazelOpts, files, config}] = parseTsconfig(project);
|
||||
const {basePath} = calcProjectFileAndBasePath(project);
|
||||
const ngOptions = createNgCompilerOptions(basePath, config, tsOptions);
|
||||
|
||||
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 result = performCompilation(basePath, files, options, ngOptions, undefined);
|
||||
|
||||
return result.errorCode;
|
||||
const {diagnostics} = performCompilation(files, ngOptions);
|
||||
if (diagnostics.length) {
|
||||
console.error(formatDiagnostics(ngOptions, diagnostics));
|
||||
}
|
||||
return diagnostics.some(d => d.category === ts.DiagnosticCategory.Error) ? 1 : 0;
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
|
|
Loading…
Reference in New Issue