in packages/concatjs/internal/tsc_wrapped/tsc_wrapped.ts [295:491]
export async function createProgramAndEmit(
fileLoader: FileLoader, options: ts.CompilerOptions,
bazelOpts: BazelOptions, files: string[], disabledTsetseRules: string[]):
Promise<{program?: ts.Program, diagnostics: ts.Diagnostic[]}> {
// Beware! createProgramAndEmit must not print to console, nor exit etc.
// Handle errors by reporting and returning diagnostics.
perfTrace.snapshotMemoryUsage();
cache.resetStats();
cache.traceStats();
const compilerHostDelegate =
ts.createCompilerHost({target: ts.ScriptTarget.ES5});
const moduleResolver = bazelOpts.isJsTranspilation ?
makeJsModuleResolver(bazelOpts.workspaceName) :
ts.resolveModuleName;
// Files which should be allowed to be read, but aren't TypeScript code
const assets: string[] = [];
if (bazelOpts.angularCompilerOptions) {
if (bazelOpts.angularCompilerOptions.assets) {
assets.push(...bazelOpts.angularCompilerOptions.assets);
}
}
const tsickleCompilerHost = new CompilerHost(
[...files, ...assets], options, bazelOpts, compilerHostDelegate, fileLoader,
moduleResolver);
let compilerHost: PluginCompilerHost = tsickleCompilerHost;
const diagnosticPlugins: DiagnosticPlugin[] = [];
let angularPlugin: EmitPlugin&DiagnosticPlugin|undefined;
if (bazelOpts.angularCompilerOptions) {
try {
// Dynamically load the Angular emit plugin.
// Lazy load, so that code that does not use the plugin doesn't even
// have to spend the time to parse and load the plugin's source.
const NgEmitPluginCtor = await getAngularEmitPluginOrThrow();
const ngOptions = bazelOpts.angularCompilerOptions;
// Add the rootDir setting to the options passed to NgTscPlugin.
// Required so that synthetic files added to the rootFiles in the program
// can be given absolute paths, just as we do in tsconfig.ts, matching
// the behavior in TypeScript's tsconfig parsing logic.
ngOptions['rootDir'] = options.rootDir;
angularPlugin = new NgEmitPluginCtor(ngOptions);
} catch (e) {
return {
diagnostics: [errorDiag(
'when using `ts_library(use_angular_plugin=True)`, ' +
`you must install "@angular/compiler-cli". Error: ${e}`)]
};
}
diagnosticPlugins.push(angularPlugin);
// Wrap host so that Ivy compiler can add a file to it (has synthetic types for checking templates)
// TODO(arick): remove after ngsummary and ngfactory files eliminated
compilerHost = angularPlugin!.wrapHost!(compilerHost, files, options);
}
const oldProgram = cache.getProgram(bazelOpts.target);
const program = perfTrace.wrap(
'createProgram',
() => ts.createProgram(
compilerHost.inputFiles, options, compilerHost, oldProgram));
cache.putProgram(bazelOpts.target, program);
let transformers: ts.CustomTransformers = {
before: [],
after: [],
afterDeclarations: [],
};
let ignoreForDiagnostics = new Set<ts.SourceFile>();
if (angularPlugin) {
// The Angular plugin (via the `wrapHost` call above) inserts additional
// "shim" files into the `ts.Program`, beyond the user's .ts files. For
// proper operation, the plugin requires two modifications to the standard
// flow of TypeScript compilation, relating to which files are either
// type-checked or emitted.
//
// In tsc_wrapped, there is already a concept of which files should be
// emitted (which is calculated from the compilation inputs, as well as any
// paths that match expected Angular shims such as ngfactory files for those
// inputs). So the `ignoreForEmit` set produced by the plugin can be
// ignored here.
const angularSetup = angularPlugin.setupCompilation(program);
// Shims generated by the plugin do not benefit from normal type-checking,
// for a few reasons.
// 1) for emitted shims like ngfactory files, their proper contents are
// programmatically added via TypeScript transforms, so checking their
// initial contents is pointless and inefficient.
// 2) for non-emitted shims like the ngtypecheck files used in template
// type-checking, they are managed and checked internally via the plugin
// `getDiagnostics` method. Checking them as part of the normal
// diagnostics flow will at best produce spurious, duplicate errors that
// are not reported in the correct context, and at worst can produce
// incorrect errors.
//
// The `ignoreForDiagnostics` set informs tsc_wrapped which Angular shim
// files should be skipped when gathering diagnostics.
ignoreForDiagnostics = angularSetup.ignoreForDiagnostics;
transformers = angularPlugin.createTransformers();
}
for (const pluginConfig of options['plugins'] as ts.PluginImport[] || []) {
if (pluginConfig.name === 'ts-lit-plugin') {
const litTscPlugin =
// Lazy load, so that code that does not use the plugin doesn't even
// have to spend the time to parse and load the plugin's source.
//
// tslint:disable-next-line:no-require-imports
new (require('ts-lit-plugin/lib/bazel-plugin').Plugin)(
program, pluginConfig) as DiagnosticPlugin;
diagnosticPlugins.push(litTscPlugin);
}
}
if (!bazelOpts.isJsTranspilation) {
// If there are any TypeScript type errors abort now, so the error
// messages refer to the original source. After any subsequent passes
// (decorator downleveling or tsickle) we do not type check.
let diagnostics = gatherDiagnostics(
options, bazelOpts, program, disabledTsetseRules, diagnosticPlugins,
ignoreForDiagnostics);
if (!expectDiagnosticsWhitelist.length ||
expectDiagnosticsWhitelist.some(p => bazelOpts.target.startsWith(p))) {
diagnostics = bazelDiagnostics.filterExpected(
bazelOpts, diagnostics, bazelDiagnostics.uglyFormat);
} else if (bazelOpts.expectedDiagnostics.length > 0) {
diagnostics.push(errorDiag(
`Only targets under ${
expectDiagnosticsWhitelist.join(', ')} can use ` +
'expected_diagnostics, but got ' + bazelOpts.target));
}
// The Angular plugin creates a new program with template type-check information
// This consumes (destroys) the old program so it's not suitable for re-use anymore
// Ask Angular to give us the updated reusable program.
if (angularPlugin) {
cache.putProgram(bazelOpts.target, angularPlugin.getNextProgram!());
}
if (diagnostics.length > 0) {
debug('compilation failed at', new Error().stack!);
return {program, diagnostics};
}
}
// Angular might have added files like input.ngfactory.ts or input.ngsummary.ts
// and these need to be emitted.
// TODO(arick): remove after Ivy is enabled and ngsummary/ngfactory files no longer needed
function isAngularFile(sf: ts.SourceFile) {
if (!/\.ng(factory|summary)\.ts$/.test(sf.fileName)) {
return false;
}
const base = sf.fileName.slice(0, /*'.ngfactory|ngsummary.ts'.length*/ -13);
// It's possible a file was named ngsummary.ts or ngfactory.ts but *not* synthetic
// So verify that base.ts or base.tsx was originally part of the compilation
const tsCandidate = {fileName: `${base}.ts`} as ts.SourceFile;
const tsxCandidate = {fileName: `${base}.tsx`} as ts.SourceFile;
return isCompilationTarget(bazelOpts, tsCandidate) ||
isCompilationTarget(bazelOpts, tsxCandidate);
}
// If the Angular plugin is in use, this list of files to emit should exclude
// any files defined in the `ignoreForEmit` set returned by the plugin.
// However limiting the outputs to the set of compilation target files (plus
// any Angular shims defined by `isAngularFile`) already has that effect, so
// `ignoreForEmit` does not need to be factored in here.
const compilationTargets = program.getSourceFiles().filter(
sf => isCompilationTarget(bazelOpts, sf) || isAngularFile(sf));
let diagnostics: ts.Diagnostic[] = [];
let useTsickleEmit = bazelOpts.tsickle;
if (useTsickleEmit) {
diagnostics = emitWithTsickle(
program, tsickleCompilerHost, compilationTargets, options, bazelOpts,
transformers);
} else {
diagnostics = emitWithTypescript(program, compilationTargets, transformers);
}
if (diagnostics.length > 0) {
debug('compilation failed at', new Error().stack!);
}
cache.printStats();
return {program, diagnostics};
}