feature(tsc-wrapped): add option for closure compiler JSDoc annotations
This commit is contained in:
parent
c1a62e2154
commit
664a6273e1
|
@ -22,6 +22,7 @@
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
],
|
],
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": true,
|
||||||
"strictMetadataEmit": true
|
"strictMetadataEmit": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,5 +22,8 @@
|
||||||
"files": [
|
"files": [
|
||||||
"index.ts",
|
"index.ts",
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
"../../system.d.ts"
|
"../../system.d.ts"
|
||||||
],
|
],
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": true,
|
||||||
"strictMetadataEmit": true
|
"strictMetadataEmit": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
],
|
],
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": true,
|
||||||
"strictMetadataEmit": true
|
"strictMetadataEmit": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
],
|
],
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": true,
|
||||||
"strictMetadataEmit": true
|
"strictMetadataEmit": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
],
|
],
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": true,
|
||||||
"strictMetadataEmit": true
|
"strictMetadataEmit": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
],
|
],
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": true,
|
||||||
"strictMetadataEmit": true
|
"strictMetadataEmit": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
],
|
],
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": true,
|
||||||
"strictMetadataEmit": true
|
"strictMetadataEmit": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
"index.ts"
|
"index.ts"
|
||||||
],
|
],
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": true,
|
||||||
"strictMetadataEmit": true
|
"strictMetadataEmit": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
"../../../node_modules/zone.js/dist/zone.js.d.ts"
|
||||||
],
|
],
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": true,
|
||||||
"strictMetadataEmit": true
|
"strictMetadataEmit": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -77,7 +77,7 @@
|
||||||
"source-map-support": "^0.4.2",
|
"source-map-support": "^0.4.2",
|
||||||
"systemjs": "0.18.10",
|
"systemjs": "0.18.10",
|
||||||
"ts-api-guardian": "0.1.4",
|
"ts-api-guardian": "0.1.4",
|
||||||
"tsickle": "^0.1.7",
|
"tsickle": "^0.2.1",
|
||||||
"tslint": "^3.15.1",
|
"tslint": "^3.15.1",
|
||||||
"typescript": "^2.0.2",
|
"typescript": "^2.0.2",
|
||||||
"universal-analytics": "^0.3.9",
|
"universal-analytics": "^0.3.9",
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export {MetadataWriterHost, TsickleHost} from './src/compiler_host';
|
export {DecoratorDownlevelCompilerHost, MetadataWriterHost} from './src/compiler_host';
|
||||||
export {CodegenExtension, main} from './src/main';
|
export {CodegenExtension, main} from './src/main';
|
||||||
|
|
||||||
export {default as AngularCompilerOptions} from './src/options';
|
export {default as AngularCompilerOptions} from './src/options';
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {"type":"git","url":"https://github.com/angular/angular.git"},
|
"repository": {"type":"git","url":"https://github.com/angular/angular.git"},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tsickle": "^0.1.7"
|
"tsickle": "^0.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^2.0.2"
|
"typescript": "^2.0.2"
|
||||||
|
|
|
@ -7,12 +7,20 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {writeFileSync} from 'fs';
|
import {writeFileSync} from 'fs';
|
||||||
import {convertDecorators} from 'tsickle';
|
import * as tsickle from 'tsickle';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import NgOptions from './options';
|
import NgOptions from './options';
|
||||||
import {MetadataCollector} from './collector';
|
import {MetadataCollector} from './collector';
|
||||||
|
|
||||||
|
export function formatDiagnostics(d: ts.Diagnostic[]): string {
|
||||||
|
const host: ts.FormatDiagnosticsHost = {
|
||||||
|
getCurrentDirectory: () => ts.sys.getCurrentDirectory(),
|
||||||
|
getNewLine: () => ts.sys.newLine,
|
||||||
|
getCanonicalFileName: (f: string) => f
|
||||||
|
};
|
||||||
|
return ts.formatDiagnostics(d, host);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of CompilerHost that forwards all methods to another instance.
|
* Implementation of CompilerHost that forwards all methods to another instance.
|
||||||
|
@ -41,15 +49,16 @@ export abstract class DelegatingHost implements ts.CompilerHost {
|
||||||
directoryExists = (directoryName: string) => this.delegate.directoryExists(directoryName);
|
directoryExists = (directoryName: string) => this.delegate.directoryExists(directoryName);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TsickleHost extends DelegatingHost {
|
export class DecoratorDownlevelCompilerHost extends DelegatingHost {
|
||||||
// Additional diagnostics gathered by pre- and post-emit transformations.
|
private ANNOTATION_SUPPORT = `
|
||||||
public diagnostics: ts.Diagnostic[] = [];
|
|
||||||
private TSICKLE_SUPPORT = `
|
|
||||||
interface DecoratorInvocation {
|
interface DecoratorInvocation {
|
||||||
type: Function;
|
type: Function;
|
||||||
args?: any[];
|
args?: any[];
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
/** Error messages produced by tsickle, if any. */
|
||||||
|
public diagnostics: ts.Diagnostic[] = [];
|
||||||
|
|
||||||
constructor(delegate: ts.CompilerHost, private program: ts.Program) { super(delegate); }
|
constructor(delegate: ts.CompilerHost, private program: ts.Program) { super(delegate); }
|
||||||
|
|
||||||
getSourceFile =
|
getSourceFile =
|
||||||
|
@ -58,12 +67,12 @@ interface DecoratorInvocation {
|
||||||
let newContent = originalContent;
|
let newContent = originalContent;
|
||||||
if (!/\.d\.ts$/.test(fileName)) {
|
if (!/\.d\.ts$/.test(fileName)) {
|
||||||
try {
|
try {
|
||||||
const converted = convertDecorators(
|
const converted = tsickle.convertDecorators(
|
||||||
this.program.getTypeChecker(), this.program.getSourceFile(fileName));
|
this.program.getTypeChecker(), this.program.getSourceFile(fileName));
|
||||||
if (converted.diagnostics) {
|
if (converted.diagnostics) {
|
||||||
this.diagnostics.push(...converted.diagnostics);
|
this.diagnostics.push(...converted.diagnostics);
|
||||||
}
|
}
|
||||||
newContent = converted.output + this.TSICKLE_SUPPORT;
|
newContent = converted.output + this.ANNOTATION_SUPPORT;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Cannot convertDecorators on file', fileName);
|
console.error('Cannot convertDecorators on file', fileName);
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -73,14 +82,35 @@ interface DecoratorInvocation {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TsickleCompilerHost extends DelegatingHost {
|
||||||
|
/** Error messages produced by tsickle, if any. */
|
||||||
|
public diagnostics: ts.Diagnostic[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
delegate: ts.CompilerHost, private oldProgram: ts.Program, private options: NgOptions) {
|
||||||
|
super(delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSourceFile =
|
||||||
|
(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) => {
|
||||||
|
let sourceFile = this.oldProgram.getSourceFile(fileName);
|
||||||
|
let isDefinitions = /\.d\.ts$/.test(fileName);
|
||||||
|
// Don't tsickle-process any d.ts that isn't a compilation target;
|
||||||
|
// this means we don't process e.g. lib.d.ts.
|
||||||
|
if (isDefinitions) return sourceFile;
|
||||||
|
|
||||||
|
let {output, externs, diagnostics} =
|
||||||
|
tsickle.annotate(this.oldProgram, sourceFile, {untyped: true});
|
||||||
|
this.diagnostics = diagnostics;
|
||||||
|
return ts.createSourceFile(fileName, output, languageVersion, true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const IGNORED_FILES = /\.ngfactory\.js$|\.css\.js$|\.css\.shim\.js$/;
|
const IGNORED_FILES = /\.ngfactory\.js$|\.css\.js$|\.css\.shim\.js$/;
|
||||||
|
|
||||||
export class MetadataWriterHost extends DelegatingHost {
|
export class MetadataWriterHost extends DelegatingHost {
|
||||||
private metadataCollector = new MetadataCollector();
|
private metadataCollector = new MetadataCollector();
|
||||||
constructor(
|
constructor(delegate: ts.CompilerHost, private ngOptions: NgOptions) { super(delegate); }
|
||||||
delegate: ts.CompilerHost, private program: ts.Program, private ngOptions: NgOptions) {
|
|
||||||
super(delegate);
|
|
||||||
}
|
|
||||||
|
|
||||||
private writeMetadata(emitFilePath: string, sourceFile: ts.SourceFile) {
|
private writeMetadata(emitFilePath: string, sourceFile: ts.SourceFile) {
|
||||||
// TODO: replace with DTS filePath when https://github.com/Microsoft/TypeScript/pull/8412 is
|
// TODO: replace with DTS filePath when https://github.com/Microsoft/TypeScript/pull/8412 is
|
||||||
|
|
|
@ -13,7 +13,7 @@ import * as ts from 'typescript';
|
||||||
import {check, tsc} from './tsc';
|
import {check, tsc} from './tsc';
|
||||||
|
|
||||||
import NgOptions from './options';
|
import NgOptions from './options';
|
||||||
import {MetadataWriterHost, TsickleHost} from './compiler_host';
|
import {MetadataWriterHost, DecoratorDownlevelCompilerHost, TsickleCompilerHost} from './compiler_host';
|
||||||
import {CliOptions} from './cli_options';
|
import {CliOptions} from './cli_options';
|
||||||
|
|
||||||
export type CodegenExtension =
|
export type CodegenExtension =
|
||||||
|
@ -34,6 +34,10 @@ export function main(
|
||||||
// read the configuration options from wherever you store them
|
// read the configuration options from wherever you store them
|
||||||
const {parsed, ngOptions} = tsc.readConfiguration(project, basePath);
|
const {parsed, ngOptions} = tsc.readConfiguration(project, basePath);
|
||||||
ngOptions.basePath = basePath;
|
ngOptions.basePath = basePath;
|
||||||
|
const createProgram = (host: ts.CompilerHost, oldProgram?: ts.Program) =>
|
||||||
|
ts.createProgram(parsed.fileNames, parsed.options, host, oldProgram);
|
||||||
|
const diagnostics = (parsed.options as any).diagnostics;
|
||||||
|
if (diagnostics) (ts as any).performance.enable();
|
||||||
|
|
||||||
const host = ts.createCompilerHost(parsed.options, true);
|
const host = ts.createCompilerHost(parsed.options, true);
|
||||||
|
|
||||||
|
@ -42,30 +46,60 @@ export function main(
|
||||||
// todo(misko): remove once facade symlinks are removed
|
// todo(misko): remove once facade symlinks are removed
|
||||||
host.realpath = (path) => path;
|
host.realpath = (path) => path;
|
||||||
|
|
||||||
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
|
const program = createProgram(host);
|
||||||
const errors = program.getOptionsDiagnostics();
|
const errors = program.getOptionsDiagnostics();
|
||||||
check(errors);
|
check(errors);
|
||||||
|
|
||||||
if (ngOptions.skipTemplateCodegen || !codegen) {
|
if (ngOptions.skipTemplateCodegen || !codegen) {
|
||||||
codegen = () => Promise.resolve(null);
|
codegen = () => Promise.resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (diagnostics) console.time('NG codegen');
|
||||||
return codegen(ngOptions, cliOptions, program, host).then(() => {
|
return codegen(ngOptions, cliOptions, program, host).then(() => {
|
||||||
// Create a new program since codegen files were created after making the old program
|
if (diagnostics) console.timeEnd('NG codegen');
|
||||||
const newProgram = ts.createProgram(parsed.fileNames, parsed.options, host, program);
|
let definitionsHost = host;
|
||||||
tsc.typeCheck(host, newProgram);
|
|
||||||
|
|
||||||
// Emit *.js with Decorators lowered to Annotations, and also *.js.map
|
|
||||||
const tsicklePreProcessor = new TsickleHost(host, newProgram);
|
|
||||||
tsc.emit(tsicklePreProcessor, newProgram);
|
|
||||||
|
|
||||||
if (!ngOptions.skipMetadataEmit) {
|
if (!ngOptions.skipMetadataEmit) {
|
||||||
// Emit *.metadata.json and *.d.ts
|
definitionsHost = new MetadataWriterHost(host, ngOptions);
|
||||||
// Not in the same emit pass with above, because tsickle erases
|
}
|
||||||
// decorators which we want to read or document.
|
// Create a new program since codegen files were created after making the old program
|
||||||
// Do this emit second since TypeScript will create missing directories for us
|
let programWithCodegen = createProgram(definitionsHost, program);
|
||||||
// in the standard emit.
|
tsc.typeCheck(host, programWithCodegen);
|
||||||
const metadataWriter = new MetadataWriterHost(host, newProgram, ngOptions);
|
|
||||||
tsc.emit(metadataWriter, newProgram);
|
let preprocessHost = host;
|
||||||
|
let programForJsEmit = programWithCodegen;
|
||||||
|
|
||||||
|
if (ngOptions.annotationsAs !== 'decorators') {
|
||||||
|
if (diagnostics) console.time('NG downlevel');
|
||||||
|
const downlevelHost = new DecoratorDownlevelCompilerHost(preprocessHost, programForJsEmit);
|
||||||
|
// A program can be re-used only once; save the programWithCodegen to be reused by
|
||||||
|
// metadataWriter
|
||||||
|
programForJsEmit = createProgram(downlevelHost);
|
||||||
|
check(downlevelHost.diagnostics);
|
||||||
|
preprocessHost = downlevelHost;
|
||||||
|
if (diagnostics) console.timeEnd('NG downlevel');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ngOptions.annotateForClosureCompiler) {
|
||||||
|
if (diagnostics) console.time('NG JSDoc');
|
||||||
|
const tsickleHost = new TsickleCompilerHost(preprocessHost, programForJsEmit, ngOptions);
|
||||||
|
programForJsEmit = createProgram(tsickleHost);
|
||||||
|
check(tsickleHost.diagnostics);
|
||||||
|
if (diagnostics) console.timeEnd('NG JSDoc');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit *.js and *.js.map
|
||||||
|
tsc.emit(programForJsEmit);
|
||||||
|
|
||||||
|
// Emit *.d.ts and maybe *.metadata.json
|
||||||
|
// Not in the same emit pass with above, because tsickle erases
|
||||||
|
// decorators which we want to read or document.
|
||||||
|
// Do this emit second since TypeScript will create missing directories for us
|
||||||
|
// in the standard emit.
|
||||||
|
tsc.emit(programWithCodegen);
|
||||||
|
|
||||||
|
if (diagnostics) {
|
||||||
|
(ts as any).performance.forEachMeasure(
|
||||||
|
(name: string, duration: number) => { console.error(`TS ${name}: ${duration}ms`); });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -9,31 +9,43 @@
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
interface Options extends ts.CompilerOptions {
|
interface Options extends ts.CompilerOptions {
|
||||||
// Absolute path to a directory where generated file structure is written
|
// Absolute path to a directory where generated file structure is written.
|
||||||
genDir: string;
|
// If unspecified, generated files will be written alongside sources.
|
||||||
|
genDir?: string;
|
||||||
|
|
||||||
// Path to the directory containing the tsconfig.json file.
|
// Path to the directory containing the tsconfig.json file.
|
||||||
basePath: string;
|
basePath?: string;
|
||||||
|
|
||||||
// Don't produce .metadata.json files (they don't work for bundled emit with --out)
|
// Don't produce .metadata.json files (they don't work for bundled emit with --out)
|
||||||
skipMetadataEmit: boolean;
|
skipMetadataEmit?: boolean;
|
||||||
|
|
||||||
// Produce an error if the metadata written for a class would produce an error if used.
|
// Produce an error if the metadata written for a class would produce an error if used.
|
||||||
strictMetadataEmit: boolean;
|
strictMetadataEmit?: boolean;
|
||||||
|
|
||||||
// Don't produce .ngfactory.ts or .css.shim.ts files
|
// Don't produce .ngfactory.ts or .css.shim.ts files
|
||||||
skipTemplateCodegen: boolean;
|
skipTemplateCodegen?: boolean;
|
||||||
|
|
||||||
// Whether to generate code for library code.
|
// Whether to generate code for library code.
|
||||||
// If true, produce .ngfactory.ts and .css.shim.ts files for .d.ts inputs.
|
// If true, produce .ngfactory.ts and .css.shim.ts files for .d.ts inputs.
|
||||||
// Default is true.
|
// Default is true.
|
||||||
generateCodeForLibraries?: boolean;
|
generateCodeForLibraries?: boolean;
|
||||||
|
|
||||||
|
// Insert JSDoc type annotations needed by Closure Compiler
|
||||||
|
annotateForClosureCompiler?: boolean;
|
||||||
|
|
||||||
// Modify how angular annotations are emitted to improve tree-shaking.
|
// Modify how angular annotations are emitted to improve tree-shaking.
|
||||||
annotationsAs?: string; /* 'decorator'|'static field' */
|
// Default is static fields.
|
||||||
|
// decorators: Leave the Decorators in-place. This makes compilation faster.
|
||||||
|
// TypeScript will emit calls to the __decorate helper.
|
||||||
|
// `--emitDecoratorMetadata` can be used for runtime reflection.
|
||||||
|
// However, the resulting code will not properly tree-shake.
|
||||||
|
// static fields: Replace decorators with a static field in the class.
|
||||||
|
// Allows advanced tree-shakers like Closure Compiler to remove
|
||||||
|
// unused classes.
|
||||||
|
annotationsAs?: 'decorators'|'static fields';
|
||||||
|
|
||||||
// Print extra information while running the compiler
|
// Print extra information while running the compiler
|
||||||
trace: boolean;
|
trace?: boolean;
|
||||||
|
|
||||||
// Whether to embed debug information in the compiled templates
|
// Whether to embed debug information in the compiled templates
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
|
|
|
@ -11,7 +11,6 @@ import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import AngularCompilerOptions from './options';
|
import AngularCompilerOptions from './options';
|
||||||
import {TsickleHost} from './compiler_host';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Our interface to the TypeScript standard compiler.
|
* Our interface to the TypeScript standard compiler.
|
||||||
|
@ -22,7 +21,7 @@ export interface CompilerInterface {
|
||||||
readConfiguration(project: string, basePath: string):
|
readConfiguration(project: string, basePath: string):
|
||||||
{parsed: ts.ParsedCommandLine, ngOptions: AngularCompilerOptions};
|
{parsed: ts.ParsedCommandLine, ngOptions: AngularCompilerOptions};
|
||||||
typeCheck(compilerHost: ts.CompilerHost, program: ts.Program): void;
|
typeCheck(compilerHost: ts.CompilerHost, program: ts.Program): void;
|
||||||
emit(compilerHost: ts.CompilerHost, program: ts.Program): number;
|
emit(program: ts.Program): number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEBUG = false;
|
const DEBUG = false;
|
||||||
|
@ -134,17 +133,11 @@ export class Tsc implements CompilerInterface {
|
||||||
check(diagnostics);
|
check(diagnostics);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(compilerHost: TsickleHost, oldProgram: ts.Program): number {
|
emit(program: ts.Program): number {
|
||||||
// Create a program if we are lowering annotations with tsickle.
|
|
||||||
const program = this.ngOptions.annotationsAs === 'static fields' ?
|
|
||||||
ts.createProgram(this.parsed.fileNames, this.parsed.options, compilerHost) :
|
|
||||||
oldProgram;
|
|
||||||
debug('Emitting outputs...');
|
debug('Emitting outputs...');
|
||||||
const emitResult = program.emit();
|
const emitResult = program.emit();
|
||||||
const diagnostics: ts.Diagnostic[] = [];
|
const diagnostics: ts.Diagnostic[] = [];
|
||||||
diagnostics.push(...emitResult.diagnostics);
|
diagnostics.push(...emitResult.diagnostics);
|
||||||
|
|
||||||
check(compilerHost.diagnostics);
|
|
||||||
return emitResult.emitSkipped ? 1 : 0;
|
return emitResult.emitSkipped ? 1 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/**
|
||||||
|
* @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 * as ts from 'typescript';
|
||||||
|
import NgOptions from '../src/options';
|
||||||
|
import {formatDiagnostics, TsickleCompilerHost} from '../src/compiler_host';
|
||||||
|
import {writeTempFile} from './test_support';
|
||||||
|
|
||||||
|
describe('Compiler Host', () => {
|
||||||
|
function makeProgram(fileName: string, source: string): [ts.Program, ts.CompilerHost, NgOptions] {
|
||||||
|
let fn = writeTempFile(fileName, source);
|
||||||
|
let opts: NgOptions = {
|
||||||
|
target: ts.ScriptTarget.ES5,
|
||||||
|
types: [],
|
||||||
|
genDir: '/tmp',
|
||||||
|
basePath: '/tmp',
|
||||||
|
noEmit: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TsickleCompilerHost wants a ts.Program, which is the result of
|
||||||
|
// parsing and typechecking the code before tsickle processing.
|
||||||
|
// So we must create and run the entire stack of CompilerHost.
|
||||||
|
let host = ts.createCompilerHost(opts);
|
||||||
|
let program = ts.createProgram([fn], opts, host);
|
||||||
|
// To get types resolved, you must first call getPreEmitDiagnostics.
|
||||||
|
let diags = formatDiagnostics(ts.getPreEmitDiagnostics(program));
|
||||||
|
expect(diags).toEqual('');
|
||||||
|
|
||||||
|
return [program, host, opts];
|
||||||
|
}
|
||||||
|
|
||||||
|
it('inserts JSDoc annotations', () => {
|
||||||
|
const [program, host, opts] = makeProgram('foo.ts', 'let x: number = 123');
|
||||||
|
const tsickleHost = new TsickleCompilerHost(host, program, opts);
|
||||||
|
const f = tsickleHost.getSourceFile(program.getRootFileNames()[0], ts.ScriptTarget.ES5);
|
||||||
|
expect(f.text).toContain('/** @type {?} */');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reports diagnostics about existing JSDoc', () => {
|
||||||
|
const [program, host, opts] =
|
||||||
|
makeProgram('error.ts', '/** @param {string} x*/ function f(x: string){};');
|
||||||
|
const tsickleHost = new TsickleCompilerHost(host, program, opts);
|
||||||
|
const f = tsickleHost.getSourceFile(program.getRootFileNames()[0], ts.ScriptTarget.ES5);
|
||||||
|
expect(formatDiagnostics(tsickleHost.diagnostics)).toContain('redundant with TypeScript types');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,209 @@
|
||||||
|
/**
|
||||||
|
* @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 * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
import {main} from '../src/main';
|
||||||
|
|
||||||
|
import {makeTempDir} from './test_support';
|
||||||
|
|
||||||
|
describe('tsc-wrapped', () => {
|
||||||
|
let basePath: string;
|
||||||
|
let write: (fileName: string, content: string) => void;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
basePath = makeTempDir();
|
||||||
|
write = (fileName: string, content: string) => {
|
||||||
|
fs.writeFileSync(path.join(basePath, fileName), content, {encoding: 'utf-8'});
|
||||||
|
};
|
||||||
|
write('decorators.ts', '/** @Annotation */ export var Component: Function;');
|
||||||
|
write('dep.ts', `
|
||||||
|
export const A = 1;
|
||||||
|
export const B = 2;
|
||||||
|
`);
|
||||||
|
write('test.ts', `
|
||||||
|
import {Component} from './decorators';
|
||||||
|
export * from './dep';
|
||||||
|
|
||||||
|
@Component({})
|
||||||
|
export class Comp {
|
||||||
|
/**
|
||||||
|
* Comment that is
|
||||||
|
* multiple lines
|
||||||
|
*/
|
||||||
|
method(x: string): void {}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
function readOut(ext: string) {
|
||||||
|
return fs.readFileSync(path.join(basePath, 'built', `test.${ext}`), {encoding: 'utf-8'});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should report error if project not found', () => {
|
||||||
|
main('not-exist', null as any)
|
||||||
|
.then(() => fail('should report error'))
|
||||||
|
.catch(e => expect(e.message).toContain('ENOENT'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pre-process sources', (done) => {
|
||||||
|
write('tsconfig.json', `{
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"types": [],
|
||||||
|
"outDir": "built",
|
||||||
|
"declaration": true,
|
||||||
|
"module": "es2015"
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": true
|
||||||
|
},
|
||||||
|
"files": ["test.ts"]
|
||||||
|
}`);
|
||||||
|
|
||||||
|
main(basePath, {basePath})
|
||||||
|
.then(() => {
|
||||||
|
const out = readOut('js');
|
||||||
|
// No helpers since decorators were lowered
|
||||||
|
expect(out).not.toContain('__decorate');
|
||||||
|
// Expand `export *`
|
||||||
|
expect(out).toContain('export { A, B }');
|
||||||
|
// Annotated for Closure compiler
|
||||||
|
expect(out).toContain('* @param {?} x');
|
||||||
|
// Comments should stay multi-line
|
||||||
|
expect(out).not.toContain('Comment that is multiple lines');
|
||||||
|
// Decorator is now an annotation
|
||||||
|
expect(out).toMatch(/Comp.decorators = \[\s+\{ type: Component/);
|
||||||
|
const decl = readOut('d.ts');
|
||||||
|
expect(decl).toContain('declare class Comp');
|
||||||
|
const metadata = readOut('metadata.json');
|
||||||
|
expect(metadata).toContain('"Comp":{"__symbolic":"class"');
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(e => done.fail(e));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow all options disabled', (done) => {
|
||||||
|
write('tsconfig.json', `{
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"types": [],
|
||||||
|
"outDir": "built",
|
||||||
|
"declaration": false,
|
||||||
|
"module": "es2015"
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": false,
|
||||||
|
"annotationsAs": "decorators",
|
||||||
|
"skipMetadataEmit": true,
|
||||||
|
"skipTemplateCodegen": true
|
||||||
|
},
|
||||||
|
"files": ["test.ts"]
|
||||||
|
}`);
|
||||||
|
|
||||||
|
main(basePath, {basePath})
|
||||||
|
.then(() => {
|
||||||
|
const out = readOut('js');
|
||||||
|
// TypeScript's decorator emit
|
||||||
|
expect(out).toContain('__decorate');
|
||||||
|
// Not annotated for Closure compiler
|
||||||
|
expect(out).not.toContain('* @param {?} x');
|
||||||
|
expect(() => fs.accessSync(path.join(basePath, 'built', 'test.metadata.json'))).toThrow();
|
||||||
|
expect(() => fs.accessSync(path.join(basePath, 'built', 'test.d.ts'))).toThrow();
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(e => done.fail(e));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow JSDoc annotations without decorator downleveling', (done) => {
|
||||||
|
write('tsconfig.json', `{
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"types": [],
|
||||||
|
"outDir": "built",
|
||||||
|
"declaration": true
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": true,
|
||||||
|
"annotationsAs": "decorators"
|
||||||
|
},
|
||||||
|
"files": ["test.ts"]
|
||||||
|
}`);
|
||||||
|
main(basePath, {basePath}).then(() => done()).catch(e => done.fail(e));
|
||||||
|
});
|
||||||
|
|
||||||
|
xit('should run quickly (performance baseline)', (done) => {
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
|
write(`input${i}.ts`, `
|
||||||
|
import {Component} from './decorators';
|
||||||
|
@Component({})
|
||||||
|
export class Input${i} {
|
||||||
|
private __brand: string;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
write('tsconfig.json', `{
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"types": [],
|
||||||
|
"outDir": "built",
|
||||||
|
"declaration": true,
|
||||||
|
"diagnostics": true
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": false,
|
||||||
|
"annotationsAs": "decorators",
|
||||||
|
"skipMetadataEmit": true
|
||||||
|
},
|
||||||
|
"include": ["input*.ts"]
|
||||||
|
}`);
|
||||||
|
console.time('BASELINE');
|
||||||
|
|
||||||
|
main(basePath, {basePath})
|
||||||
|
.then(() => {
|
||||||
|
console.timeEnd('BASELINE');
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(e => done.fail(e));
|
||||||
|
});
|
||||||
|
|
||||||
|
xit('should run quickly (performance test)', (done) => {
|
||||||
|
for (let i = 0; i < 1000; i++) {
|
||||||
|
write(`input${i}.ts`, `
|
||||||
|
import {Component} from './decorators';
|
||||||
|
@Component({})
|
||||||
|
export class Input${i} {
|
||||||
|
private __brand: string;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
write('tsconfig.json', `{
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"types": [],
|
||||||
|
"outDir": "built",
|
||||||
|
"declaration": true,
|
||||||
|
"diagnostics": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"annotateForClosureCompiler": true
|
||||||
|
},
|
||||||
|
"include": ["input*.ts"]
|
||||||
|
}`);
|
||||||
|
console.time('TSICKLE');
|
||||||
|
|
||||||
|
main(basePath, {basePath})
|
||||||
|
.then(() => {
|
||||||
|
console.timeEnd('TSICKLE');
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(e => done.fail(e));
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
* @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 * as fs from 'fs';
|
||||||
|
import * as os from 'os';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
const tmpdir = process.env.TEST_TMPDIR || os.tmpdir();
|
||||||
|
|
||||||
|
export function writeTempFile(name: string, contents: string): string {
|
||||||
|
// TEST_TMPDIR is set by bazel.
|
||||||
|
const id = (Math.random() * 1000000).toFixed(0);
|
||||||
|
const fn = path.join(tmpdir, `tmp.${id}.${name}`);
|
||||||
|
fs.writeFileSync(fn, contents);
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeTempDir(): string {
|
||||||
|
const id = (Math.random() * 1000000).toFixed(0);
|
||||||
|
const dir = path.join(tmpdir, `tmp.${id}`);
|
||||||
|
fs.mkdirSync(dir);
|
||||||
|
return dir;
|
||||||
|
}
|
Loading…
Reference in New Issue