ngc can now validate metadata before emitting to verify it doesn't
contain an error symbol that will result in a runtime error if
it is used by the StaticReflector.
To enable this add the section,
"angularCompilerOptions": {
"strictMetadataEmit": true
}
to the top level of the tsconfig.json file passed to ngc.
Enabled metadata validation for packages that are intended to be
used statically.
117 lines
4.6 KiB
TypeScript
117 lines
4.6 KiB
TypeScript
import {writeFileSync} from 'fs';
|
|
import {convertDecorators} from 'tsickle';
|
|
import * as ts from 'typescript';
|
|
|
|
import NgOptions from './options';
|
|
import {MetadataCollector} from './collector';
|
|
|
|
|
|
/**
|
|
* 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();
|
|
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 TsickleHost extends DelegatingHost {
|
|
// Additional diagnostics gathered by pre- and post-emit transformations.
|
|
public diagnostics: ts.Diagnostic[] = [];
|
|
private TSICKLE_SUPPORT = `
|
|
interface DecoratorInvocation {
|
|
type: Function;
|
|
args?: any[];
|
|
}
|
|
`;
|
|
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 = convertDecorators(
|
|
this.program.getTypeChecker(), this.program.getSourceFile(fileName));
|
|
if (converted.diagnostics) {
|
|
this.diagnostics.push(...converted.diagnostics);
|
|
}
|
|
newContent = converted.output + this.TSICKLE_SUPPORT;
|
|
} catch (e) {
|
|
console.error('Cannot convertDecorators on file', fileName);
|
|
throw e;
|
|
}
|
|
}
|
|
return ts.createSourceFile(fileName, newContent, 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);
|
|
}
|
|
|
|
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);
|
|
if (metadata && metadata.metadata) {
|
|
const metadataText = JSON.stringify(metadata);
|
|
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]);
|
|
};
|
|
}
|