2017-08-07 17:30:35 -04:00
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
|
|
|
|
2017-11-14 20:49:47 -05:00
|
|
|
import {Position, isSyntaxError, syntaxError} from '@angular/compiler';
|
2017-08-07 17:30:35 -04:00
|
|
|
import * as fs from 'fs';
|
|
|
|
import * as path from 'path';
|
|
|
|
import * as ts from 'typescript';
|
2017-08-02 14:20:07 -04:00
|
|
|
|
2017-08-07 17:30:35 -04:00
|
|
|
import * as api from './transformers/api';
|
|
|
|
import * as ng from './transformers/entry_points';
|
2017-09-29 18:02:11 -04:00
|
|
|
import {createMessageDiagnostic} from './transformers/util';
|
2017-08-07 17:30:35 -04:00
|
|
|
|
|
|
|
const TS_EXT = /\.ts$/;
|
|
|
|
|
2017-12-22 12:36:47 -05:00
|
|
|
export type Diagnostics = ReadonlyArray<ts.Diagnostic|api.Diagnostic>;
|
2017-08-07 17:30:35 -04:00
|
|
|
|
2017-09-29 18:02:11 -04:00
|
|
|
export function filterErrorsAndWarnings(diagnostics: Diagnostics): Diagnostics {
|
|
|
|
return diagnostics.filter(d => d.category !== ts.DiagnosticCategory.Message);
|
|
|
|
}
|
|
|
|
|
2017-10-16 12:31:25 -04:00
|
|
|
const defaultFormatHost: ts.FormatDiagnosticsHost = {
|
|
|
|
getCurrentDirectory: () => ts.sys.getCurrentDirectory(),
|
|
|
|
getCanonicalFileName: fileName => fileName,
|
|
|
|
getNewLine: () => ts.sys.newLine
|
|
|
|
};
|
|
|
|
|
2017-11-14 20:49:47 -05:00
|
|
|
function displayFileName(fileName: string, host: ts.FormatDiagnosticsHost): string {
|
|
|
|
return path.relative(host.getCurrentDirectory(), host.getCanonicalFileName(fileName));
|
|
|
|
}
|
|
|
|
|
|
|
|
export function formatDiagnosticPosition(
|
|
|
|
position: Position, host: ts.FormatDiagnosticsHost = defaultFormatHost): string {
|
|
|
|
return `${displayFileName(position.fileName, host)}(${position.line + 1},${position.column+1})`;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function flattenDiagnosticMessageChain(
|
|
|
|
chain: api.DiagnosticMessageChain, host: ts.FormatDiagnosticsHost = defaultFormatHost): string {
|
|
|
|
let result = chain.messageText;
|
|
|
|
let indent = 1;
|
|
|
|
let current = chain.next;
|
|
|
|
const newLine = host.getNewLine();
|
|
|
|
while (current) {
|
|
|
|
result += newLine;
|
|
|
|
for (let i = 0; i < indent; i++) {
|
|
|
|
result += ' ';
|
|
|
|
}
|
|
|
|
result += current.messageText;
|
|
|
|
const position = current.position;
|
|
|
|
if (position) {
|
|
|
|
result += ` at ${formatDiagnosticPosition(position, host)}`;
|
|
|
|
}
|
|
|
|
current = current.next;
|
|
|
|
indent++;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function formatDiagnostic(
|
|
|
|
diagnostic: api.Diagnostic, host: ts.FormatDiagnosticsHost = defaultFormatHost) {
|
|
|
|
let result = '';
|
|
|
|
const newLine = host.getNewLine();
|
|
|
|
const span = diagnostic.span;
|
|
|
|
if (span) {
|
|
|
|
result += `${formatDiagnosticPosition({
|
|
|
|
fileName: span.start.file.url,
|
|
|
|
line: span.start.line,
|
|
|
|
column: span.start.col
|
|
|
|
}, host)}: `;
|
|
|
|
} else if (diagnostic.position) {
|
|
|
|
result += `${formatDiagnosticPosition(diagnostic.position, host)}: `;
|
|
|
|
}
|
|
|
|
if (diagnostic.span && diagnostic.span.details) {
|
|
|
|
result += `: ${diagnostic.span.details}, ${diagnostic.messageText}${newLine}`;
|
|
|
|
} else if (diagnostic.chain) {
|
|
|
|
result += `${flattenDiagnosticMessageChain(diagnostic.chain, host)}.${newLine}`;
|
|
|
|
} else {
|
|
|
|
result += `: ${diagnostic.messageText}${newLine}`;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-10-16 12:31:25 -04:00
|
|
|
export function formatDiagnostics(
|
2017-11-14 20:49:47 -05:00
|
|
|
diags: Diagnostics, host: ts.FormatDiagnosticsHost = defaultFormatHost): string {
|
2017-08-07 17:30:35 -04:00
|
|
|
if (diags && diags.length) {
|
2017-08-09 16:45:45 -04:00
|
|
|
return diags
|
2017-11-14 20:49:47 -05:00
|
|
|
.map(diagnostic => {
|
|
|
|
if (api.isTsDiagnostic(diagnostic)) {
|
|
|
|
return ts.formatDiagnostics([diagnostic], host);
|
2017-08-09 16:45:45 -04:00
|
|
|
} else {
|
2017-11-14 20:49:47 -05:00
|
|
|
return formatDiagnostic(diagnostic, host);
|
2017-08-09 16:45:45 -04:00
|
|
|
}
|
|
|
|
})
|
2017-09-26 14:25:57 -04:00
|
|
|
.join('');
|
2017-11-14 20:49:47 -05:00
|
|
|
} else {
|
2017-08-07 17:30:35 -04:00
|
|
|
return '';
|
2017-11-14 20:49:47 -05:00
|
|
|
}
|
2017-08-07 17:30:35 -04:00
|
|
|
}
|
|
|
|
|
2017-08-09 16:45:45 -04:00
|
|
|
export interface ParsedConfiguration {
|
2017-08-18 17:03:59 -04:00
|
|
|
project: string;
|
2017-08-09 16:45:45 -04:00
|
|
|
options: api.CompilerOptions;
|
|
|
|
rootNames: string[];
|
2017-09-12 18:53:17 -04:00
|
|
|
emitFlags: api.EmitFlags;
|
2017-08-09 16:45:45 -04:00
|
|
|
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};
|
2017-08-07 17:30:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
export function readConfiguration(
|
2017-08-09 16:45:45 -04:00
|
|
|
project: string, existingOptions?: ts.CompilerOptions): ParsedConfiguration {
|
|
|
|
try {
|
|
|
|
const {projectFile, basePath} = calcProjectFileAndBasePath(project);
|
|
|
|
|
2018-09-11 03:38:28 -04:00
|
|
|
const readExtendedConfigFile =
|
|
|
|
(configFile: string, existingConfig?: any): {config?: any, error?: ts.Diagnostic} => {
|
|
|
|
const {config, error} = ts.readConfigFile(configFile, ts.sys.readFile);
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
return {error};
|
|
|
|
}
|
|
|
|
|
|
|
|
// we are only interested into merging 'angularCompilerOptions' as
|
|
|
|
// other options like 'compilerOptions' are merged by TS
|
|
|
|
const baseConfig = existingConfig || config;
|
|
|
|
if (existingConfig) {
|
|
|
|
baseConfig.angularCompilerOptions = {...config.angularCompilerOptions,
|
|
|
|
...baseConfig.angularCompilerOptions};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config.extends) {
|
|
|
|
let extendedConfigPath = path.resolve(path.dirname(configFile), config.extends);
|
|
|
|
extendedConfigPath = path.extname(extendedConfigPath) ? extendedConfigPath :
|
|
|
|
`${extendedConfigPath}.json`;
|
|
|
|
|
|
|
|
if (fs.existsSync(extendedConfigPath)) {
|
|
|
|
// Call read config recursively as TypeScript only merges CompilerOptions
|
|
|
|
return readExtendedConfigFile(extendedConfigPath, baseConfig);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {config: baseConfig};
|
|
|
|
};
|
|
|
|
|
|
|
|
const {config, error} = readExtendedConfigFile(projectFile);
|
2017-08-09 16:45:45 -04:00
|
|
|
|
|
|
|
if (error) {
|
2017-09-12 18:53:17 -04:00
|
|
|
return {
|
|
|
|
project,
|
|
|
|
errors: [error],
|
|
|
|
rootNames: [],
|
|
|
|
options: {},
|
|
|
|
emitFlags: api.EmitFlags.Default
|
|
|
|
};
|
2017-08-09 16:45:45 -04:00
|
|
|
}
|
|
|
|
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));
|
|
|
|
|
|
|
|
const options = createNgCompilerOptions(basePath, config, parsed.options);
|
2017-09-12 18:53:17 -04:00
|
|
|
let emitFlags = api.EmitFlags.Default;
|
|
|
|
if (!(options.skipMetadataEmit || options.flatModuleOutFile)) {
|
|
|
|
emitFlags |= api.EmitFlags.Metadata;
|
|
|
|
}
|
2017-09-19 14:41:47 -04:00
|
|
|
if (options.skipTemplateCodegen) {
|
|
|
|
emitFlags = emitFlags & ~api.EmitFlags.Codegen;
|
|
|
|
}
|
2017-09-12 18:53:17 -04:00
|
|
|
return {project: projectFile, rootNames, options, errors: parsed.errors, emitFlags};
|
2017-08-09 16:45:45 -04:00
|
|
|
} catch (e) {
|
|
|
|
const errors: Diagnostics = [{
|
|
|
|
category: ts.DiagnosticCategory.Error,
|
2017-08-18 17:03:59 -04:00
|
|
|
messageText: e.stack,
|
|
|
|
source: api.SOURCE,
|
|
|
|
code: api.UNKNOWN_ERROR_CODE
|
2017-08-09 16:45:45 -04:00
|
|
|
}];
|
2017-09-12 18:53:17 -04:00
|
|
|
return {project: '', errors, rootNames: [], options: {}, emitFlags: api.EmitFlags.Default};
|
2017-08-09 16:45:45 -04:00
|
|
|
}
|
2017-08-07 17:30:35 -04:00
|
|
|
}
|
|
|
|
|
2017-08-18 17:03:59 -04:00
|
|
|
export interface PerformCompilationResult {
|
|
|
|
diagnostics: Diagnostics;
|
|
|
|
program?: api.Program;
|
|
|
|
emitResult?: ts.EmitResult;
|
|
|
|
}
|
|
|
|
|
2017-09-12 18:53:17 -04:00
|
|
|
export function exitCodeFromResult(diags: Diagnostics | undefined): number {
|
2017-09-29 18:02:11 -04:00
|
|
|
if (!diags || filterErrorsAndWarnings(diags).length === 0) {
|
2017-08-18 17:03:59 -04:00
|
|
|
// If we have a result and didn't get any errors, we succeeded.
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return 2 if any of the errors were unknown.
|
2017-09-12 18:53:17 -04:00
|
|
|
return diags.some(d => d.source === 'angular' && d.code === api.UNKNOWN_ERROR_CODE) ? 2 : 1;
|
2017-08-18 17:03:59 -04:00
|
|
|
}
|
|
|
|
|
2017-08-31 13:02:27 -04:00
|
|
|
export function performCompilation({rootNames, options, host, oldProgram, emitCallback,
|
2018-03-20 19:11:20 -04:00
|
|
|
mergeEmitResultsCallback,
|
2017-08-31 13:02:27 -04:00
|
|
|
gatherDiagnostics = defaultGatherDiagnostics,
|
2017-09-12 18:53:17 -04:00
|
|
|
customTransformers, emitFlags = api.EmitFlags.Default}: {
|
2017-08-31 13:02:27 -04:00
|
|
|
rootNames: string[],
|
|
|
|
options: api.CompilerOptions,
|
|
|
|
host?: api.CompilerHost,
|
|
|
|
oldProgram?: api.Program,
|
2017-10-12 19:09:49 -04:00
|
|
|
emitCallback?: api.TsEmitCallback,
|
2018-03-20 19:11:20 -04:00
|
|
|
mergeEmitResultsCallback?: api.TsMergeEmitResultsCallback,
|
2017-08-31 13:02:27 -04:00
|
|
|
gatherDiagnostics?: (program: api.Program) => Diagnostics,
|
2017-09-12 18:53:17 -04:00
|
|
|
customTransformers?: api.CustomTransformers,
|
|
|
|
emitFlags?: api.EmitFlags
|
2017-08-31 13:02:27 -04:00
|
|
|
}): PerformCompilationResult {
|
2017-08-09 16:45:45 -04:00
|
|
|
let program: api.Program|undefined;
|
2017-08-16 18:35:19 -04:00
|
|
|
let emitResult: ts.EmitResult|undefined;
|
2017-12-22 12:36:47 -05:00
|
|
|
let allDiagnostics: Array<ts.Diagnostic|api.Diagnostic> = [];
|
2017-08-09 16:45:45 -04:00
|
|
|
try {
|
|
|
|
if (!host) {
|
2017-08-14 14:04:55 -04:00
|
|
|
host = ng.createCompilerHost({options});
|
2017-08-09 16:45:45 -04:00
|
|
|
}
|
2017-08-07 17:30:35 -04:00
|
|
|
|
2017-08-09 16:45:45 -04:00
|
|
|
program = ng.createProgram({rootNames, host, options, oldProgram});
|
2017-08-07 17:30:35 -04:00
|
|
|
|
2017-09-29 18:02:11 -04:00
|
|
|
const beforeDiags = Date.now();
|
2017-08-31 13:02:27 -04:00
|
|
|
allDiagnostics.push(...gatherDiagnostics(program !));
|
2017-09-29 18:02:11 -04:00
|
|
|
if (options.diagnostics) {
|
|
|
|
const afterDiags = Date.now();
|
|
|
|
allDiagnostics.push(
|
|
|
|
createMessageDiagnostic(`Time for diagnostics: ${afterDiags - beforeDiags}ms.`));
|
|
|
|
}
|
2017-08-02 14:20:07 -04:00
|
|
|
|
2017-08-31 13:02:27 -04:00
|
|
|
if (!hasErrors(allDiagnostics)) {
|
2018-03-20 19:11:20 -04:00
|
|
|
emitResult =
|
|
|
|
program !.emit({emitCallback, mergeEmitResultsCallback, customTransformers, emitFlags});
|
2017-08-09 16:45:45 -04:00
|
|
|
allDiagnostics.push(...emitResult.diagnostics);
|
2017-08-18 17:03:59 -04:00
|
|
|
return {diagnostics: allDiagnostics, program, emitResult};
|
2017-08-09 16:45:45 -04:00
|
|
|
}
|
2017-08-18 17:03:59 -04:00
|
|
|
return {diagnostics: allDiagnostics, program};
|
2017-08-07 17:30:35 -04:00
|
|
|
} catch (e) {
|
2017-08-09 16:45:45 -04:00
|
|
|
let errMsg: string;
|
2017-08-18 17:03:59 -04:00
|
|
|
let code: number;
|
2017-08-07 17:30:35 -04:00
|
|
|
if (isSyntaxError(e)) {
|
2017-08-09 16:45:45 -04:00
|
|
|
// don't report the stack for syntax errors as they are well known errors.
|
|
|
|
errMsg = e.message;
|
2017-08-18 17:03:59 -04:00
|
|
|
code = api.DEFAULT_ERROR_CODE;
|
2017-08-09 16:45:45 -04:00
|
|
|
} else {
|
|
|
|
errMsg = e.stack;
|
2017-08-18 17:03:59 -04:00
|
|
|
// It is not a syntax error we might have a program with unknown state, discard it.
|
|
|
|
program = undefined;
|
|
|
|
code = api.UNKNOWN_ERROR_CODE;
|
2017-08-07 17:30:35 -04:00
|
|
|
}
|
2017-08-18 17:03:59 -04:00
|
|
|
allDiagnostics.push(
|
|
|
|
{category: ts.DiagnosticCategory.Error, messageText: errMsg, code, source: api.SOURCE});
|
|
|
|
return {diagnostics: allDiagnostics, program};
|
2017-08-07 17:30:35 -04:00
|
|
|
}
|
2017-08-31 13:02:27 -04:00
|
|
|
}
|
|
|
|
function defaultGatherDiagnostics(program: api.Program): Diagnostics {
|
2017-12-22 12:36:47 -05:00
|
|
|
const allDiagnostics: Array<ts.Diagnostic|api.Diagnostic> = [];
|
2017-08-31 13:02:27 -04:00
|
|
|
|
|
|
|
function checkDiagnostics(diags: Diagnostics | undefined) {
|
|
|
|
if (diags) {
|
|
|
|
allDiagnostics.push(...diags);
|
|
|
|
return !hasErrors(diags);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
let checkOtherDiagnostics = true;
|
|
|
|
// Check parameter diagnostics
|
|
|
|
checkOtherDiagnostics = checkOtherDiagnostics &&
|
|
|
|
checkDiagnostics([...program.getTsOptionDiagnostics(), ...program.getNgOptionDiagnostics()]);
|
|
|
|
|
|
|
|
// Check syntactic diagnostics
|
|
|
|
checkOtherDiagnostics =
|
2017-12-22 12:36:47 -05:00
|
|
|
checkOtherDiagnostics && checkDiagnostics(program.getTsSyntacticDiagnostics() as Diagnostics);
|
2017-08-31 13:02:27 -04:00
|
|
|
|
|
|
|
// Check TypeScript semantic and Angular structure diagnostics
|
|
|
|
checkOtherDiagnostics =
|
|
|
|
checkOtherDiagnostics &&
|
|
|
|
checkDiagnostics(
|
|
|
|
[...program.getTsSemanticDiagnostics(), ...program.getNgStructuralDiagnostics()]);
|
|
|
|
|
|
|
|
// Check Angular semantic diagnostics
|
|
|
|
checkOtherDiagnostics =
|
2017-12-22 12:36:47 -05:00
|
|
|
checkOtherDiagnostics && checkDiagnostics(program.getNgSemanticDiagnostics() as Diagnostics);
|
2017-08-31 13:02:27 -04:00
|
|
|
|
|
|
|
return allDiagnostics;
|
|
|
|
}
|
|
|
|
|
|
|
|
function hasErrors(diags: Diagnostics) {
|
|
|
|
return diags.some(d => d.category === ts.DiagnosticCategory.Error);
|
2017-09-12 18:53:17 -04:00
|
|
|
}
|