feature(tsc-wrapped): add option for closure compiler JSDoc annotations
This commit is contained in:

committed by
Chuck Jazdzewski

parent
c1a62e2154
commit
664a6273e1
@ -7,12 +7,20 @@
|
||||
*/
|
||||
|
||||
import {writeFileSync} from 'fs';
|
||||
import {convertDecorators} from 'tsickle';
|
||||
import * as tsickle from 'tsickle';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import NgOptions from './options';
|
||||
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.
|
||||
@ -41,15 +49,16 @@ export abstract class DelegatingHost implements ts.CompilerHost {
|
||||
directoryExists = (directoryName: string) => this.delegate.directoryExists(directoryName);
|
||||
}
|
||||
|
||||
export class TsickleHost extends DelegatingHost {
|
||||
// Additional diagnostics gathered by pre- and post-emit transformations.
|
||||
public diagnostics: ts.Diagnostic[] = [];
|
||||
private TSICKLE_SUPPORT = `
|
||||
export class DecoratorDownlevelCompilerHost extends DelegatingHost {
|
||||
private ANNOTATION_SUPPORT = `
|
||||
interface DecoratorInvocation {
|
||||
type: Function;
|
||||
args?: any[];
|
||||
}
|
||||
`;
|
||||
/** Error messages produced by tsickle, if any. */
|
||||
public diagnostics: ts.Diagnostic[] = [];
|
||||
|
||||
constructor(delegate: ts.CompilerHost, private program: ts.Program) { super(delegate); }
|
||||
|
||||
getSourceFile =
|
||||
@ -58,12 +67,12 @@ interface DecoratorInvocation {
|
||||
let newContent = originalContent;
|
||||
if (!/\.d\.ts$/.test(fileName)) {
|
||||
try {
|
||||
const converted = convertDecorators(
|
||||
const converted = tsickle.convertDecorators(
|
||||
this.program.getTypeChecker(), this.program.getSourceFile(fileName));
|
||||
if (converted.diagnostics) {
|
||||
this.diagnostics.push(...converted.diagnostics);
|
||||
}
|
||||
newContent = converted.output + this.TSICKLE_SUPPORT;
|
||||
newContent = converted.output + this.ANNOTATION_SUPPORT;
|
||||
} catch (e) {
|
||||
console.error('Cannot convertDecorators on file', fileName);
|
||||
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$/;
|
||||
|
||||
export class MetadataWriterHost extends DelegatingHost {
|
||||
private metadataCollector = new MetadataCollector();
|
||||
constructor(
|
||||
delegate: ts.CompilerHost, private program: ts.Program, private ngOptions: NgOptions) {
|
||||
super(delegate);
|
||||
}
|
||||
constructor(delegate: ts.CompilerHost, private ngOptions: NgOptions) { super(delegate); }
|
||||
|
||||
private writeMetadata(emitFilePath: string, sourceFile: ts.SourceFile) {
|
||||
// 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 NgOptions from './options';
|
||||
import {MetadataWriterHost, TsickleHost} from './compiler_host';
|
||||
import {MetadataWriterHost, DecoratorDownlevelCompilerHost, TsickleCompilerHost} from './compiler_host';
|
||||
import {CliOptions} from './cli_options';
|
||||
|
||||
export type CodegenExtension =
|
||||
@ -34,6 +34,10 @@ export function main(
|
||||
// read the configuration options from wherever you store them
|
||||
const {parsed, ngOptions} = tsc.readConfiguration(project, 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);
|
||||
|
||||
@ -42,30 +46,60 @@ export function main(
|
||||
// todo(misko): remove once facade symlinks are removed
|
||||
host.realpath = (path) => path;
|
||||
|
||||
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
|
||||
const program = createProgram(host);
|
||||
const errors = program.getOptionsDiagnostics();
|
||||
check(errors);
|
||||
|
||||
if (ngOptions.skipTemplateCodegen || !codegen) {
|
||||
codegen = () => Promise.resolve(null);
|
||||
}
|
||||
|
||||
if (diagnostics) console.time('NG codegen');
|
||||
return codegen(ngOptions, cliOptions, program, host).then(() => {
|
||||
// Create a new program since codegen files were created after making the old program
|
||||
const newProgram = ts.createProgram(parsed.fileNames, parsed.options, host, program);
|
||||
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 (diagnostics) console.timeEnd('NG codegen');
|
||||
let definitionsHost = host;
|
||||
if (!ngOptions.skipMetadataEmit) {
|
||||
// Emit *.metadata.json and *.d.ts
|
||||
// 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.
|
||||
const metadataWriter = new MetadataWriterHost(host, newProgram, ngOptions);
|
||||
tsc.emit(metadataWriter, newProgram);
|
||||
definitionsHost = new MetadataWriterHost(host, ngOptions);
|
||||
}
|
||||
// Create a new program since codegen files were created after making the old program
|
||||
let programWithCodegen = createProgram(definitionsHost, program);
|
||||
tsc.typeCheck(host, programWithCodegen);
|
||||
|
||||
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) {
|
||||
|
@ -9,31 +9,43 @@
|
||||
import * as ts from 'typescript';
|
||||
|
||||
interface Options extends ts.CompilerOptions {
|
||||
// Absolute path to a directory where generated file structure is written
|
||||
genDir: string;
|
||||
// Absolute path to a directory where generated file structure is written.
|
||||
// If unspecified, generated files will be written alongside sources.
|
||||
genDir?: string;
|
||||
|
||||
// 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)
|
||||
skipMetadataEmit: boolean;
|
||||
skipMetadataEmit?: boolean;
|
||||
|
||||
// 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
|
||||
skipTemplateCodegen: boolean;
|
||||
skipTemplateCodegen?: boolean;
|
||||
|
||||
// Whether to generate code for library code.
|
||||
// If true, produce .ngfactory.ts and .css.shim.ts files for .d.ts inputs.
|
||||
// Default is true.
|
||||
generateCodeForLibraries?: boolean;
|
||||
|
||||
// Insert JSDoc type annotations needed by Closure Compiler
|
||||
annotateForClosureCompiler?: boolean;
|
||||
|
||||
// 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
|
||||
trace: boolean;
|
||||
trace?: boolean;
|
||||
|
||||
// Whether to embed debug information in the compiled templates
|
||||
debug?: boolean;
|
||||
|
@ -11,7 +11,6 @@ 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.
|
||||
@ -22,7 +21,7 @@ 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;
|
||||
emit(program: ts.Program): number;
|
||||
}
|
||||
|
||||
const DEBUG = false;
|
||||
@ -134,17 +133,11 @@ export class Tsc implements CompilerInterface {
|
||||
check(diagnostics);
|
||||
}
|
||||
|
||||
emit(compilerHost: TsickleHost, oldProgram: 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;
|
||||
emit(program: ts.Program): number {
|
||||
debug('Emitting outputs...');
|
||||
const emitResult = program.emit();
|
||||
const diagnostics: ts.Diagnostic[] = [];
|
||||
diagnostics.push(...emitResult.diagnostics);
|
||||
|
||||
check(compilerHost.diagnostics);
|
||||
return emitResult.emitSkipped ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user