Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/compiler-cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ export {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Sp
export * from './src/transformers/api';
export * from './src/transformers/entry_points';

export {main as ngc} from './src/ngc';
export {performCompilation} from './src/ngc';
export {performCompilation} 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';
175 changes: 2 additions & 173 deletions packages/compiler-cli/src/ngc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,185 +10,14 @@
import 'reflect-metadata';

import {isSyntaxError, syntaxError} from '@angular/compiler';
import {MetadataBundler, createBundleIndexHost} from '@angular/tsc-wrapped';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import * as api from './transformers/api';
import * as ng from './transformers/entry_points';

const TS_EXT = /\.ts$/;

type Diagnostics = ts.Diagnostic[] | api.Diagnostic[];

function isTsDiagnostics(diagnostics: any): diagnostics is ts.Diagnostic[] {
return diagnostics && diagnostics[0] && (diagnostics[0].file || diagnostics[0].messageText);
}

function formatDiagnostics(cwd: string, 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];
if (d.span) {
res +=
` at ${d.span.start.file.url}(${d.span.start.line + 1},${d.span.start.col + 1})`;
}
if (d.span && d.span.details) {
res += `: ${d.span.details}, ${d.message}\n`;
} else {
res += `: ${d.message}\n`;
}
return res;
})
.join();
}
} else
return '';
}

function check(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(''));
}
}

function syntheticError(message: string): ts.Diagnostic {
return {
file: null as any as ts.SourceFile,
start: 0,
length: 0,
messageText: message,
category: ts.DiagnosticCategory.Error,
code: 0
};
}

export function readConfiguration(
project: string, basePath: string, checkFunc: (cwd: string, ...args: any[]) => void = check,
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);

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);

checkFunc(basePath, parsed.errors);

// 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};
}

function getProjectDirectory(project: string): string {
let isFile: boolean;
try {
isFile = fs.lstatSync(project).isFile();
} catch (e) {
// Project doesn't exist. Assume it is a file has an extension. This case happens
// when the project file is passed to set basePath but no tsconfig.json file exists.
// It is used in tests to ensure that the options can be passed in without there being
// an actual config file.
isFile = path.extname(project) !== '';
}

// If project refers to a file, the project directory is the file's parent directory
// otherwise project is the project directory.
return isFile ? path.dirname(project) : project;
}

export function performCompilation(
basePath: string, files: string[], options: ts.CompilerOptions, ngOptions: any,
consoleError: (s: string) => void = console.error,
checkFunc: (cwd: string, ...args: any[]) => void = check, tsCompilerHost?: ts.CompilerHost) {
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;
}

const ngHostOptions = {...options, ...ngOptions};
const ngHost = ng.createHost({tsHost: host, options: ngHostOptions});

const ngProgram =
ng.createProgram({rootNames: rootFileNames, host: ngHost, options: ngHostOptions});

// Check parameter diagnostics
checkFunc(basePath, ngProgram.getTsOptionDiagnostics(), ngProgram.getNgOptionDiagnostics());

// Check syntactic diagnostics
checkFunc(basePath, ngProgram.getTsSyntacticDiagnostics());

// Check TypeScript semantic and Angular structure diagnostics
checkFunc(
basePath, ngProgram.getTsSemanticDiagnostics(), ngProgram.getNgStructuralDiagnostics());

// Check Angular semantic diagnostics
checkFunc(basePath, ngProgram.getNgSemanticDiagnostics());

ngProgram.emit({
emitFlags: api.EmitFlags.Default |
((ngOptions.skipMetadataEmit || ngOptions.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata)
});
} catch (e) {
if (isSyntaxError(e)) {
console.error(e.message);
consoleError(e.message);
return 1;
}
throw e;
}

return 0;
}

import {performCompilation, readConfiguration, throwOnDiagnostics} from './perform-compile';

export function main(
args: string[], consoleError: (s: string) => void = console.error,
checkFunc: (cwd: string, ...args: any[]) => void = check): number {
checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics): number {
try {
const parsedArgs = require('minimist')(args);
const project = parsedArgs.p || parsedArgs.project || '.';
Expand Down
192 changes: 192 additions & 0 deletions packages/compiler-cli/src/perform-compile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/**
* @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 {isSyntaxError, syntaxError} from '@angular/compiler';
import {MetadataBundler, createBundleIndexHost} from '@angular/tsc-wrapped';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import * as api from './transformers/api';
import * as ng from './transformers/entry_points';

const TS_EXT = /\.ts$/;

export type Diagnostics = ts.Diagnostic[] | api.Diagnostic[];

function isTsDiagnostics(diagnostics: any): diagnostics is ts.Diagnostic[] {
return diagnostics && diagnostics[0] && (diagnostics[0].file || diagnostics[0].messageText);
}

function formatDiagnostics(cwd: string, 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];
if (d.span) {
res +=
` at ${d.span.start.file.url}(${d.span.start.line + 1},${d.span.start.col + 1})`;
}
if (d.span && d.span.details) {
res += `: ${d.span.details}, ${d.message}\n`;
} else {
res += `: ${d.message}\n`;
}
return res;
})
.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(''));
}
}

function syntheticError(message: string): ts.Diagnostic {
return {
file: null as any as ts.SourceFile,
start: 0,
length: 0,
messageText: message,
category: ts.DiagnosticCategory.Error,
code: 0
};
}

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);

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);

checkFunc(basePath, parsed.errors);

// 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};
}

function getProjectDirectory(project: string): string {
let isFile: boolean;
try {
isFile = fs.lstatSync(project).isFile();
} catch (e) {
// Project doesn't exist. Assume it is a file has an extension. This case happens
// when the project file is passed to set basePath but no tsconfig.json file exists.
// It is used in tests to ensure that the options can be passed in without there being
// an actual config file.
isFile = path.extname(project) !== '';
}

// If project refers to a file, the project directory is the file's parent directory
// otherwise project is the project directory.
return isFile ? path.dirname(project) : project;
}

export function performCompilation(
basePath: string, files: string[], options: ts.CompilerOptions, ngOptions: any,
consoleError: (s: string) => void = console.error,
checkFunc: (cwd: string, ...args: any[]) => void = throwOnDiagnostics,
tsCompilerHost?: ts.CompilerHost) {
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;
}

const ngHostOptions = {...options, ...ngOptions};
const ngHost = ng.createHost({tsHost: host, options: ngHostOptions});

const ngProgram =
ng.createProgram({rootNames: rootFileNames, host: ngHost, options: ngHostOptions});

// Check parameter diagnostics
checkFunc(basePath, ngProgram.getTsOptionDiagnostics(), ngProgram.getNgOptionDiagnostics());

// Check syntactic diagnostics
checkFunc(basePath, ngProgram.getTsSyntacticDiagnostics());

// Check TypeScript semantic and Angular structure diagnostics
checkFunc(
basePath, ngProgram.getTsSemanticDiagnostics(), ngProgram.getNgStructuralDiagnostics());

// Check Angular semantic diagnostics
checkFunc(basePath, ngProgram.getNgSemanticDiagnostics());

ngProgram.emit({
emitFlags: api.EmitFlags.Default |
((ngOptions.skipMetadataEmit || ngOptions.flatModuleOutFile) ? 0 : api.EmitFlags.Metadata)
});
} catch (e) {
if (isSyntaxError(e)) {
console.error(e.message);
consoleError(e.message);
return 1;
}
throw e;
}

return 0;
}
Loading