Chuck Jazdzewski b6078f5887 fix(compiler): update to metadata version 3 (#13464)
This change retracts support for metadata version 2.

The collector used to produce version 2 metadata was incomplete
and can cause the AOT compiler to fail to resolve symbols or
produce other spurious errors.

All libraries compiled and published with 2.3.0 ngc will need
to be recompiled and updated with this change.
2016-12-14 15:28:51 -08:00

161 lines
6.4 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 {writeFileSync} from 'fs';
import * as tsickle from 'tsickle';
import * as ts from 'typescript';
import NgOptions from './options';
import {MetadataCollector} from './collector';
import {ModuleMetadata} from './schema';
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.
* Useful for partial implementations to override only methods they care about.
*/
export abstract class DelegatingHost implements ts.CompilerHost {
constructor(protected delegate: ts.CompilerHost) {}
getSourceFile =
(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) =>
this.delegate.getSourceFile(fileName, languageVersion, onError);
getCancellationToken = () => this.delegate.getCancellationToken();
getDefaultLibFileName = (options: ts.CompilerOptions) =>
this.delegate.getDefaultLibFileName(options);
getDefaultLibLocation = () => this.delegate.getDefaultLibLocation();
writeFile: ts.WriteFileCallback = this.delegate.writeFile;
getCurrentDirectory = () => this.delegate.getCurrentDirectory();
getDirectories = (path: string): string[] =>
(this.delegate as any).getDirectories?(this.delegate as any).getDirectories(path): [];
getCanonicalFileName = (fileName: string) => this.delegate.getCanonicalFileName(fileName);
useCaseSensitiveFileNames = () => this.delegate.useCaseSensitiveFileNames();
getNewLine = () => this.delegate.getNewLine();
fileExists = (fileName: string) => this.delegate.fileExists(fileName);
readFile = (fileName: string) => this.delegate.readFile(fileName);
trace = (s: string) => this.delegate.trace(s);
directoryExists = (directoryName: string) => this.delegate.directoryExists(directoryName);
}
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 =
(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) => {
const originalContent = this.delegate.readFile(fileName);
let newContent = originalContent;
if (!/\.d\.ts$/.test(fileName)) {
try {
const converted = tsickle.convertDecorators(
this.program.getTypeChecker(), this.program.getSourceFile(fileName));
if (converted.diagnostics) {
this.diagnostics.push(...converted.diagnostics);
}
newContent = converted.output + this.ANNOTATION_SUPPORT;
} catch (e) {
console.error('Cannot convertDecorators on file', fileName);
throw e;
}
}
return ts.createSourceFile(fileName, newContent, languageVersion, true);
};
}
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$|\.ngstyle\.js$/;
export class MetadataWriterHost extends DelegatingHost {
private metadataCollector = new MetadataCollector({quotedNames: true});
private metadataCollector1 = new MetadataCollector({version: 1});
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
// released
if (/*DTS*/ /\.js$/.test(emitFilePath)) {
const path = emitFilePath.replace(/*DTS*/ /\.js$/, '.metadata.json');
const metadata =
this.metadataCollector.getMetadata(sourceFile, !!this.ngOptions.strictMetadataEmit);
const metadata1 = this.metadataCollector1.getMetadata(sourceFile, false);
const metadatas: ModuleMetadata[] = [metadata, metadata1].filter(e => !!e);
if (metadatas.length) {
const metadataText = JSON.stringify(metadatas);
writeFileSync(path, metadataText, {encoding: 'utf-8'});
}
}
}
writeFile: ts.WriteFileCallback =
(fileName: string, data: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
if (/\.d\.ts$/.test(fileName)) {
// Let the original file be written first; this takes care of creating parent directories
this.delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
// TODO: remove this early return after https://github.com/Microsoft/TypeScript/pull/8412
// is
// released
return;
}
if (IGNORED_FILES.test(fileName)) {
return;
}
if (!sourceFiles) {
throw new Error(
'Metadata emit requires the sourceFiles are passed to WriteFileCallback. ' +
'Update to TypeScript ^1.9.0-dev');
}
if (sourceFiles.length > 1) {
throw new Error('Bundled emit with --out is not supported');
}
this.writeMetadata(fileName, sourceFiles[0]);
};
}