fix(compiler-cli): identify aliased initializer functions (#54480)

Fixes that initializer functions weren't being recognized if they are aliased (e.g. `import {model as alias} from '@angular/core';`).

To do this efficiently, I had to introduce the `ImportedSymbolsTracker` which scans the top-level imports of a file and allows them to be checked quickly, without having to go through the type checker. It will be useful in the future when verifying that that initializer APIs aren't used in unexpected places.

I've also introduced tests specifically for the `tryParseInitializerApiMember` function so that we can test it in isolation instead of going through the various functions that call into it.

PR Close #54480
This commit is contained in:
Kristiyan Kostadinov
2024-02-16 13:33:06 +01:00
committed by Dylan Hunn
parent d68cf175a4
commit 48aec63ee4
26 changed files with 562 additions and 131 deletions
@@ -12,7 +12,7 @@ import ts from 'typescript';
import {Cycle, CycleAnalyzer, CycleHandlingStrategy} from '../../../cycles';
import {ErrorCode, FatalDiagnosticError, makeDiagnostic, makeRelatedInformation} from '../../../diagnostics';
import {absoluteFrom, relative} from '../../../file_system';
import {assertSuccessfulReferenceEmit, DeferredSymbolTracker, ImportedFile, LocalCompilationExtraImportsTracker, ModuleResolver, Reference, ReferenceEmitter} from '../../../imports';
import {assertSuccessfulReferenceEmit, DeferredSymbolTracker, ImportedFile, ImportedSymbolsTracker, LocalCompilationExtraImportsTracker, ModuleResolver, Reference, ReferenceEmitter} from '../../../imports';
import {DependencyTracker} from '../../../incremental/api';
import {extractSemanticTypeParameters, SemanticDepGraphUpdater} from '../../../incremental/semantic_graph';
import {IndexingContext} from '../../../indexer';
@@ -83,7 +83,8 @@ export class ComponentDecoratorHandler implements
private injectableRegistry: InjectableClassRegistry,
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
private annotateForClosureCompiler: boolean, private perf: PerfRecorder,
private hostDirectivesResolver: HostDirectivesResolver, private includeClassMetadata: boolean,
private hostDirectivesResolver: HostDirectivesResolver,
private importTracker: ImportedSymbolsTracker, private includeClassMetadata: boolean,
private readonly compilationMode: CompilationMode,
private readonly deferredSymbolTracker: DeferredSymbolTracker,
private readonly forbidOrphanRendering: boolean, private readonly enableBlockSyntax: boolean,
@@ -225,8 +226,8 @@ export class ComponentDecoratorHandler implements
// @Component inherits @Directive, so begin by extracting the @Directive metadata and building
// on it.
const directiveResult = extractDirectiveMetadata(
node, decorator, this.reflector, this.evaluator, this.refEmitter, this.referencesRegistry,
this.isCore, this.annotateForClosureCompiler, this.compilationMode,
node, decorator, this.reflector, this.importTracker, this.evaluator, this.refEmitter,
this.referencesRegistry, this.isCore, this.annotateForClosureCompiler, this.compilationMode,
this.elementSchemaRegistry.getDefaultComponentElementName(), this.useTemplatePipeline);
if (directiveResult === undefined) {
// `extractDirectiveMetadata` returns undefined when the @Directive has `jit: true`. In this
@@ -13,7 +13,7 @@ import {CycleAnalyzer, CycleHandlingStrategy, ImportGraph} from '../../../cycles
import {ErrorCode, FatalDiagnosticError, ngErrorCode} from '../../../diagnostics';
import {absoluteFrom} from '../../../file_system';
import {runInEachFileSystem} from '../../../file_system/testing';
import {DeferredSymbolTracker, ModuleResolver, Reference, ReferenceEmitter} from '../../../imports';
import {DeferredSymbolTracker, ImportedSymbolsTracker, ModuleResolver, Reference, ReferenceEmitter} from '../../../imports';
import {CompoundMetadataReader, DtsMetadataReader, HostDirectivesResolver, LocalMetadataRegistry, ResourceRegistry} from '../../../metadata';
import {PartialEvaluator} from '../../../partial_evaluator';
import {NOOP_PERF_RECORDER} from '../../../perf';
@@ -68,6 +68,7 @@ function setup(
const typeCheckScopeRegistry =
new TypeCheckScopeRegistry(scopeRegistry, metaReader, hostDirectivesResolver);
const resourceLoader = new StubResourceLoader();
const importTracker = new ImportedSymbolsTracker();
const handler = new ComponentDecoratorHandler(
reflectionHost,
@@ -99,6 +100,7 @@ function setup(
/* annotateForClosureCompiler */ false,
NOOP_PERF_RECORDER,
hostDirectivesResolver,
importTracker,
true,
compilationMode,
new DeferredSymbolTracker(checker, /* onlyExplicitDeferDependencyImports */ false),
@@ -9,7 +9,7 @@
import {compileClassMetadata, compileDeclareClassMetadata, compileDeclareDirectiveFromMetadata, compileDirectiveFromMetadata, ConstantPool, FactoryTarget, makeBindingParser, R3ClassMetadata, R3DirectiveMetadata, WrappedNodeExpr} from '@angular/compiler';
import ts from 'typescript';
import {Reference, ReferenceEmitter} from '../../../imports';
import {ImportedSymbolsTracker, Reference, ReferenceEmitter} from '../../../imports';
import {extractSemanticTypeParameters, SemanticDepGraphUpdater} from '../../../incremental/semantic_graph';
import {ClassPropertyMapping, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, HostDirectiveMeta, InputMapping, MatchSource, MetadataReader, MetadataRegistry, MetaKind} from '../../../metadata';
import {PartialEvaluator} from '../../../partial_evaluator';
@@ -62,6 +62,7 @@ export class DirectiveDecoratorHandler implements
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
private annotateForClosureCompiler: boolean,
private perf: PerfRecorder,
private importTracker: ImportedSymbolsTracker,
private includeClassMetadata: boolean,
private readonly compilationMode: CompilationMode,
private readonly useTemplatePipeline: boolean,
@@ -104,8 +105,8 @@ export class DirectiveDecoratorHandler implements
this.perf.eventCount(PerfEvent.AnalyzeDirective);
const directiveResult = extractDirectiveMetadata(
node, decorator, this.reflector, this.evaluator, this.refEmitter, this.referencesRegistry,
this.isCore, this.annotateForClosureCompiler, this.compilationMode,
node, decorator, this.reflector, this.importTracker, this.evaluator, this.refEmitter,
this.referencesRegistry, this.isCore, this.annotateForClosureCompiler, this.compilationMode,
/* defaultSelector */ null, this.useTemplatePipeline);
if (directiveResult === undefined) {
return {};
@@ -8,6 +8,7 @@
import ts from 'typescript';
import {ImportedSymbolsTracker} from '../../../imports';
import {ClassMember, ReflectionHost} from '../../../reflection';
import {CORE_MODULE} from '../../common';
@@ -39,6 +40,18 @@ interface InitializerFunctionMetadata {
isRequired: boolean;
}
/**
* Metadata that can be inferred from an initializer
* statically without going through the type checker.
*/
interface StaticInitializerData {
/** Identifier in the initializer that refers to the Angular API. */
node: ts.Identifier;
/** Whether the call is required. */
isRequired: boolean;
}
/**
* Attempts to identify an Angular class member that is declared via
* its initializer referring to a given initializer API function.
@@ -48,97 +61,110 @@ interface InitializerFunctionMetadata {
*/
export function tryParseInitializerApiMember<FnNames extends InitializerApiFunction[]>(
fnNames: FnNames, member: Pick<ClassMember, 'value'>, reflector: ReflectionHost,
isCore: boolean): InitializerFunctionMetadata&{apiName: FnNames[number]}|null {
importTracker: ImportedSymbolsTracker): InitializerFunctionMetadata|null {
if (member.value === null || !ts.isCallExpression(member.value)) {
return null;
}
const call = member.value;
const staticResult = parseTopLevelCall(call, fnNames, importTracker) ||
parseTopLevelRequiredCall(call, fnNames, importTracker) ||
parseTopLevelCallFromNamespace(call, fnNames, importTracker);
// Extract target. Either:
// - `[input]`
// - `core.[input]`
// - `input.[required]`
// - `core.input.[required]`.
let target = extractPropertyTarget(call.expression);
if (target === null) {
if (staticResult === null) {
return null;
}
// Find if the `target` matches one of the expected APIs we are looking for.
// e.g. `input`, or `viewChild`.
let apiName = fnNames.find(n => n === target!.text);
// Case 1: API is directly called. e.g. `input`
// If no API name was matched, continue looking for `input.required`.
if (apiName !== undefined) {
if (!isReferenceToInitializerApiFunction(apiName, target, isCore, reflector)) {
return null;
}
return {apiName, call, isRequired: false};
}
// Case 2: API is the `.required`
// Ensure there is a property access to `[input].required` or `[core.input].required`.
if (target.text !== 'required' || !ts.isPropertyAccessExpression(call.expression)) {
return null;
}
// e.g. `[input.required]` (the full property access is this)
const apiPropertyAccess = call.expression;
// e.g. `[input].required` (we now extract the left side of the access).
target = extractPropertyTarget(apiPropertyAccess.expression);
if (target === null) {
return null;
}
// Find if the `target` matches one of the expected APIs are are looking for.
apiName = fnNames.find(n => n === target!.text);
// Ensure the call refers to the real API function from Angular core.
if (apiName === undefined ||
!isReferenceToInitializerApiFunction(apiName, target, isCore, reflector)) {
// Once we've statically determined that the initializer is one of the APIs we're looking for, we
// need to verify it using the type checker which accounts for things like shadowed variables.
// This should be done as the absolute last step since using the type check can be expensive.
const resolvedImport = reflector.getImportOfIdentifier(staticResult.node);
if (resolvedImport === null || !(fnNames as string[]).includes(resolvedImport.name)) {
return null;
}
return {
apiName,
call,
isRequired: true,
isRequired: staticResult.isRequired,
apiName: resolvedImport.name as InitializerApiFunction,
};
}
/**
* Extracts the identifier property target of a expression, supporting
* one level deep property accesses.
*
* e.g. `input.required` will return `required`.
* e.g. `input` will return `input`.
*
* Attempts to parse a top-level call to an initializer function,
* e.g. `prop = input()`. Returns null if it can't be parsed.
*/
function extractPropertyTarget(node: ts.Expression): ts.Identifier|null {
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name)) {
return node.name;
} else if (ts.isIdentifier(node)) {
return node;
function parseTopLevelCall(
call: ts.CallExpression, fnNames: InitializerApiFunction[],
importTracker: ImportedSymbolsTracker): StaticInitializerData|null {
const node = call.expression;
if (!ts.isIdentifier(node)) {
return null;
}
return null;
return fnNames.some(
name => importTracker.isPotentialReferenceToNamedImport(node, name, CORE_MODULE)) ?
{node, isRequired: false} :
null;
}
/**
* Verifies that the given identifier resolves to the given initializer API
* function expression from Angular core.
* Attempts to parse a top-level call to a required initializer,
* e.g. `prop = input.required()`. Returns null if it can't be parsed.
*/
function isReferenceToInitializerApiFunction(
functionName: InitializerApiFunction, target: ts.Identifier, isCore: boolean,
reflector: ReflectionHost): boolean {
let targetImport: {name: string, from: string}|null = reflector.getImportOfIdentifier(target);
if (targetImport === null) {
if (!isCore) {
return false;
}
// We are compiling the core module, where no import can be present.
targetImport = {name: target.text, from: CORE_MODULE};
function parseTopLevelRequiredCall(
call: ts.CallExpression, fnNames: InitializerApiFunction[],
importTracker: ImportedSymbolsTracker): StaticInitializerData|null {
const node = call.expression;
if (!ts.isPropertyAccessExpression(node) || !ts.isIdentifier(node.expression) ||
node.name.text !== 'required') {
return null;
}
return targetImport.name === functionName && targetImport.from === CORE_MODULE;
const expression = node.expression;
const matchesCoreApi = fnNames.some(
name => importTracker.isPotentialReferenceToNamedImport(expression, name, CORE_MODULE));
return matchesCoreApi ? {node: expression, isRequired: true} : null;
}
/**
* Attempts to parse a top-level call to a function referenced via a namespace import,
* e.g. `prop = core.input.required()`. Returns null if it can't be parsed.
*/
function parseTopLevelCallFromNamespace(
call: ts.CallExpression, fnNames: InitializerApiFunction[],
importTracker: ImportedSymbolsTracker): StaticInitializerData|null {
const node = call.expression;
if (!ts.isPropertyAccessExpression(node)) {
return null;
}
let apiReference: ts.Identifier|null = null;
let isRequired = false;
// `prop = core.input()`
if (ts.isIdentifier(node.expression) && ts.isIdentifier(node.name) &&
importTracker.isPotentialReferenceToNamespaceImport(node.expression, CORE_MODULE)) {
apiReference = node.name;
} else if (
// `prop = core.input.required()`
ts.isPropertyAccessExpression(node.expression) &&
ts.isIdentifier(node.expression.expression) && ts.isIdentifier(node.expression.name) &&
importTracker.isPotentialReferenceToNamespaceImport(
node.expression.expression, CORE_MODULE) &&
node.name.text === 'required') {
apiReference = node.expression.name;
isRequired = true;
}
if (apiReference === null || !(fnNames as string[]).includes(apiReference.text)) {
return null;
}
return {node: apiReference, isRequired};
}
@@ -8,9 +8,9 @@
import ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../../diagnostics';
import {ImportedSymbolsTracker} from '../../../imports';
import {InputMapping} from '../../../metadata';
import {ClassMember, ReflectionHost, reflectObjectLiteral} from '../../../reflection';
import {ClassMember, ReflectionHost} from '../../../reflection';
import {tryParseInitializerApiMember} from './initializer_functions';
import {parseAndValidateInputAndOutputOptions} from './input_output_parse_options';
@@ -21,8 +21,8 @@ import {parseAndValidateInputAndOutputOptions} from './input_output_parse_option
*/
export function tryParseSignalInputMapping(
member: Pick<ClassMember, 'name'|'value'>, reflector: ReflectionHost,
isCore: boolean): InputMapping|null {
const signalInput = tryParseInitializerApiMember(['input'], member, reflector, isCore);
importTracker: ImportedSymbolsTracker): InputMapping|null {
const signalInput = tryParseInitializerApiMember(['input'], member, reflector, importTracker);
if (signalInput === null) {
return null;
}
@@ -8,6 +8,7 @@
import ts from 'typescript';
import {ImportedSymbolsTracker} from '../../../imports';
import {ModelMapping} from '../../../metadata';
import {ClassMember, ReflectionHost} from '../../../reflection';
@@ -19,8 +20,8 @@ import {parseAndValidateInputAndOutputOptions} from './input_output_parse_option
*/
export function tryParseSignalModelMapping(
member: Pick<ClassMember, 'name'|'value'>, reflector: ReflectionHost,
isCore: boolean): ModelMapping|null {
const model = tryParseInitializerApiMember(['model'], member, reflector, isCore);
importTracker: ImportedSymbolsTracker): ModelMapping|null {
const model = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
if (model === null) {
return null;
}
@@ -9,6 +9,7 @@
import ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../../diagnostics';
import {ImportedSymbolsTracker} from '../../../imports';
import {InputOrOutput} from '../../../metadata';
import {ClassMember, ReflectionHost} from '../../../reflection';
@@ -21,8 +22,10 @@ import {parseAndValidateInputAndOutputOptions} from './input_output_parse_option
*/
export function tryParseInitializerBasedOutput(
member: Pick<ClassMember, 'name'|'value'>, reflector: ReflectionHost,
isCore: boolean): {call: ts.CallExpression, metadata: InputOrOutput}|null {
const output = tryParseInitializerApiMember(['output', 'ɵoutput'], member, reflector, isCore);
importTracker: ImportedSymbolsTracker): {call: ts.CallExpression, metadata: InputOrOutput}|
null {
const output =
tryParseInitializerApiMember(['output', 'ɵoutput'], member, reflector, importTracker);
if (output === null) {
return null;
}
@@ -11,6 +11,7 @@ import {createMayBeForwardRefExpression, ForwardRefHandling, MaybeForwardRefExpr
import ts from 'typescript';
import {ErrorCode, FatalDiagnosticError} from '../../../diagnostics';
import {ImportedSymbolsTracker} from '../../../imports';
import {ClassMember, ReflectionHost, reflectObjectLiteral} from '../../../reflection';
import {tryUnwrapForwardRef} from '../../common';
@@ -35,9 +36,10 @@ const defaultDescendantsValue = (type: QueryFunctionName) => type !== 'contentCh
* @returns Resolved query metadata, or null if no query is declared.
*/
export function tryParseSignalQueryFromInitializer(
member: Pick<ClassMember, 'name'|'value'>, reflector: ReflectionHost, isCore: boolean):
member: Pick<ClassMember, 'name'|'value'>, reflector: ReflectionHost,
importTracker: ImportedSymbolsTracker):
{name: QueryFunctionName, metadata: R3QueryMetadata, call: ts.CallExpression}|null {
const query = tryParseInitializerApiMember(queryFunctionNames, member, reflector, isCore);
const query = tryParseInitializerApiMember(queryFunctionNames, member, reflector, importTracker);
if (query === null) {
return null;
}
@@ -58,10 +60,10 @@ export function tryParseSignalQueryFromInitializer(
const read = options?.has('read') ? parseReadOption(options.get('read')!) : null;
const descendants = options?.has('descendants') ?
parseDescendantsOption(options.get('descendants')!) :
defaultDescendantsValue(query.apiName);
defaultDescendantsValue(query.apiName as QueryFunctionName);
return {
name: query.apiName,
name: query.apiName as QueryFunctionName,
call: query.call,
metadata: {
isSignal: true,
@@ -10,7 +10,7 @@ import {createMayBeForwardRefExpression, emitDistinctChangesOnlyDefaultValue, Ex
import ts from 'typescript';
import {ErrorCode, FatalDiagnosticError, makeRelatedInformation} from '../../../diagnostics';
import {assertSuccessfulReferenceEmit, ImportFlags, Reference, ReferenceEmitter} from '../../../imports';
import {assertSuccessfulReferenceEmit, ImportedSymbolsTracker, ImportFlags, Reference, ReferenceEmitter} from '../../../imports';
import {ClassPropertyMapping, DecoratorInputTransform, HostDirectiveMeta, InputMapping, InputOrOutput, isHostDirectiveMetaForGlobalMode} from '../../../metadata';
import {DynamicValue, EnumValue, PartialEvaluator, ResolvedValue, traceDynamicValue} from '../../../partial_evaluator';
import {AmbientImport, ClassDeclaration, ClassMember, ClassMemberKind, Decorator, filterToMembersWithDecorator, isNamedClassDeclaration, ReflectionHost, reflectObjectLiteral} from '../../../reflection';
@@ -38,9 +38,10 @@ const QUERY_TYPES = new Set<string>(queryDecoratorNames);
*/
export function extractDirectiveMetadata(
clazz: ClassDeclaration, decorator: Readonly<Decorator>, reflector: ReflectionHost,
evaluator: PartialEvaluator, refEmitter: ReferenceEmitter,
referencesRegistry: ReferencesRegistry, isCore: boolean, annotateForClosureCompiler: boolean,
compilationMode: CompilationMode, defaultSelector: string|null, useTemplatePipeline: boolean): {
importTracker: ImportedSymbolsTracker, evaluator: PartialEvaluator,
refEmitter: ReferenceEmitter, referencesRegistry: ReferencesRegistry, isCore: boolean,
annotateForClosureCompiler: boolean, compilationMode: CompilationMode,
defaultSelector: string|null, useTemplatePipeline: boolean): {
decorator: Map<string, ts.Expression>,
metadata: R3DirectiveMetadata,
inputs: ClassPropertyMapping<InputMapping>,
@@ -84,19 +85,19 @@ export function extractDirectiveMetadata(
const inputsFromMeta =
parseInputsArray(clazz, directive, evaluator, reflector, refEmitter, compilationMode);
const inputsFromFields = parseInputFields(
clazz, members, evaluator, reflector, refEmitter, isCore, compilationMode, inputsFromMeta,
decorator);
clazz, members, evaluator, reflector, importTracker, refEmitter, isCore, compilationMode,
inputsFromMeta, decorator);
const inputs = ClassPropertyMapping.fromMappedObject({...inputsFromMeta, ...inputsFromFields});
// And outputs.
const outputsFromMeta = parseOutputsArray(directive, evaluator);
const outputsFromFields =
parseOutputFields(clazz, decorator, members, isCore, reflector, evaluator, outputsFromMeta);
const outputsFromFields = parseOutputFields(
clazz, decorator, members, isCore, reflector, importTracker, evaluator, outputsFromMeta);
const outputs = ClassPropertyMapping.fromMappedObject({...outputsFromMeta, ...outputsFromFields});
// Parse queries of fields.
const {viewQueries, contentQueries} =
parseQueriesOfClassFields(members, reflector, evaluator, isCore);
parseQueriesOfClassFields(members, reflector, importTracker, evaluator, isCore);
if (directive.has('queries')) {
const signalQueryFields = new Set(
@@ -750,13 +751,13 @@ function tryGetDecoratorOnMember(
function tryParseInputFieldMapping(
clazz: ClassDeclaration, member: ClassMember, evaluator: PartialEvaluator,
reflector: ReflectionHost, isCore: boolean, refEmitter: ReferenceEmitter,
compilationMode: CompilationMode): InputMapping|null {
reflector: ReflectionHost, importTracker: ImportedSymbolsTracker, isCore: boolean,
refEmitter: ReferenceEmitter, compilationMode: CompilationMode): InputMapping|null {
const classPropertyName = member.name;
const decorator = tryGetDecoratorOnMember(member, 'Input', isCore);
const signalInputMapping = tryParseSignalInputMapping(member, reflector, isCore);
const modelInputMapping = tryParseSignalModelMapping(member, reflector, isCore);
const signalInputMapping = tryParseSignalInputMapping(member, reflector, importTracker);
const modelInputMapping = tryParseSignalModelMapping(member, reflector, importTracker);
if (decorator !== null && signalInputMapping !== null) {
throw new FatalDiagnosticError(
@@ -838,8 +839,9 @@ function tryParseInputFieldMapping(
/** Parses the class members that declare inputs (via decorator or initializer). */
function parseInputFields(
clazz: ClassDeclaration, members: ClassMember[], evaluator: PartialEvaluator,
reflector: ReflectionHost, refEmitter: ReferenceEmitter, isCore: boolean,
compilationMode: CompilationMode, inputsFromClassDecorator: Record<string, InputMapping>,
reflector: ReflectionHost, importTracker: ImportedSymbolsTracker, refEmitter: ReferenceEmitter,
isCore: boolean, compilationMode: CompilationMode,
inputsFromClassDecorator: Record<string, InputMapping>,
classDecorator: Decorator): Record<string, InputMapping> {
const inputs = {} as Record<string, InputMapping>;
@@ -850,6 +852,7 @@ function parseInputFields(
member,
evaluator,
reflector,
importTracker,
isCore,
refEmitter,
compilationMode,
@@ -1028,8 +1031,8 @@ function assertEmittableInputType(
* initializers for signal-based queries.
*/
function parseQueriesOfClassFields(
members: ClassMember[], reflector: ReflectionHost, evaluator: PartialEvaluator,
isCore: boolean): {
members: ClassMember[], reflector: ReflectionHost, importTracker: ImportedSymbolsTracker,
evaluator: PartialEvaluator, isCore: boolean): {
viewQueries: R3QueryMetadata[],
contentQueries: R3QueryMetadata[],
} {
@@ -1046,7 +1049,7 @@ function parseQueriesOfClassFields(
for (const member of members) {
const decoratorQuery = tryGetQueryFromFieldDecorator(member, reflector, evaluator, isCore);
const signalQuery = tryParseSignalQueryFromInitializer(member, reflector, isCore);
const signalQuery = tryParseSignalQueryFromInitializer(member, reflector, importTracker);
if (decoratorQuery !== null && signalQuery !== null) {
throw new FatalDiagnosticError(
@@ -1106,14 +1109,14 @@ function parseOutputsArray(
/** Parses the class members that are outputs. */
function parseOutputFields(
clazz: ClassDeclaration, classDecorator: Decorator, members: ClassMember[], isCore: boolean,
reflector: ReflectionHost, evaluator: PartialEvaluator,
reflector: ReflectionHost, importTracker: ImportedSymbolsTracker, evaluator: PartialEvaluator,
outputsFromMeta: Record<string, string>): Record<string, string> {
const outputs = {} as Record<string, string>;
for (const member of members) {
const decoratorOutput = tryParseDecoratorOutput(member, evaluator, isCore);
const initializerOutput = tryParseInitializerBasedOutput(member, reflector, isCore);
const modelMapping = tryParseSignalModelMapping(member, reflector, isCore);
const initializerOutput = tryParseInitializerBasedOutput(member, reflector, importTracker);
const modelMapping = tryParseSignalModelMapping(member, reflector, importTracker);
if (decoratorOutput !== null && initializerOutput !== null) {
throw new FatalDiagnosticError(
@@ -10,7 +10,7 @@ import ts from 'typescript';
import {absoluteFrom} from '../../../file_system';
import {runInEachFileSystem} from '../../../file_system/testing';
import {ReferenceEmitter} from '../../../imports';
import {ImportedSymbolsTracker, ReferenceEmitter} from '../../../imports';
import {CompoundMetadataReader, DtsMetadataReader, LocalMetadataRegistry} from '../../../metadata';
import {PartialEvaluator} from '../../../partial_evaluator';
import {NOOP_PERF_RECORDER} from '../../../perf';
@@ -174,13 +174,15 @@ runInEachFileSystem(() => {
metaReader, new CompoundMetadataReader([metaReader, dtsReader]),
new MetadataDtsModuleScopeResolver(dtsReader, null), refEmitter, null);
const injectableRegistry = new InjectableClassRegistry(reflectionHost, /* isCore */ false);
const importTracker = new ImportedSymbolsTracker();
const handler = new DirectiveDecoratorHandler(
reflectionHost, evaluator, scopeRegistry, scopeRegistry, metaReader, injectableRegistry,
refEmitter, referenceRegistry,
/*isCore*/ false,
/*strictCtorDeps*/ false,
/*semanticDepGraphUpdater*/ null,
/*annotateForClosureCompiler*/ false, NOOP_PERF_RECORDER, /*includeClassMetadata*/ true,
/*annotateForClosureCompiler*/ false, NOOP_PERF_RECORDER, importTracker,
/*includeClassMetadata*/ true,
/*compilationMode */ CompilationMode.FULL, /* useTemplatePipeline */ true,
/*generateExtraImportsInLocalMode*/ false);
@@ -0,0 +1,267 @@
/*!
* @license
* Copyright Google LLC 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 ts from 'typescript';
import {absoluteFrom} from '../../../file_system';
import {runInEachFileSystem} from '../../../file_system/testing';
import {ImportedSymbolsTracker} from '../../../imports';
import {ClassMember, TypeScriptReflectionHost} from '../../../reflection';
import {makeProgram} from '../../../testing';
import {tryParseInitializerApiMember} from '../src/initializer_functions';
runInEachFileSystem(() => {
describe('initializer function detection', () => {
it('should identify a non-required function that is imported directly', () => {
const {member, reflector, importTracker} = setup(`
import {Directive, model} from '@angular/core';
@Directive()
export class Dir {
test = model(1);
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toEqual({
apiName: 'model',
isRequired: false,
call: jasmine.objectContaining({kind: ts.SyntaxKind.CallExpression}),
});
});
it('should identify a required function that is imported directly', () => {
const {member, reflector, importTracker} = setup(`
import {Directive, model} from '@angular/core';
@Directive()
export class Dir {
test = model.required();
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toEqual({
apiName: 'model',
isRequired: true,
call: jasmine.objectContaining({kind: ts.SyntaxKind.CallExpression}),
});
});
it('should identify a non-required function that is aliased', () => {
const {member, reflector, importTracker} = setup(`
import {Directive, model as alias} from '@angular/core';
@Directive()
export class Dir {
test = alias(1);
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toEqual({
apiName: 'model',
isRequired: false,
call: jasmine.objectContaining({kind: ts.SyntaxKind.CallExpression}),
});
});
it('should identify a required function that is aliased', () => {
const {member, reflector, importTracker} = setup(`
import {Directive, model as alias} from '@angular/core';
@Directive()
export class Dir {
test = alias.required();
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toEqual({
apiName: 'model',
isRequired: true,
call: jasmine.objectContaining({kind: ts.SyntaxKind.CallExpression}),
});
});
it('should identify a non-required function that is imported via namespace import', () => {
const {member, reflector, importTracker} = setup(`
import * as core from '@angular/core';
@core.Directive()
export class Dir {
test = core.model(1);
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toEqual({
apiName: 'model',
isRequired: false,
call: jasmine.objectContaining({kind: ts.SyntaxKind.CallExpression}),
});
});
it('should identify a required function that is imported via namespace import', () => {
const {member, reflector, importTracker} = setup(`
import * as core from '@angular/core';
@core.Directive()
export class Dir {
test = core.model.required();
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toEqual({
apiName: 'model',
isRequired: true,
call: jasmine.objectContaining({kind: ts.SyntaxKind.CallExpression}),
});
});
it('should not identify a valid core function that is not being checked for', () => {
const {member, reflector, importTracker} = setup(`
import {Directive, input} from '@angular/core';
@Directive()
export class Dir {
test = input(1);
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toBe(null);
});
it('should not identify a function coming from a different module', () => {
const {member, reflector, importTracker} = setup(`
import {Directive} from '@angular/core';
import {model} from '@not-angular/core';
@Directive()
export class Dir {
test = model(1);
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toBe(null);
});
it('should not identify an invalid call on a core function', () => {
const {member, reflector, importTracker} = setup(`
import {Directive, model} from '@angular/core';
@Directive()
export class Dir {
test = model.unknown();
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toBe(null);
});
it('should not identify an invalid call on a core function through a namespace import', () => {
const {member, reflector, importTracker} = setup(`
import {Directive} from '@angular/core';
import * as core from '@angular/core';
@Directive()
export class Dir {
test = core.model.unknown();
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toBe(null);
});
it('should identify shadowed declarations', () => {
const {member, reflector, importTracker} = setup(`
import {Directive, model} from '@angular/core';
function wrapper() {
function model(value: number): any {}
@Directive()
class Dir {
test = model(1);
}
}
`);
const result = tryParseInitializerApiMember(['model'], member, reflector, importTracker);
expect(result).toBe(null);
});
});
});
function setup(contents: string) {
const fileName = absoluteFrom('/test.ts');
const {program} = makeProgram([
{
name: absoluteFrom('/node_modules/@angular/core/index.d.ts'),
contents: `
export const Directive: any;
export interface InitializerFunction {
(initialValue: any): any;
required(): any;
unknown(): any;
}
export const input: InitializerFunction;
export const model: InitializerFunction;
`,
},
{
name: absoluteFrom('/node_modules/@not-angular/core/index.d.ts'),
contents: `
export interface InitializerFunction {
(initialValue: any): any;
required(): any;
}
export const model: InitializerFunction;
`,
},
{name: fileName, contents}
]);
const sourceFile = program.getSourceFile(fileName);
const importTracker = new ImportedSymbolsTracker();
const reflector = new TypeScriptReflectionHost(program.getTypeChecker());
if (sourceFile === undefined) {
throw new Error(`Cannot resolve test file ${fileName}`);
}
let member: Pick<ClassMember, 'value'>|null = null;
(function walk(node: ts.Node) {
if (ts.isPropertyDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === 'test') {
member = {value: node.initializer ?? null};
} else {
ts.forEachChild(node, walk);
}
})(sourceFile);
if (member === null) {
throw new Error(`Could not resolve a class property with a name of "test" in the test file`);
}
return {member, reflector, importTracker};
}
@@ -15,7 +15,7 @@ import {COMPILER_ERRORS_WITH_GUIDES, ERROR_DETAILS_PAGE_BASE_URL, ErrorCode, isF
import {DocEntry, DocsExtractor} from '../../docs';
import {checkForPrivateExports, ReferenceGraph} from '../../entry_point';
import {absoluteFromSourceFile, AbsoluteFsPath, LogicalFileSystem, resolve} from '../../file_system';
import {AbsoluteModuleStrategy, AliasingHost, AliasStrategy, DefaultImportTracker, DeferredSymbolTracker, ImportRewriter, LocalCompilationExtraImportsTracker, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NoopImportRewriter, PrivateExportAliasingHost, R3SymbolsImportRewriter, Reference, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy, UnifiedModulesAliasingHost, UnifiedModulesStrategy} from '../../imports';
import {AbsoluteModuleStrategy, AliasingHost, AliasStrategy, DefaultImportTracker, DeferredSymbolTracker, ImportedSymbolsTracker, ImportRewriter, LocalCompilationExtraImportsTracker, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NoopImportRewriter, PrivateExportAliasingHost, R3SymbolsImportRewriter, Reference, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy, UnifiedModulesAliasingHost, UnifiedModulesStrategy} from '../../imports';
import {IncrementalBuildStrategy, IncrementalCompilation, IncrementalState} from '../../incremental';
import {SemanticSymbol} from '../../incremental/semantic_graph';
import {generateAnalysis, IndexedComponent, IndexingContext} from '../../indexer';
@@ -1101,6 +1101,7 @@ export class NgCompiler {
const injectableRegistry = new InjectableClassRegistry(reflector, isCore);
const hostDirectivesResolver = new HostDirectivesResolver(metaReader);
const exportedProviderStatusResolver = new ExportedProviderStatusResolver(metaReader);
const importTracker = new ImportedSymbolsTracker();
const typeCheckScopeRegistry =
new TypeCheckScopeRegistry(scopeReader, metaReader, hostDirectivesResolver);
@@ -1174,7 +1175,7 @@ export class NgCompiler {
this.cycleAnalyzer, cycleHandlingStrategy, refEmitter, referencesRegistry,
this.incrementalCompilation.depGraph, injectableRegistry, semanticDepGraphUpdater,
this.closureCompilerEnabled, this.delegatingPerfRecorder, hostDirectivesResolver,
supportTestBed, compilationMode, deferredSymbolsTracker,
importTracker, supportTestBed, compilationMode, deferredSymbolsTracker,
!!this.options.forbidOrphanComponents, this.enableBlockSyntax,
this.options.useTemplatePipeline ?? SHOULD_USE_TEMPLATE_PIPELINE,
localCompilationExtraImportsTracker),
@@ -1187,6 +1188,7 @@ export class NgCompiler {
injectableRegistry, refEmitter, referencesRegistry, isCore, strictCtorDeps, semanticDepGraphUpdater,
this.closureCompilerEnabled,
this.delegatingPerfRecorder,
importTracker,
supportTestBed, compilationMode,
this.options.useTemplatePipeline ?? SHOULD_USE_TEMPLATE_PIPELINE,
!!this.options.generateExtraImportsInLocalMode,
@@ -11,6 +11,7 @@ export {ImportRewriter, NoopImportRewriter, R3SymbolsImportRewriter, validateAnd
export {DefaultImportTracker} from './src/default';
export {DeferredSymbolTracker} from './src/deferred_symbol_tracker';
export {AbsoluteModuleStrategy, assertSuccessfulReferenceEmit, EmittedReference, FailedEmitResult, ImportedFile, ImportFlags, LocalIdentifierStrategy, LogicalProjectStrategy, ReferenceEmitKind, ReferenceEmitResult, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy, UnifiedModulesStrategy} from './src/emitter';
export {ImportedSymbolsTracker} from './src/imported_symbols_tracker';
export {LocalCompilationExtraImportsTracker} from './src/local_compilation_extra_imports_tracker';
export {isAliasImportDeclaration, loadIsReferencedAliasDeclarationPatch} from './src/patch_alias_reference_resolution';
export {Reexport} from './src/reexport';
@@ -0,0 +1,104 @@
/*!
* @license
* Copyright Google LLC 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 ts from 'typescript';
/** Mapping between modules and the named imports consumed by them in a file. */
type NamedImportsMap = {
[moduleName: string]: {
// The key is the symbol's original name, while the set
// includes all the local names it is vailable under.
[exportedName: string]: Set<string>,
}
};
type NamespaceImportsMap = {
// The key is a set of names the namespace is available under within a file.
[moduleName: string]: Set<string>
};
/**
* Tracks which symbols are imported in specific files and under what names. Allows for efficient
* querying for references to those symbols without having to consult the type checker early in the
* process.
*
* Note that the tracker doesn't account for variable shadowing so a final verification with the
* type checker may be necessary, depending on the context. Also does not track dynamic imports.
*/
export class ImportedSymbolsTracker {
private fileToNamedImports = new WeakMap<ts.SourceFile, NamedImportsMap>();
private fileToNamespaceImports = new WeakMap<ts.SourceFile, NamespaceImportsMap>();
/**
* Checks if an identifier is a potential reference to a specific named import within the same
* file.
* @param node Identifier to be checked.
* @param exportedName Name of the exported symbol that is being searched for.
* @param moduleName Module from which the symbol should be imported.
*/
isPotentialReferenceToNamedImport(node: ts.Identifier, exportedName: string, moduleName: string):
boolean {
const sourceFile = node.getSourceFile();
this.scanImports(sourceFile);
const fileImports = this.fileToNamedImports.get(sourceFile)!;
const moduleImports = fileImports[moduleName] ?? null;
const symbolImports = moduleImports?.[exportedName];
return symbolImports !== undefined && symbolImports.has(node.text);
}
/**
* Checks if an identifier is a potential reference to a specific namespace import within the same
* file.
* @param node Identifier to be checked.
* @param moduleName Module from which the namespace is imported.
*/
isPotentialReferenceToNamespaceImport(node: ts.Identifier, moduleName: string): boolean {
const sourceFile = node.getSourceFile();
this.scanImports(sourceFile);
const namespaces = this.fileToNamespaceImports.get(sourceFile)!;
return namespaces[moduleName]?.has(node.text) ?? false;
}
/** Scans a `SourceFile` for import statements and caches them for later use. */
private scanImports(sourceFile: ts.SourceFile): void {
if (this.fileToNamedImports.has(sourceFile) && this.fileToNamespaceImports.has(sourceFile)) {
return;
}
const namedImports: NamedImportsMap = {};
const namespaceImports: NamespaceImportsMap = {};
this.fileToNamedImports.set(sourceFile, namedImports);
this.fileToNamespaceImports.set(sourceFile, namespaceImports);
// Only check top-level imports.
for (const stmt of sourceFile.statements) {
if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteralLike(stmt.moduleSpecifier) ||
stmt.importClause?.namedBindings === undefined) {
continue;
}
const moduleName = stmt.moduleSpecifier.text;
if (ts.isNamespaceImport(stmt.importClause.namedBindings)) {
// import * as foo from 'module'
namespaceImports[moduleName] ??= new Set();
namespaceImports[moduleName].add(stmt.importClause.namedBindings.name.text);
} else {
// import {foo, bar as alias} from 'module'
for (const element of stmt.importClause.namedBindings.elements) {
const localName = element.name.text;
const exportedName =
element.propertyName === undefined ? localName : element.propertyName.text;
namedImports[moduleName] ??= {};
namedImports[moduleName][exportedName] ??= new Set();
namedImports[moduleName][exportedName].add(localName);
}
}
}
}
}
@@ -8,7 +8,7 @@
import ts from 'typescript';
import {PartialEvaluator} from '../../ngtsc/partial_evaluator';
import {ImportedSymbolsTracker} from '../../ngtsc/imports';
import {TypeScriptReflectionHost} from '../../ngtsc/reflection';
import {getDownlevelDecoratorsTransform} from './downlevel_decorators_transform';
@@ -39,13 +39,14 @@ export function angularJitApplicationTransform(
program: ts.Program, isCore = false): ts.TransformerFactory<ts.SourceFile> {
const typeChecker = program.getTypeChecker();
const reflectionHost = new TypeScriptReflectionHost(typeChecker);
const evaluator = new PartialEvaluator(reflectionHost, typeChecker, null);
const importTracker = new ImportedSymbolsTracker();
const downlevelDecoratorTransform = getDownlevelDecoratorsTransform(
typeChecker, reflectionHost, [], isCore,
/* enableClosureCompiler */ false);
const initializerApisJitTransform = getInitializerApiJitTransform(reflectionHost, isCore);
const initializerApisJitTransform =
getInitializerApiJitTransform(reflectionHost, importTracker, isCore);
return (ctx) => {
return (sourceFile) => {
@@ -28,6 +28,7 @@ export const signalInputsTransform: PropertyTransform = (
member,
host,
factory,
importTracker,
importManager,
classDecorator,
isCore,
@@ -38,10 +39,7 @@ export const signalInputsTransform: PropertyTransform = (
}
const inputMapping = tryParseSignalInputMapping(
{name: member.name.text, value: member.initializer ?? null},
host,
isCore,
);
{name: member.name.text, value: member.initializer ?? null}, host, importTracker);
if (inputMapping === null) {
return member;
}
@@ -21,6 +21,7 @@ export const signalModelTransform: PropertyTransform = (
member,
host,
factory,
importTracker,
importManager,
decorator,
isCore,
@@ -34,7 +35,7 @@ export const signalModelTransform: PropertyTransform = (
const modelMapping = tryParseSignalModelMapping(
{name: member.name.text, value: member.initializer ?? null},
host,
isCore,
importTracker,
);
if (modelMapping === null) {
@@ -24,6 +24,7 @@ export const initializerApiOutputTransform: PropertyTransform = (
member,
host,
factory,
importTracker,
importManager,
classDecorator,
isCore,
@@ -36,7 +37,7 @@ export const initializerApiOutputTransform: PropertyTransform = (
const output = tryParseInitializerBasedOutput(
{name: member.name.text, value: member.initializer ?? null},
host,
isCore,
importTracker,
);
if (output === null) {
return member;
@@ -33,6 +33,7 @@ export const queryFunctionsTransforms: PropertyTransform = (
member,
host,
factory,
importTracker,
importManager,
classDecorator,
isCore,
@@ -49,7 +50,7 @@ export const queryFunctionsTransforms: PropertyTransform = (
const queryDefinition = tryParseSignalQueryFromInitializer(
{name: member.name.text, value: member.initializer ?? null},
host,
isCore,
importTracker,
);
if (queryDefinition === null) {
return member;
@@ -9,6 +9,7 @@
import ts from 'typescript';
import {isAngularDecorator} from '../../../ngtsc/annotations';
import {ImportedSymbolsTracker} from '../../../ngtsc/imports';
import {ReflectionHost} from '../../../ngtsc/reflection';
import {addImports} from '../../../ngtsc/transform';
import {ImportManager} from '../../../ngtsc/translator';
@@ -42,6 +43,7 @@ const propertyTransforms: PropertyTransform[] = [
*/
export function getInitializerApiJitTransform(
host: ReflectionHost,
importTracker: ImportedSymbolsTracker,
isCore: boolean,
): ts.TransformerFactory<ts.SourceFile> {
return ctx => {
@@ -50,7 +52,7 @@ export function getInitializerApiJitTransform(
sourceFile = ts.visitNode(
sourceFile,
createTransformVisitor(ctx, host, importManager, isCore),
createTransformVisitor(ctx, host, importManager, importTracker, isCore),
ts.isSourceFile,
);
@@ -68,6 +70,7 @@ function createTransformVisitor(
ctx: ts.TransformationContext,
host: ReflectionHost,
importManager: ImportManager,
importTracker: ImportedSymbolsTracker,
isCore: boolean,
): ts.Visitor<ts.Node, ts.Node> {
const visitor: ts.Visitor<ts.Node, ts.Node> = (node: ts.Node): ts.Node => {
@@ -90,7 +93,7 @@ function createTransformVisitor(
for (const transform of propertyTransforms) {
const newNode = transform(
member as ts.PropertyDeclaration & {name: ts.Identifier | ts.StringLiteralLike},
host, ctx.factory, importManager, angularDecorator, isCore);
host, ctx.factory, importTracker, importManager, angularDecorator, isCore);
if (newNode !== member) {
hasChanged = true;
@@ -8,14 +8,16 @@
import ts from 'typescript';
import {Decorator, ReflectionHost} from '../../..//ngtsc/reflection';
import {ImportedSymbolsTracker} from '../../../ngtsc/imports';
import {Decorator, ReflectionHost} from '../../../ngtsc/reflection';
import {ImportManager} from '../../../ngtsc/translator';
/** Function that can be used to transform class properties. */
export type PropertyTransform =
(node: ts.PropertyDeclaration&{name: ts.Identifier | ts.StringLiteralLike},
host: ReflectionHost, factory: ts.NodeFactory, importManager: ImportManager,
classDecorator: Decorator, isCore: boolean) => ts.PropertyDeclaration;
host: ReflectionHost, factory: ts.NodeFactory, importTracker: ImportedSymbolsTracker,
importManager: ImportManager, classDecorator: Decorator, isCore: boolean) =>
ts.PropertyDeclaration;
/**
* Creates an import and access for a given Angular core import while
+1
View File
@@ -62,6 +62,7 @@ ts_library(
],
deps = [
":test_utils",
"//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/transformers/jit_transforms",
@@ -8,6 +8,7 @@
import ts from 'typescript';
import {ImportedSymbolsTracker} from '../src/ngtsc/imports';
import {TypeScriptReflectionHost} from '../src/ngtsc/reflection';
import {getDownlevelDecoratorsTransform, getInitializerApiJitTransform} from '../src/transformers/jit_transforms';
@@ -51,9 +52,10 @@ describe('initializer API metadata transform', () => {
const testFile = program.getSourceFile(TEST_FILE_INPUT);
const typeChecker = program.getTypeChecker();
const reflectionHost = new TypeScriptReflectionHost(typeChecker);
const importTracker = new ImportedSymbolsTracker();
const transformers: ts.CustomTransformers = {
before: [
getInitializerApiJitTransform(reflectionHost, /* isCore */ false),
getInitializerApiJitTransform(reflectionHost, importTracker, /* isCore */ false),
]
};
@@ -8,6 +8,7 @@
import ts from 'typescript';
import {ImportedSymbolsTracker} from '../src/ngtsc/imports';
import {TypeScriptReflectionHost} from '../src/ngtsc/reflection';
import {getDownlevelDecoratorsTransform, getInitializerApiJitTransform} from '../src/transformers/jit_transforms';
@@ -58,9 +59,10 @@ describe('signal queries metadata transform', () => {
const testFile = program.getSourceFile(TEST_FILE_INPUT);
const typeChecker = program.getTypeChecker();
const reflectionHost = new TypeScriptReflectionHost(typeChecker);
const importTracker = new ImportedSymbolsTracker();
const transformers: ts.CustomTransformers = {
before: [
getInitializerApiJitTransform(reflectionHost, /* isCore */ false),
getInitializerApiJitTransform(reflectionHost, importTracker, /* isCore */ false),
]
};
@@ -19,6 +19,7 @@ ts_library(
srcs = ["authoring_test_compiler.ts"],
deps = [
"//packages/compiler-cli",
"//packages/compiler-cli/src/ngtsc/imports",
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/transformers/jit_transforms",
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ImportedSymbolsTracker} from '@angular/compiler-cli/src/ngtsc/imports';
import {TypeScriptReflectionHost} from '@angular/compiler-cli/src/ngtsc/reflection';
import {getInitializerApiJitTransform} from '@angular/compiler-cli/src/transformers/jit_transforms';
import fs from 'fs';
@@ -28,11 +29,13 @@ async function main() {
});
const host = new TypeScriptReflectionHost(program.getTypeChecker());
const importTracker = new ImportedSymbolsTracker();
for (const inputFileExecpath of inputFileExecpaths) {
const outputFile = ts.transform(
program.getSourceFile(inputFileExecpath)!,
[getInitializerApiJitTransform(host, /* isCore */ false)], program.getCompilerOptions());
[getInitializerApiJitTransform(host, importTracker, /* isCore */ false)],
program.getCompilerOptions());
await fs.promises.writeFile(
path.join(outputDirExecPath, `transformed_${path.basename(inputFileExecpath)}`),