feat(ivy): support separate .js and .d.ts trees when generating imports (#26403)
The `NgModule` handler generates `R3References` for its declarations, imports, exports, and bootstrap components, based on the relative import path between the module and the classes it's referring to. This works fine for compilation of a .ts Program inside ngtsc, but in ngcc the import needed in the .d.ts file may be very different to the import needed between .js files (for example, if the .js files are flattened and the .d.ts is not). This commit introduces a new API in the `ReflectionHost` for extracting the .d.ts version of a declaration, and makes use of it in the `NgModuleDecorationHandler` to write a correct expression for the `NgModule` definition type. PR Close #26403
This commit is contained in:

committed by
Kara Erickson

parent
eb5d3088a4
commit
1918f8d5b5
@ -6,12 +6,12 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ConstantPool, Expression, LiteralArrayExpr, R3DirectiveMetadata, R3InjectorMetadata, R3NgModuleMetadata, Statement, WrappedNodeExpr, compileInjector, compileNgModule, makeBindingParser, parseTemplate} from '@angular/compiler';
|
||||
import {Expression, LiteralArrayExpr, R3InjectorMetadata, R3NgModuleMetadata, R3Reference, Statement, WrappedNodeExpr, compileInjector, compileNgModule} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {Decorator, ReflectionHost} from '../../host';
|
||||
import {Reference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
||||
import {Reference, ResolvedReference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from '../../metadata';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
|
||||
|
||||
import {generateSetClassMetadataCall} from './metadata';
|
||||
@ -100,14 +100,21 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||
// the compile() phase, the module's metadata is available for selector scope computation.
|
||||
this.scopeRegistry.registerModule(node, {declarations, imports, exports});
|
||||
|
||||
const context = node.getSourceFile();
|
||||
const valueContext = node.getSourceFile();
|
||||
|
||||
let typeContext = valueContext;
|
||||
const typeNode = this.reflector.getDtsDeclarationOfClass(node);
|
||||
if (typeNode !== null) {
|
||||
typeContext = typeNode.getSourceFile();
|
||||
}
|
||||
|
||||
const ngModuleDef: R3NgModuleMetadata = {
|
||||
type: new WrappedNodeExpr(node.name !),
|
||||
bootstrap: bootstrap.map(bootstrap => toR3Reference(bootstrap, context)),
|
||||
declarations: declarations.map(decl => toR3Reference(decl, context)),
|
||||
exports: exports.map(exp => toR3Reference(exp, context)),
|
||||
imports: imports.map(imp => toR3Reference(imp, context)),
|
||||
bootstrap:
|
||||
bootstrap.map(bootstrap => this._toR3Reference(bootstrap, valueContext, typeContext)),
|
||||
declarations: declarations.map(decl => this._toR3Reference(decl, valueContext, typeContext)),
|
||||
exports: exports.map(exp => this._toR3Reference(exp, valueContext, typeContext)),
|
||||
imports: imports.map(imp => this._toR3Reference(imp, valueContext, typeContext)),
|
||||
emitInline: false,
|
||||
};
|
||||
|
||||
@ -163,6 +170,21 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||
];
|
||||
}
|
||||
|
||||
private _toR3Reference(
|
||||
valueRef: Reference<ts.Declaration>, valueContext: ts.SourceFile,
|
||||
typeContext: ts.SourceFile): R3Reference {
|
||||
if (!(valueRef instanceof ResolvedReference)) {
|
||||
return toR3Reference(valueRef, valueRef, valueContext, valueContext);
|
||||
} else {
|
||||
let typeRef = valueRef;
|
||||
let typeNode = this.reflector.getDtsDeclarationOfClass(typeRef.node);
|
||||
if (typeNode !== null) {
|
||||
typeRef = new ResolvedReference(typeNode, typeNode.name !);
|
||||
}
|
||||
return toR3Reference(valueRef, typeRef, valueContext, typeContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a `FunctionDeclaration` or `MethodDeclaration`, check if it is typed as a
|
||||
* `ModuleWithProviders` and return an expression referencing the module if available.
|
||||
|
@ -14,7 +14,7 @@ import {AbsoluteReference, Reference, ResolvedReference, reflectTypeEntityToDecl
|
||||
import {reflectIdentifierOfDeclaration, reflectNameOfDeclaration} from '../../metadata/src/reflector';
|
||||
import {TypeCheckableDirectiveMeta} from '../../typecheck';
|
||||
|
||||
import {extractDirectiveGuards, toR3Reference} from './util';
|
||||
import {extractDirectiveGuards} from './util';
|
||||
|
||||
|
||||
/**
|
||||
@ -423,7 +423,11 @@ function convertDirectiveReferenceMap(
|
||||
context: ts.SourceFile): Map<string, ScopeDirective<Expression>> {
|
||||
const newMap = new Map<string, ScopeDirective<Expression>>();
|
||||
map.forEach((meta, selector) => {
|
||||
newMap.set(selector, {...meta, directive: toR3Reference(meta.directive, context).value});
|
||||
const directive = meta.directive.toExpression(context);
|
||||
if (directive === null) {
|
||||
throw new Error(`Could not write expression to reference ${meta.directive.node}`);
|
||||
}
|
||||
newMap.set(selector, {...meta, directive});
|
||||
});
|
||||
return newMap;
|
||||
}
|
||||
@ -431,7 +435,13 @@ function convertDirectiveReferenceMap(
|
||||
function convertPipeReferenceMap(
|
||||
map: Map<string, Reference>, context: ts.SourceFile): Map<string, Expression> {
|
||||
const newMap = new Map<string, Expression>();
|
||||
map.forEach((meta, selector) => { newMap.set(selector, toR3Reference(meta, context).value); });
|
||||
map.forEach((meta, selector) => {
|
||||
const pipe = meta.toExpression(context);
|
||||
if (pipe === null) {
|
||||
throw new Error(`Could not write expression to reference ${meta.node}`);
|
||||
}
|
||||
newMap.set(selector, pipe);
|
||||
});
|
||||
return newMap;
|
||||
}
|
||||
|
||||
|
@ -70,11 +70,13 @@ export function getConstructorDependencies(
|
||||
return useType;
|
||||
}
|
||||
|
||||
export function toR3Reference(ref: Reference, context: ts.SourceFile): R3Reference {
|
||||
const value = ref.toExpression(context, ImportMode.UseExistingImport);
|
||||
const type = ref.toExpression(context, ImportMode.ForceNewImport);
|
||||
export function toR3Reference(
|
||||
valueRef: Reference, typeRef: Reference, valueContext: ts.SourceFile,
|
||||
typeContext: ts.SourceFile): R3Reference {
|
||||
const value = valueRef.toExpression(valueContext, ImportMode.UseExistingImport);
|
||||
const type = typeRef.toExpression(typeContext, ImportMode.ForceNewImport);
|
||||
if (value === null || type === null) {
|
||||
throw new Error(`Could not refer to ${ts.SyntaxKind[ref.node.kind]}`);
|
||||
throw new Error(`Could not refer to ${ts.SyntaxKind[valueRef.node.kind]}`);
|
||||
}
|
||||
return {value, type};
|
||||
}
|
||||
|
@ -436,4 +436,18 @@ export interface ReflectionHost {
|
||||
* if the value cannot be computed.
|
||||
*/
|
||||
getVariableValue(declaration: ts.VariableDeclaration): ts.Expression|null;
|
||||
|
||||
/**
|
||||
* Take an exported declaration of a class (maybe downleveled to a variable) and look up the
|
||||
* declaration of its type in a separate .d.ts tree.
|
||||
*
|
||||
* This function is allowed to return `null` if the current compilation unit does not have a
|
||||
* separate .d.ts tree. When compiling TypeScript code this is always the case, since .d.ts files
|
||||
* are produced only during the emit of such a compilation. When compiling .js code, however,
|
||||
* there is frequently a parallel .d.ts tree which this method exposes.
|
||||
*
|
||||
* Note that the `ts.ClassDeclaration` returned from this function may not be from the same
|
||||
* `ts.Program` as the input declaration.
|
||||
*/
|
||||
getDtsDeclarationOfClass(declaration: ts.Declaration): ts.ClassDeclaration|null;
|
||||
}
|
||||
|
@ -170,6 +170,8 @@ export class TypeScriptReflectionHost implements ReflectionHost {
|
||||
return declaration.initializer || null;
|
||||
}
|
||||
|
||||
getDtsDeclarationOfClass(_: ts.Declaration): ts.ClassDeclaration|null { return null; }
|
||||
|
||||
/**
|
||||
* Resolve a `ts.Symbol` to its declaration, keeping track of the `viaModule` along the way.
|
||||
*
|
||||
@ -312,7 +314,7 @@ export function reflectTypeEntityToDeclaration(
|
||||
type: ts.EntityName, checker: ts.TypeChecker): {node: ts.Declaration, from: string | null} {
|
||||
let realSymbol = checker.getSymbolAtLocation(type);
|
||||
if (realSymbol === undefined) {
|
||||
throw new Error(`Cannot resolve type entity to symbol`);
|
||||
throw new Error(`Cannot resolve type entity ${type.getText()} to symbol`);
|
||||
}
|
||||
while (realSymbol.flags & ts.SymbolFlags.Alias) {
|
||||
realSymbol = checker.getAliasedSymbol(realSymbol);
|
||||
|
Reference in New Issue
Block a user