refactor(ivy): introduce the 'core' package and split apart NgtscProgram (#34887)

Previously, NgtscProgram lived in the main @angular/compiler-cli package
alongside the legacy View Engine compiler. As a result, the main package
depended on all of the ngtsc internal packages, and a significant portion of
ngtsc logic lived in NgtscProgram.

This commit refactors NgtscProgram and moves the main logic of compilation
into a new 'core' package. The new package defines a new API which enables
implementers of TypeScript compilers (compilers built using the TS API) to
support Angular transpilation as well. It involves a new NgCompiler type
which takes a ts.Program and performs Angular analysis and transformations,
as well as an NgCompilerHost which wraps an input ts.CompilerHost and adds
any extra Angular files.

Together, these two classes are used to implement a new NgtscProgram which
adapts the legacy api.Program interface used by the View Engine compiler
onto operations on the new types. The new NgtscProgram implementation is
significantly smaller and easier to reason about.

The new NgCompilerHost replaces the previous GeneratedShimsHostWrapper which
lived in the 'shims' package.

A new 'resource' package is added to support the HostResourceLoader which
previously lived in the outer compiler package.

As a result of the refactoring, the dependencies of the outer
@angular/compiler-cli package on ngtsc internal packages are significantly
trimmed.

This refactoring was driven by the desire to build a plugin interface to the
compiler so that tsc_wrapped (another consumer of the TS compiler APIs) can
perform Angular transpilation on user request.

PR Close #34887
This commit is contained in:
Alex Rickabaugh
2020-01-17 16:00:07 -08:00
committed by Andrew Kushnir
parent 31e9dda2c8
commit 24b2f1da2b
38 changed files with 1915 additions and 1300 deletions

View File

@ -8,8 +8,8 @@
/// <reference types="node" />
export {ShimGenerator} from './src/api';
export {FactoryGenerator, FactoryInfo, generatedFactoryTransform} from './src/factory_generator';
export {FactoryTracker} from './src/factory_tracker';
export {GeneratedShimsHostWrapper, ShimGenerator} from './src/host';
export {SummaryGenerator} from './src/summary_generator';
export {TypeCheckShimGenerator} from './src/typecheck_shim';

View File

@ -0,0 +1,27 @@
/**
* @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 {AbsoluteFsPath} from '../../file_system';
export interface ShimGenerator {
/**
* Returns `true` if this generator is intended to handle the given file.
*/
recognize(fileName: AbsoluteFsPath): boolean;
/**
* Generate a shim's `ts.SourceFile` for the given original file.
*
* `readFile` is a function which allows the generator to look up the contents of existing source
* files. It returns null if the requested file doesn't exist.
*
* If `generate` returns null, then the shim generator declines to generate the file after all.
*/
generate(genFileName: AbsoluteFsPath, readFile: (fileName: string) => ts.SourceFile | null):
ts.SourceFile|null;
}

View File

@ -11,7 +11,7 @@ import {AbsoluteFsPath, absoluteFrom, basename} from '../../file_system';
import {ImportRewriter} from '../../imports';
import {isNonDeclarationTsPath} from '../../util/src/typescript';
import {ShimGenerator} from './host';
import {ShimGenerator} from './api';
import {generatedModuleName} from './util';
const TS_DTS_SUFFIX = /(\.d)?\.ts$/;

View File

@ -1,129 +0,0 @@
/**
* @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 {AbsoluteFsPath, absoluteFrom, resolve} from '../../file_system';
export interface ShimGenerator {
/**
* Returns `true` if this generator is intended to handle the given file.
*/
recognize(fileName: AbsoluteFsPath): boolean;
/**
* Generate a shim's `ts.SourceFile` for the given original file.
*
* `readFile` is a function which allows the generator to look up the contents of existing source
* files. It returns null if the requested file doesn't exist.
*
* If `generate` returns null, then the shim generator declines to generate the file after all.
*/
generate(genFileName: AbsoluteFsPath, readFile: (fileName: string) => ts.SourceFile | null):
ts.SourceFile|null;
}
/**
* A wrapper around a `ts.CompilerHost` which supports generated files.
*/
export class GeneratedShimsHostWrapper implements ts.CompilerHost {
constructor(private delegate: ts.CompilerHost, private shimGenerators: ShimGenerator[]) {
if (delegate.resolveModuleNames !== undefined) {
this.resolveModuleNames =
(moduleNames: string[], containingFile: string, reusedNames: string[],
redirectedReference: ts.ResolvedProjectReference, options?: ts.CompilerOptions) =>
// FIXME: Additional parameters are required in TS3.6, but ignored in 3.5.
// Remove the any cast once google3 is fully on TS3.6.
(delegate.resolveModuleNames as any) !(
moduleNames, containingFile, reusedNames, redirectedReference, options);
}
if (delegate.resolveTypeReferenceDirectives) {
// Backward compatibility with TypeScript 2.9 and older since return
// type has changed from (ts.ResolvedTypeReferenceDirective | undefined)[]
// to ts.ResolvedTypeReferenceDirective[] in Typescript 3.0
type ts3ResolveTypeReferenceDirectives = (names: string[], containingFile: string) =>
ts.ResolvedTypeReferenceDirective[];
this.resolveTypeReferenceDirectives = (names: string[], containingFile: string) =>
(delegate.resolveTypeReferenceDirectives as ts3ResolveTypeReferenceDirectives) !(
names, containingFile);
}
if (delegate.directoryExists !== undefined) {
this.directoryExists = (directoryName: string) => delegate.directoryExists !(directoryName);
}
if (delegate.getDirectories !== undefined) {
this.getDirectories = (path: string) => delegate.getDirectories !(path);
}
}
// FIXME: Additional options param is needed in TS3.6, but not alloowed in 3.5.
// Make the options param non-optional once google3 is fully on TS3.6.
resolveModuleNames?:
(moduleNames: string[], containingFile: string, reusedNames: string[],
redirectedReference: ts.ResolvedProjectReference,
options?: ts.CompilerOptions) => (ts.ResolvedModule | undefined)[];
resolveTypeReferenceDirectives?:
(names: string[], containingFile: string) => ts.ResolvedTypeReferenceDirective[];
directoryExists?: (directoryName: string) => boolean;
getSourceFile(
fileName: string, languageVersion: ts.ScriptTarget,
onError?: ((message: string) => void)|undefined,
shouldCreateNewSourceFile?: boolean|undefined): ts.SourceFile|undefined {
for (let i = 0; i < this.shimGenerators.length; i++) {
const generator = this.shimGenerators[i];
// TypeScript internal paths are guaranteed to be POSIX-like absolute file paths.
const absoluteFsPath = resolve(fileName);
if (generator.recognize(absoluteFsPath)) {
const readFile = (originalFile: string) => {
return this.delegate.getSourceFile(
originalFile, languageVersion, onError, shouldCreateNewSourceFile) ||
null;
};
return generator.generate(absoluteFsPath, readFile) || undefined;
}
}
return this.delegate.getSourceFile(
fileName, languageVersion, onError, shouldCreateNewSourceFile);
}
getDefaultLibFileName(options: ts.CompilerOptions): string {
return this.delegate.getDefaultLibFileName(options);
}
writeFile(
fileName: string, data: string, writeByteOrderMark: boolean,
onError: ((message: string) => void)|undefined,
sourceFiles: ReadonlyArray<ts.SourceFile>|undefined): void {
return this.delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
}
getCurrentDirectory(): string { return this.delegate.getCurrentDirectory(); }
getDirectories?: (path: string) => string[];
getCanonicalFileName(fileName: string): string {
return this.delegate.getCanonicalFileName(fileName);
}
useCaseSensitiveFileNames(): boolean { return this.delegate.useCaseSensitiveFileNames(); }
getNewLine(): string { return this.delegate.getNewLine(); }
fileExists(fileName: string): boolean {
// Consider the file as existing whenever
// 1) it really does exist in the delegate host, or
// 2) at least one of the shim generators recognizes it
// Note that we can pass the file name as branded absolute fs path because TypeScript
// internally only passes POSIX-like paths.
return this.delegate.fileExists(fileName) ||
this.shimGenerators.some(gen => gen.recognize(absoluteFrom(fileName)));
}
readFile(fileName: string): string|undefined { return this.delegate.readFile(fileName); }
}

View File

@ -11,7 +11,7 @@ import * as ts from 'typescript';
import {AbsoluteFsPath, absoluteFrom} from '../../file_system';
import {isNonDeclarationTsPath} from '../../util/src/typescript';
import {ShimGenerator} from './host';
import {ShimGenerator} from './api';
import {generatedModuleName} from './util';
export class SummaryGenerator implements ShimGenerator {

View File

@ -10,7 +10,7 @@ import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../file_system';
import {ShimGenerator} from './host';
import {ShimGenerator} from './api';
/**
* A `ShimGenerator` which adds a type-checking file to the `ts.Program`.

View File

@ -1,37 +0,0 @@
/**
* @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 {GeneratedShimsHostWrapper} from '../src/host';
describe('shim host', () => {
it('should not have optional methods when delegate does not have them', function() {
const delegate = {} as unknown as ts.CompilerHost;
const shimsHost = new GeneratedShimsHostWrapper(delegate, []);
expect(shimsHost.resolveModuleNames).not.toBeDefined();
expect(shimsHost.resolveTypeReferenceDirectives).not.toBeDefined();
expect(shimsHost.directoryExists).not.toBeDefined();
expect(shimsHost.getDirectories).not.toBeDefined();
});
it('should delegate optional methods if available', function() {
const delegate = {
resolveModuleNames: () => undefined,
resolveTypeReferenceDirectives: () => undefined,
directoryExists: () => undefined,
getDirectories: () => undefined,
} as unknown as ts.CompilerHost;
const shimsHost = new GeneratedShimsHostWrapper(delegate, []);
expect(shimsHost.resolveModuleNames).toBeDefined();
expect(shimsHost.resolveTypeReferenceDirectives).toBeDefined();
expect(shimsHost.directoryExists).toBeDefined();
expect(shimsHost.getDirectories).toBeDefined();
});
});