fix(ivy): disable use of aliasing in template type-checking (#34649)

FileToModuleHost aliasing supports compilation within environments that have
two properties:

1. A `FileToModuleHost` exists which defines canonical module names for any
   given TS file.
2. Dependency restrictions exist which prevent the import of arbitrary files
   even if such files are within the .d.ts transitive closure of a
   compilation ("strictdeps").

In such an environment, generated imports can only go through import paths
which are already present in the user program. The aliasing system supports
the generation and consumption of such imports at runtime.

`FileToModuleHost` aliasing does not emit re-exports in .d.ts files. This
means that it's safe to rely on alias re-exports in generated .js code (they
are guaranteed to exist at runtime) but not in template type-checking code
(since TS will not be able to follow such imports). Therefore, non-aliased
imports should be used in template type-checking code.

This commit adds a `NoAliasing` flag to `ImportFlags` and sets it when
generating imports in template type-checking code. The testing environment
is also patched to support resolution of FileToModuleHost canonical paths
within the template type-checking program, enabling testing of this change.

PR Close #34649
This commit is contained in:
Alex Rickabaugh
2019-12-19 16:33:56 -08:00
committed by Andrew Kushnir
parent 5b9c96b9b8
commit cb11380515
6 changed files with 109 additions and 3 deletions

View File

@ -209,6 +209,10 @@ export class PrivateExportAliasingHost implements AliasingHost {
*/
export class AliasStrategy implements ReferenceEmitStrategy {
emit(ref: Reference<ts.Node>, context: ts.SourceFile, importMode: ImportFlags): Expression|null {
if (importMode & ImportFlags.NoAliasing) {
return null;
}
return ref.alias;
}
}

View File

@ -42,6 +42,14 @@ export enum ImportFlags {
* This is sometimes required if there's a risk TypeScript might remove imports during emit.
*/
ForceNewImport = 0x01,
/**
* Don't make use of any aliasing information when emitting a reference.
*
* This is sometimes required if emitting into a context where generated references will be fed
* into TypeScript and type-checked (such as in template type-checking).
*/
NoAliasing = 0x02,
}
/**

View File

@ -9,7 +9,7 @@
import {ExpressionType, ExternalExpr, Type, WrappedNodeExpr} from '@angular/compiler';
import * as ts from 'typescript';
import {NOOP_DEFAULT_IMPORT_RECORDER, Reference, ReferenceEmitter} from '../../imports';
import {ImportFlags, NOOP_DEFAULT_IMPORT_RECORDER, Reference, ReferenceEmitter} from '../../imports';
import {ClassDeclaration, ReflectionHost} from '../../reflection';
import {ImportManager, translateExpression, translateType} from '../../translator';
@ -205,7 +205,11 @@ export class Environment {
* This may involve importing the node into the file if it's not declared there already.
*/
reference(ref: Reference<ClassDeclaration<ts.ClassDeclaration>>): ts.Expression {
const ngExpr = this.refEmitter.emit(ref, this.contextFile);
// Disable aliasing for imports generated in a template type-checking context, as there is no
// guarantee that any alias re-exports exist in the .d.ts files. It's safe to use direct imports
// in these cases as there is no strict dependency checking during the template type-checking
// pass.
const ngExpr = this.refEmitter.emit(ref, this.contextFile, ImportFlags.NoAliasing);
// Use `translateExpression` to convert the `Expression` into a `ts.Expression`.
return translateExpression(
@ -218,7 +222,7 @@ export class Environment {
* This may involve importing the node into the file if it's not declared there already.
*/
referenceType(ref: Reference): ts.TypeNode {
const ngExpr = this.refEmitter.emit(ref, this.contextFile);
const ngExpr = this.refEmitter.emit(ref, this.contextFile, ImportFlags.NoAliasing);
// Create an `ExpressionType` from the `Expression` and translate it via `translateType`.
// TODO(alxhub): support references to types with generic arguments in a clean way.

View File

@ -19,12 +19,18 @@ export class TypeCheckProgramHost implements ts.CompilerHost {
*/
private sfMap: Map<string, ts.SourceFile>;
readonly resolveModuleNames?: ts.CompilerHost['resolveModuleNames'];
constructor(sfMap: Map<string, ts.SourceFile>, private delegate: ts.CompilerHost) {
this.sfMap = sfMap;
if (delegate.getDirectories !== undefined) {
this.getDirectories = (path: string) => delegate.getDirectories !(path);
}
if (delegate.resolveModuleNames !== undefined) {
this.resolveModuleNames = delegate.resolveModuleNames;
}
}
getSourceFile(