refactor(ivy): split apart the 'metadata' package in the ngtsc compiler (#27743)
This refactoring moves code around between a few of the ngtsc subpackages, with the goal of having a more logical package structure. Additional interfaces are also introduced where they make sense. The 'metadata' package formerly contained both the partial evaluator, the TypeScriptReflectionHost as well as some other reflection functions, and the Reference interface and various implementations. This package was split into 3 parts. The partial evaluator now has its own package 'partial_evaluator', and exists behind an interface PartialEvaluator instead of a top-level function. In the future this will be useful for reducing churn as the partial evaluator becomes more complicated. The TypeScriptReflectionHost and other miscellaneous functions have moved into a new 'reflection' package. The former 'host' package which contained the ReflectionHost interface and associated types was also merged into this new 'reflection' package. Finally, the Reference APIs were moved to the 'imports' package, which will consolidate all import-related logic in ngtsc. PR Close #27743
This commit is contained in:

committed by
Kara Erickson

parent
37b716b298
commit
2a6108af97
18
packages/compiler-cli/src/ngtsc/imports/BUILD.bazel
Normal file
18
packages/compiler-cli/src/ngtsc/imports/BUILD.bazel
Normal file
@ -0,0 +1,18 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "imports",
|
||||
srcs = glob([
|
||||
"index.ts",
|
||||
"src/*.ts",
|
||||
]),
|
||||
module_name = "@angular/compiler-cli/src/ngtsc/imports",
|
||||
deps = [
|
||||
"//packages:types",
|
||||
"//packages/compiler",
|
||||
"@ngdeps//@types/node",
|
||||
"@ngdeps//typescript",
|
||||
],
|
||||
)
|
9
packages/compiler-cli/src/ngtsc/imports/index.ts
Normal file
9
packages/compiler-cli/src/ngtsc/imports/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
export {AbsoluteReference, ImportMode, NodeReference, Reference, ResolvedReference} from './src/references';
|
150
packages/compiler-cli/src/ngtsc/imports/src/references.ts
Normal file
150
packages/compiler-cli/src/ngtsc/imports/src/references.ts
Normal file
@ -0,0 +1,150 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
/// <reference types="node" />
|
||||
|
||||
import {Expression, ExternalExpr, ExternalReference, WrappedNodeExpr} from '@angular/compiler';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
|
||||
const TS_DTS_JS_EXTENSION = /(?:\.d)?\.ts$|\.js$/;
|
||||
|
||||
export enum ImportMode {
|
||||
UseExistingImport,
|
||||
ForceNewImport,
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to a `ts.Node`.
|
||||
*
|
||||
* For example, if an expression evaluates to a function or class definition, it will be returned
|
||||
* as a `Reference` (assuming references are allowed in evaluation).
|
||||
*/
|
||||
export abstract class Reference<T extends ts.Node = ts.Node> {
|
||||
constructor(readonly node: T) {}
|
||||
|
||||
/**
|
||||
* Whether an `Expression` can be generated which references the node.
|
||||
*/
|
||||
// TODO(issue/24571): remove '!'.
|
||||
readonly expressable !: boolean;
|
||||
|
||||
/**
|
||||
* Generate an `Expression` representing this type, in the context of the given SourceFile.
|
||||
*
|
||||
* This could be a local variable reference, if the symbol is imported, or it could be a new
|
||||
* import if needed.
|
||||
*/
|
||||
abstract toExpression(context: ts.SourceFile, importMode?: ImportMode): Expression|null;
|
||||
|
||||
abstract addIdentifier(identifier: ts.Identifier): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to a node only, without any ability to get an `Expression` representing that node.
|
||||
*
|
||||
* This is used for returning references to things like method declarations, which are not directly
|
||||
* referenceable.
|
||||
*/
|
||||
export class NodeReference<T extends ts.Node = ts.Node> extends Reference<T> {
|
||||
constructor(node: T, readonly moduleName: string|null) { super(node); }
|
||||
|
||||
toExpression(context: ts.SourceFile): null { return null; }
|
||||
|
||||
addIdentifier(identifier: ts.Identifier): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to a node which has a `ts.Identifier` and can be resolved to an `Expression`.
|
||||
*
|
||||
* Imports generated by `ResolvedReference`s are always relative.
|
||||
*/
|
||||
export class ResolvedReference<T extends ts.Node = ts.Node> extends Reference<T> {
|
||||
protected identifiers: ts.Identifier[] = [];
|
||||
|
||||
constructor(node: T, protected primaryIdentifier: ts.Identifier) { super(node); }
|
||||
|
||||
readonly expressable = true;
|
||||
|
||||
toExpression(context: ts.SourceFile, importMode: ImportMode = ImportMode.UseExistingImport):
|
||||
Expression {
|
||||
const localIdentifier =
|
||||
pickIdentifier(context, this.primaryIdentifier, this.identifiers, importMode);
|
||||
if (localIdentifier !== null) {
|
||||
return new WrappedNodeExpr(localIdentifier);
|
||||
} else {
|
||||
// Relative import from context -> this.node.getSourceFile().
|
||||
// TODO(alxhub): investigate the impact of multiple source roots here.
|
||||
// TODO(alxhub): investigate the need to map such paths via the Host for proper g3 support.
|
||||
let relative =
|
||||
path.posix.relative(path.dirname(context.fileName), this.node.getSourceFile().fileName)
|
||||
.replace(TS_DTS_JS_EXTENSION, '');
|
||||
|
||||
// path.relative() does not include the leading './'.
|
||||
if (!relative.startsWith('.')) {
|
||||
relative = `./${relative}`;
|
||||
}
|
||||
|
||||
// path.relative() returns the empty string (converted to './' above) if the two paths are the
|
||||
// same.
|
||||
if (relative === './') {
|
||||
// Same file after all.
|
||||
return new WrappedNodeExpr(this.primaryIdentifier);
|
||||
} else {
|
||||
return new ExternalExpr(new ExternalReference(relative, this.primaryIdentifier.text));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addIdentifier(identifier: ts.Identifier): void { this.identifiers.push(identifier); }
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to a node which has a `ts.Identifer` and an expected absolute module name.
|
||||
*
|
||||
* An `AbsoluteReference` can be resolved to an `Expression`, and if that expression is an import
|
||||
* the module specifier will be an absolute module name, not a relative path.
|
||||
*/
|
||||
export class AbsoluteReference<T extends ts.Node> extends Reference<T> {
|
||||
private identifiers: ts.Identifier[] = [];
|
||||
constructor(
|
||||
node: T, private primaryIdentifier: ts.Identifier, readonly moduleName: string,
|
||||
readonly symbolName: string) {
|
||||
super(node);
|
||||
}
|
||||
|
||||
readonly expressable = true;
|
||||
|
||||
toExpression(context: ts.SourceFile, importMode: ImportMode = ImportMode.UseExistingImport):
|
||||
Expression {
|
||||
const localIdentifier =
|
||||
pickIdentifier(context, this.primaryIdentifier, this.identifiers, importMode);
|
||||
if (localIdentifier !== null) {
|
||||
return new WrappedNodeExpr(localIdentifier);
|
||||
} else {
|
||||
return new ExternalExpr(new ExternalReference(this.moduleName, this.symbolName));
|
||||
}
|
||||
}
|
||||
|
||||
addIdentifier(identifier: ts.Identifier): void { this.identifiers.push(identifier); }
|
||||
}
|
||||
|
||||
function pickIdentifier(
|
||||
context: ts.SourceFile, primary: ts.Identifier, secondaries: ts.Identifier[],
|
||||
mode: ImportMode): ts.Identifier|null {
|
||||
context = ts.getOriginalNode(context) as ts.SourceFile;
|
||||
|
||||
if (ts.getOriginalNode(primary).getSourceFile() === context) {
|
||||
return primary;
|
||||
} else if (mode === ImportMode.UseExistingImport) {
|
||||
return secondaries.find(id => ts.getOriginalNode(id).getSourceFile() === context) || null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user