2016-10-31 14:25:53 -07:00

128 lines
4.3 KiB
TypeScript

/**
* @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
*/
import {existsSync} from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import AngularCompilerOptions from './options';
import {TsickleHost} from './compiler_host';
/**
* Our interface to the TypeScript standard compiler.
* If you write an Angular compiler plugin for another build tool,
* you should implement a similar interface.
*/
export interface CompilerInterface {
readConfiguration(project: string, basePath: string):
{parsed: ts.ParsedCommandLine, ngOptions: AngularCompilerOptions};
typeCheck(compilerHost: ts.CompilerHost, program: ts.Program): void;
emit(compilerHost: ts.CompilerHost, program: ts.Program): number;
}
const DEBUG = false;
function debug(msg: string, ...o: any[]) {
if (DEBUG) console.log(msg, ...o);
}
export function formatDiagnostics(diags: ts.Diagnostic[]): string {
return diags
.map((d) => {
let res = ts.DiagnosticCategory[d.category];
if (d.file) {
res += ' at ' + d.file.fileName + ':';
const {line, character} = d.file.getLineAndCharacterOfPosition(d.start);
res += (line + 1) + ':' + (character + 1) + ':';
}
res += ' ' + ts.flattenDiagnosticMessageText(d.messageText, '\n');
return res;
})
.join('\n');
}
export function check(diags: ts.Diagnostic[]) {
if (diags && diags.length && diags[0]) {
throw new Error(formatDiagnostics(diags));
}
}
export class Tsc implements CompilerInterface {
public ngOptions: AngularCompilerOptions;
public parsed: ts.ParsedCommandLine;
private basePath: string;
constructor(private readFile = ts.sys.readFile, private readDirectory = ts.sys.readDirectory) {}
readConfiguration(project: string, basePath: string) {
this.basePath = basePath;
// Allow a directory containing tsconfig.json as the project value
// Note, TS@next returns an empty array, while earlier versions throw
try {
if (this.readDirectory(project).length > 0) {
project = path.join(project, 'tsconfig.json');
}
} catch (e) {
// Was not a directory, continue on assuming it's a file
}
const {config, error} = ts.readConfigFile(project, this.readFile);
check([error]);
// Do not inline `host` into `parseJsonConfigFileContent` until after
// g3 is updated to the latest TypeScript.
// The issue is that old typescript only has `readDirectory` while
// the newer TypeScript has additional `useCaseSensitiveFileNames` and
// `fileExists`. Inlining will trigger an error of extra parameters.
let host = {
useCaseSensitiveFileNames: true,
fileExists: existsSync,
readDirectory: this.readDirectory
};
this.parsed = ts.parseJsonConfigFileContent(config, host, basePath);
check(this.parsed.errors);
// Default codegen goes to the current directory
// Parsed options are already converted to absolute paths
this.ngOptions = config.angularCompilerOptions || {};
this.ngOptions.genDir = path.join(basePath, this.ngOptions.genDir || '.');
for (const key of Object.keys(this.parsed.options)) {
this.ngOptions[key] = this.parsed.options[key];
}
return {parsed: this.parsed, ngOptions: this.ngOptions};
}
typeCheck(compilerHost: ts.CompilerHost, program: ts.Program): void {
debug('Checking global diagnostics...');
check(program.getGlobalDiagnostics());
let diagnostics: ts.Diagnostic[] = [];
debug('Type checking...');
for (let sf of program.getSourceFiles()) {
diagnostics.push(...ts.getPreEmitDiagnostics(program, sf));
}
check(diagnostics);
}
emit(compilerHost: TsickleHost, oldProgram: ts.Program): number {
// Create a new program since the host may be different from the old program.
const program = ts.createProgram(this.parsed.fileNames, this.parsed.options, compilerHost);
debug('Emitting outputs...');
const emitResult = program.emit();
let diagnostics: ts.Diagnostic[] = [];
diagnostics.push(...emitResult.diagnostics);
check(compilerHost.diagnostics);
return emitResult.emitSkipped ? 1 : 0;
}
}
export var tsc: CompilerInterface = new Tsc();