Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@angular/core

Package Overview
Dependencies
Maintainers
2
Versions
1053
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@angular/core - npm Package Compare versions

Comparing version
22.0.0-rc.0
to
22.0.0-rc.1
+1017
schematics/bundles/index-B07c0BtS.cjs
'use strict';
/**
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/
* License: MIT
*/
'use strict';
var ts = require('typescript');
var compilerCli = require('@angular/compiler-cli');
var migrations = require('@angular/compiler-cli/private/migrations');
require('node:path');
var project_paths = require('./project_paths-D2V-Uh2L.cjs');
var compiler = require('@angular/compiler');
function getMemberName(member) {
if (member.name === undefined) {
return null;
}
if (ts.isIdentifier(member.name) || ts.isStringLiteralLike(member.name)) {
return member.name.text;
}
if (ts.isPrivateIdentifier(member.name)) {
return `#${member.name.text}`;
}
return null;
}
/** Checks whether the given node can be an `@Input()` declaration node. */
function isInputContainerNode(node) {
return (((ts.isAccessor(node) && ts.isClassDeclaration(node.parent)) ||
ts.isPropertyDeclaration(node)) &&
getMemberName(node) !== null);
}
/**
* Detects `query(By.directive(T)).componentInstance` patterns and enhances
* them with information of `T`. This is important because `.componentInstance`
* is currently typed as `any` and may cause runtime test failures after input
* migrations then.
*
* The reference resolution pass leverages information from this pattern
* recognizer.
*/
class DebugElementComponentInstance {
checker;
cache = new WeakMap();
constructor(checker) {
this.checker = checker;
}
detect(node) {
if (this.cache.has(node)) {
return this.cache.get(node);
}
if (!ts.isPropertyAccessExpression(node)) {
return null;
}
// Check for `<>.componentInstance`.
if (!ts.isIdentifier(node.name) || node.name.text !== 'componentInstance') {
return null;
}
// Check for `<>.query(..).<>`.
if (!ts.isCallExpression(node.expression) ||
!ts.isPropertyAccessExpression(node.expression.expression) ||
!ts.isIdentifier(node.expression.expression.name) ||
node.expression.expression.name.text !== 'query') {
return null;
}
const queryCall = node.expression;
if (queryCall.arguments.length !== 1) {
return null;
}
const queryArg = queryCall.arguments[0];
let typeExpr;
if (ts.isCallExpression(queryArg) &&
queryArg.arguments.length === 1 &&
ts.isIdentifier(queryArg.arguments[0])) {
// Detect references, like: `query(By.directive(T))`.
typeExpr = queryArg.arguments[0];
}
else if (ts.isIdentifier(queryArg)) {
// Detect references, like: `harness.query(T)`.
typeExpr = queryArg;
}
else {
return null;
}
const symbol = this.checker.getSymbolAtLocation(typeExpr);
if (symbol?.valueDeclaration === undefined ||
!ts.isClassDeclaration(symbol?.valueDeclaration)) {
// Cache this as we use the expensive type checker.
this.cache.set(node, null);
return null;
}
const type = this.checker.getTypeAtLocation(symbol.valueDeclaration);
this.cache.set(node, type);
return type;
}
}
/**
* Recognizes `Partial<T>` instances in Catalyst tests. Those type queries
* are likely used for typing property initialization values for the given class `T`
* and we have a few scenarios:
*
* 1. The API does not unwrap signal inputs. In which case, the values are likely no
* longer assignable to an `InputSignal`.
* 2. The API does unwrap signal inputs, in which case we need to unwrap the `Partial`
* because the values are raw initial values, like they were before.
*
* We can enable this heuristic when we detect Catalyst as we know it supports unwrapping.
*/
class PartialDirectiveTypeInCatalystTests {
checker;
knownFields;
constructor(checker, knownFields) {
this.checker = checker;
this.knownFields = knownFields;
}
detect(node) {
// Detect `Partial<...>`
if (!ts.isTypeReferenceNode(node) ||
!ts.isIdentifier(node.typeName) ||
node.typeName.text !== 'Partial') {
return null;
}
// Ignore if the source file doesn't reference Catalyst.
if (!node.getSourceFile().text.includes('angular2/testing/catalyst')) {
return null;
}
// Extract T of `Partial<T>`.
const cmpTypeArg = node.typeArguments?.[0];
if (!cmpTypeArg ||
!ts.isTypeReferenceNode(cmpTypeArg) ||
!ts.isIdentifier(cmpTypeArg.typeName)) {
return null;
}
const cmpType = cmpTypeArg.typeName;
const symbol = this.checker.getSymbolAtLocation(cmpType);
// Note: Technically the class might be derived of an input-containing class,
// but this is out of scope for now. We can expand if we see it's a common case.
if (symbol?.valueDeclaration === undefined ||
!ts.isClassDeclaration(symbol.valueDeclaration) ||
!this.knownFields.shouldTrackClassReference(symbol.valueDeclaration)) {
return null;
}
return { referenceNode: node, targetClass: symbol.valueDeclaration };
}
}
/**
* Attempts to look up the given property access chain using
* the type checker.
*
* Notably this is not as safe as using the type checker directly to
* retrieve symbols of a given identifier, but in some cases this is
* a necessary approach to compensate e.g. for a lack of TCB information
* when processing Angular templates.
*
* The path is a list of properties to be accessed sequentially on the
* given type.
*/
function lookupPropertyAccess(checker, type, path, options = {}) {
let symbol = null;
for (const propName of path) {
// Note: We support assuming `NonNullable` for the pathl This is necessary
// in some situations as otherwise the lookups would fail to resolve the target
// symbol just because of e.g. a ternary. This is used in the signal input migration
// for host bindings.
type = options.ignoreNullability ? type.getNonNullableType() : type;
const propSymbol = type.getProperty(propName);
if (propSymbol === undefined) {
return null;
}
symbol = propSymbol;
type = checker.getTypeOfSymbol(propSymbol);
}
if (symbol === null) {
return null;
}
return { symbol, type };
}
/**
* AST visitor that iterates through a template and finds all
* input references.
*
* This resolution is important to be able to migrate references to inputs
* that will be migrated to signal inputs.
*/
class TemplateReferenceVisitor extends compiler.TmplAstRecursiveVisitor {
result = [];
/**
* Whether we are currently descending into HTML AST nodes
* where all bound attributes are considered potentially narrowing.
*
* Keeps track of all referenced inputs in such attribute expressions.
*/
templateAttributeReferencedFields = null;
expressionVisitor;
seenKnownFieldsCount = new Map();
constructor(typeChecker, templateTypeChecker, componentClass, knownFields, fieldNamesToConsiderForReferenceLookup) {
super();
this.expressionVisitor = new TemplateExpressionReferenceVisitor(typeChecker, templateTypeChecker, componentClass, knownFields, fieldNamesToConsiderForReferenceLookup);
}
checkExpressionForReferencedFields(activeNode, expressionNode) {
const referencedFields = this.expressionVisitor.checkTemplateExpression(activeNode, expressionNode);
// Add all references to the overall visitor result.
this.result.push(...referencedFields);
// Count usages of seen input references. We'll use this to make decisions
// based on whether inputs are potentially narrowed or not.
for (const input of referencedFields) {
this.seenKnownFieldsCount.set(input.targetField.key, (this.seenKnownFieldsCount.get(input.targetField.key) ?? 0) + 1);
}
return referencedFields;
}
descendAndCheckForNarrowedSimilarReferences(potentiallyNarrowedInputs, descend) {
const inputs = potentiallyNarrowedInputs.map((i) => ({
ref: i,
key: i.targetField.key,
pastCount: this.seenKnownFieldsCount.get(i.targetField.key) ?? 0,
}));
descend();
for (const input of inputs) {
// Input was referenced inside a narrowable spot, and is used in child nodes.
// This is a sign for the input to be narrowed. Mark it as such.
if ((this.seenKnownFieldsCount.get(input.key) ?? 0) > input.pastCount) {
input.ref.isLikelyNarrowed = true;
}
}
}
visitTemplate(template) {
// Note: We assume all bound expressions for templates may be subject
// to TCB narrowing. This is relevant for now until we support narrowing
// of signal calls in templates.
// TODO: Remove with: https://github.com/angular/angular/pull/55456.
this.templateAttributeReferencedFields = [];
compiler.tmplAstVisitAll(this, template.attributes);
compiler.tmplAstVisitAll(this, template.templateAttrs);
// If we are dealing with a microsyntax template, do not check
// inputs and outputs as those are already passed to the children.
// Template attributes may contain relevant expressions though.
if (template.tagName === 'ng-template') {
compiler.tmplAstVisitAll(this, template.inputs);
compiler.tmplAstVisitAll(this, template.outputs);
}
const referencedInputs = this.templateAttributeReferencedFields;
this.templateAttributeReferencedFields = null;
this.descendAndCheckForNarrowedSimilarReferences(referencedInputs, () => {
compiler.tmplAstVisitAll(this, template.children);
compiler.tmplAstVisitAll(this, template.references);
compiler.tmplAstVisitAll(this, template.variables);
});
}
visitIfBlockBranch(block) {
if (block.expression) {
const referencedFields = this.checkExpressionForReferencedFields(block, block.expression);
this.descendAndCheckForNarrowedSimilarReferences(referencedFields, () => {
super.visitIfBlockBranch(block);
});
}
else {
super.visitIfBlockBranch(block);
}
}
visitForLoopBlock(block) {
this.checkExpressionForReferencedFields(block, block.expression);
if (block.trackBy !== null) {
this.checkExpressionForReferencedFields(block, block.trackBy);
}
super.visitForLoopBlock(block);
}
visitSwitchBlock(block) {
const referencedFields = this.checkExpressionForReferencedFields(block, block.expression);
this.descendAndCheckForNarrowedSimilarReferences(referencedFields, () => {
super.visitSwitchBlock(block);
});
}
visitSwitchBlockCase(block) {
if (block.expression) {
const referencedFields = this.checkExpressionForReferencedFields(block, block.expression);
this.descendAndCheckForNarrowedSimilarReferences(referencedFields, () => {
super.visitSwitchBlockCase(block);
});
}
else {
super.visitSwitchBlockCase(block);
}
}
visitDeferredBlock(deferred) {
if (deferred.triggers.when) {
this.checkExpressionForReferencedFields(deferred, deferred.triggers.when.value);
}
if (deferred.prefetchTriggers.when) {
this.checkExpressionForReferencedFields(deferred, deferred.prefetchTriggers.when.value);
}
super.visitDeferredBlock(deferred);
}
visitBoundText(text) {
this.checkExpressionForReferencedFields(text, text.value);
}
visitBoundEvent(attribute) {
this.checkExpressionForReferencedFields(attribute, attribute.handler);
}
visitBoundAttribute(attribute) {
const referencedFields = this.checkExpressionForReferencedFields(attribute, attribute.value);
// Attributes inside templates are potentially "narrowed" and hence we
// keep track of all referenced inputs to see if they actually are.
if (this.templateAttributeReferencedFields !== null) {
this.templateAttributeReferencedFields.push(...referencedFields);
}
}
visitLetDeclaration(decl) {
this.checkExpressionForReferencedFields(decl, decl.value);
}
visitIcu(icu) {
for (const v of Object.values(icu.vars)) {
this.checkExpressionForReferencedFields(icu, v.value);
}
for (const p of Object.values(icu.placeholders)) {
if (p instanceof compiler.TmplAstBoundText) {
this.checkExpressionForReferencedFields(icu, p.value);
}
}
}
}
/**
* Expression AST visitor that checks whether a given expression references
* a known `@Input()`.
*
* This resolution is important to be able to migrate references to inputs
* that will be migrated to signal inputs.
*/
class TemplateExpressionReferenceVisitor extends compiler.RecursiveAstVisitor {
typeChecker;
templateTypeChecker;
componentClass;
knownFields;
fieldNamesToConsiderForReferenceLookup;
activeTmplAstNode = null;
detectedInputReferences = [];
isInsideObjectShorthandExpression = false;
insideConditionalExpressionsWithReads = [];
constructor(typeChecker, templateTypeChecker, componentClass, knownFields, fieldNamesToConsiderForReferenceLookup) {
super();
this.typeChecker = typeChecker;
this.templateTypeChecker = templateTypeChecker;
this.componentClass = componentClass;
this.knownFields = knownFields;
this.fieldNamesToConsiderForReferenceLookup = fieldNamesToConsiderForReferenceLookup;
}
/** Checks the given AST expression. */
checkTemplateExpression(activeNode, expressionNode) {
this.detectedInputReferences = [];
this.activeTmplAstNode = activeNode;
expressionNode.visit(this, []);
return this.detectedInputReferences;
}
visit(ast, context) {
super.visit(ast, [...context, ast]);
}
// Keep track when we are inside an object shorthand expression. This is
// necessary as we need to expand the shorthand to invoke a potential new signal.
// E.g. `{bla}` may be transformed to `{bla: bla()}`.
visitLiteralMap(ast, context) {
for (const [idx, key] of ast.keys.entries()) {
this.isInsideObjectShorthandExpression =
key.kind === 'property' && !!key.isShorthandInitialized;
ast.values[idx].visit(this, context);
this.isInsideObjectShorthandExpression = false;
}
}
visitPropertyRead(ast, context) {
this._inspectPropertyAccess(ast, false, context);
super.visitPropertyRead(ast, context);
}
visitSafePropertyRead(ast, context) {
this._inspectPropertyAccess(ast, false, context);
super.visitPropertyRead(ast, context);
}
visitBinary(ast, context) {
if (ast.operation === '=' && ast.left instanceof compiler.PropertyRead) {
this._inspectPropertyAccess(ast.left, true, [...context, ast, ast.left]);
}
else {
super.visitBinary(ast, context);
}
}
visitConditional(ast, context) {
this.visit(ast.condition, context);
this.insideConditionalExpressionsWithReads.push(ast.condition);
this.visit(ast.trueExp, context);
this.visit(ast.falseExp, context);
this.insideConditionalExpressionsWithReads.pop();
}
/**
* Inspects the property access and attempts to resolve whether they access
* a known field. If so, the result is captured.
*/
_inspectPropertyAccess(ast, isAssignment, astPath) {
if (this.fieldNamesToConsiderForReferenceLookup !== null &&
!this.fieldNamesToConsiderForReferenceLookup.has(ast.name)) {
return;
}
const isWrite = !!(isAssignment ||
(this.activeTmplAstNode && isTwoWayBindingNode(this.activeTmplAstNode)));
this._checkAccessViaTemplateTypeCheckBlock(ast, isWrite, astPath) ||
this._checkAccessViaOwningComponentClassType(ast, isWrite, astPath);
}
/**
* Checks whether the node refers to an input using the TCB information.
* Type check block may not exist for e.g. test components, so this can return `null`.
*/
_checkAccessViaTemplateTypeCheckBlock(ast, isWrite, astPath) {
// There might be no template type checker. E.g. if we check host bindings.
if (this.templateTypeChecker === null) {
return false;
}
const symbol = this.templateTypeChecker.getSymbolOfNode(ast, this.componentClass);
if (symbol?.kind !== migrations.SymbolKind.Expression) {
return false;
}
const tsSymbol = this.templateTypeChecker.getTsSymbolOfSymbol(symbol);
if (tsSymbol === null) {
return false;
}
// Dangerous: Type checking symbol retrieval is a totally different `ts.Program`,
// than the one where we analyzed `knownInputs`.
// --> Find the input via its input id.
const targetInput = this.knownFields.attemptRetrieveDescriptorFromSymbol(tsSymbol);
if (targetInput === null) {
return false;
}
this.detectedInputReferences.push({
targetNode: targetInput.node,
targetField: targetInput,
read: ast,
readAstPath: astPath,
context: this.activeTmplAstNode,
isLikelyNarrowed: this._isPartOfNarrowingTernary(ast),
isObjectShorthandExpression: this.isInsideObjectShorthandExpression,
isWrite,
});
return true;
}
/**
* Simple resolution checking whether the given AST refers to a known input.
* This is a fallback for when there is no type checking information (e.g. in host bindings).
*
* It attempts to resolve references by traversing accesses of the "component class" type.
* e.g. `this.bla` is resolved via `CompType#bla` and further.
*/
_checkAccessViaOwningComponentClassType(ast, isWrite, astPath) {
// We might check host bindings, which can never point to template variables or local refs.
const expressionTemplateTarget = this.templateTypeChecker === null
? null
: this.templateTypeChecker.getExpressionTarget(ast, this.componentClass);
// Skip checking if:
// - the reference resolves to a template variable or local ref. No way to resolve without TCB.
// - the owning component does not have a name (should not happen technically).
if (expressionTemplateTarget !== null || this.componentClass.name === undefined) {
return;
}
const property = traverseReceiverAndLookupSymbol(ast, this.componentClass, this.typeChecker);
if (property === null) {
return;
}
const matchingTarget = this.knownFields.attemptRetrieveDescriptorFromSymbol(property);
if (matchingTarget === null) {
return;
}
this.detectedInputReferences.push({
targetNode: matchingTarget.node,
targetField: matchingTarget,
read: ast,
readAstPath: astPath,
context: this.activeTmplAstNode,
isLikelyNarrowed: this._isPartOfNarrowingTernary(ast),
isObjectShorthandExpression: this.isInsideObjectShorthandExpression,
isWrite,
});
}
_isPartOfNarrowingTernary(read) {
// Note: We do not safe check that the reads are fully matching 1:1. This is acceptable
// as worst case we just skip an input from being migrated. This is very unlikely too.
return this.insideConditionalExpressionsWithReads.some((r) => (r instanceof compiler.PropertyRead || r instanceof compiler.SafePropertyRead) && r.name === read.name);
}
}
/**
* Emulates an access to a given field using the TypeScript `ts.Type`
* of the given class. The resolved symbol of the access is returned.
*/
function traverseReceiverAndLookupSymbol(readOrWrite, componentClass, checker) {
const path = [readOrWrite.name];
let node = readOrWrite;
while (node.receiver instanceof compiler.PropertyRead) {
node = node.receiver;
path.unshift(node.name);
}
if (!(node.receiver instanceof compiler.ImplicitReceiver)) {
return null;
}
const classType = checker.getTypeAtLocation(componentClass.name);
return (lookupPropertyAccess(checker, classType, path, {
// Necessary to avoid breaking the resolution if there is
// some narrowing involved. E.g. `myClass ? myClass.input`.
ignoreNullability: true,
})?.symbol ?? null);
}
/** Whether the given node refers to a two-way binding AST node. */
function isTwoWayBindingNode(node) {
return ((node instanceof compiler.TmplAstBoundAttribute && node.type === compiler.BindingType.TwoWay) ||
(node instanceof compiler.TmplAstBoundEvent && node.type === compiler.ParsedEventType.TwoWay));
}
/** Possible types of references to known fields detected. */
exports.ReferenceKind = void 0;
(function (ReferenceKind) {
ReferenceKind[ReferenceKind["InTemplate"] = 0] = "InTemplate";
ReferenceKind[ReferenceKind["InHostBinding"] = 1] = "InHostBinding";
ReferenceKind[ReferenceKind["TsReference"] = 2] = "TsReference";
ReferenceKind[ReferenceKind["TsClassTypeReference"] = 3] = "TsClassTypeReference";
})(exports.ReferenceKind || (exports.ReferenceKind = {}));
/** Whether the given reference is a TypeScript reference. */
function isTsReference(ref) {
return ref.kind === exports.ReferenceKind.TsReference;
}
/** Whether the given reference is a template reference. */
function isTemplateReference(ref) {
return ref.kind === exports.ReferenceKind.InTemplate;
}
/** Whether the given reference is a host binding reference. */
function isHostBindingReference(ref) {
return ref.kind === exports.ReferenceKind.InHostBinding;
}
/**
* Whether the given reference is a TypeScript `ts.Type` reference
* to a class containing known fields.
*/
function isTsClassTypeReference(ref) {
return ref.kind === exports.ReferenceKind.TsClassTypeReference;
}
/**
* Checks host bindings of the given class and tracks all
* references to inputs within bindings.
*/
function identifyHostBindingReferences(node, programInfo, checker, reflector, result, knownFields, fieldNamesToConsiderForReferenceLookup) {
if (node.name === undefined) {
return;
}
const decorators = reflector.getDecoratorsOfDeclaration(node);
if (decorators === null) {
return;
}
const angularDecorators = migrations.getAngularDecorators(decorators, ['Directive', 'Component'],
/* isAngularCore */ false);
if (angularDecorators.length === 0) {
return;
}
// Assume only one Angular decorator per class.
const ngDecorator = angularDecorators[0];
if (ngDecorator.args?.length !== 1) {
return;
}
const metadataNode = migrations.unwrapExpression(ngDecorator.args[0]);
if (!ts.isObjectLiteralExpression(metadataNode)) {
return;
}
const metadata = migrations.reflectObjectLiteral(metadataNode);
if (!metadata.has('host')) {
return;
}
let hostField = migrations.unwrapExpression(metadata.get('host'));
// Special-case in case host bindings are shared via a variable.
// e.g. Material button shares host bindings as a constant in the same target.
if (ts.isIdentifier(hostField)) {
let symbol = checker.getSymbolAtLocation(hostField);
// Plain identifier references can point to alias symbols (e.g. imports).
if (symbol !== undefined && symbol.flags & ts.SymbolFlags.Alias) {
symbol = checker.getAliasedSymbol(symbol);
}
if (symbol !== undefined &&
symbol.valueDeclaration !== undefined &&
ts.isVariableDeclaration(symbol.valueDeclaration)) {
hostField = symbol?.valueDeclaration.initializer;
}
}
if (hostField === undefined || !ts.isObjectLiteralExpression(hostField)) {
return;
}
const hostMap = migrations.reflectObjectLiteral(hostField);
const expressionResult = [];
const expressionVisitor = new TemplateExpressionReferenceVisitor(checker, null, node, knownFields, fieldNamesToConsiderForReferenceLookup);
for (const [rawName, expression] of hostMap.entries()) {
if (!ts.isStringLiteralLike(expression)) {
continue;
}
const isEventBinding = rawName.startsWith('(');
const isPropertyBinding = rawName.startsWith('[');
// Only migrate property or event bindings.
if (!isPropertyBinding && !isEventBinding) {
continue;
}
const parser = compiler.makeBindingParser();
const sourceSpan = new compiler.ParseSourceSpan(
// Fake source span to keep parsing offsets zero-based.
// We then later combine these with the expression TS node offsets.
new compiler.ParseLocation({ content: '', url: '' }, 0, 0, 0), new compiler.ParseLocation({ content: '', url: '' }, 0, 0, 0));
const name = rawName.substring(1, rawName.length - 1);
let parsed = undefined;
if (isEventBinding) {
const result = [];
parser.parseEvent(name.substring(1, name.length - 1), expression.text, false, sourceSpan, sourceSpan, [], result, sourceSpan);
parsed = result[0].handler;
}
else {
const result = [];
parser.parsePropertyBinding(name, expression.text, true,
/* isTwoWayBinding */ false, sourceSpan, 0, sourceSpan, [], result, sourceSpan);
parsed = result[0].expression;
}
if (parsed != null) {
expressionResult.push(...expressionVisitor.checkTemplateExpression(expression, parsed));
}
}
for (const ref of expressionResult) {
result.references.push({
kind: exports.ReferenceKind.InHostBinding,
from: {
read: ref.read,
readAstPath: ref.readAstPath,
isObjectShorthandExpression: ref.isObjectShorthandExpression,
isWrite: ref.isWrite,
file: project_paths.projectFile(ref.context.getSourceFile(), programInfo),
hostPropertyNode: ref.context,
},
target: ref.targetField,
});
}
}
/**
* Attempts to extract the `TemplateDefinition` for the given
* class, if possible.
*
* The definition can then be used with the Angular compiler to
* load/parse the given template.
*/
function attemptExtractTemplateDefinition(node, checker, reflector, resourceLoader) {
const classDecorators = reflector.getDecoratorsOfDeclaration(node);
const evaluator = new migrations.PartialEvaluator(reflector, checker, null);
const ngDecorators = classDecorators !== null
? migrations.getAngularDecorators(classDecorators, ['Component'], /* isAngularCore */ false)
: [];
if (ngDecorators.length === 0 ||
ngDecorators[0].args === null ||
ngDecorators[0].args.length === 0 ||
!ts.isObjectLiteralExpression(ngDecorators[0].args[0])) {
return null;
}
const properties = migrations.reflectObjectLiteral(ngDecorators[0].args[0]);
const templateProp = properties.get('template');
const templateUrlProp = properties.get('templateUrl');
const containingFile = node.getSourceFile().fileName;
// inline template.
if (templateProp !== undefined) {
const templateStr = evaluator.evaluate(templateProp);
if (typeof templateStr === 'string') {
return {
isInline: true,
expression: templateProp,
preserveWhitespaces: false,
resolvedTemplateUrl: containingFile,
templateUrl: containingFile,
};
}
}
try {
// external template.
if (templateUrlProp !== undefined) {
const templateUrl = evaluator.evaluate(templateUrlProp);
if (typeof templateUrl === 'string') {
return {
isInline: false,
preserveWhitespaces: false,
templateUrlExpression: templateUrlProp,
templateUrl,
resolvedTemplateUrl: resourceLoader.resolve(templateUrl, containingFile),
};
}
}
}
catch (e) {
console.error(`Could not parse external template: ${e}`);
}
return null;
}
/**
* Checks whether the given class has an Angular template, and resolves
* all of the references to inputs.
*/
function identifyTemplateReferences(programInfo, node, reflector, checker, evaluator, templateTypeChecker, resourceLoader, options, result, knownFields, fieldNamesToConsiderForReferenceLookup) {
const template = templateTypeChecker.getTemplate(node, compilerCli.OptimizeFor.WholeProgram) ??
// If there is no template registered in the TCB or compiler, the template may
// be skipped due to an explicit `jit: true` setting. We try to detect this case
// and parse the template manually.
extractTemplateWithoutCompilerAnalysis(node, checker, reflector, resourceLoader, evaluator, options);
if (template !== null) {
const visitor = new TemplateReferenceVisitor(checker, templateTypeChecker, node, knownFields, fieldNamesToConsiderForReferenceLookup);
template.forEach((node) => node.visit(visitor));
for (const res of visitor.result) {
const templateFilePath = res.context.sourceSpan.start.file.url;
// Templates without an URL are non-mappable artifacts of e.g.
// string concatenated templates. See the `indirect` template
// source mapping concept in the compiler. We skip such references
// as those cannot be migrated, but print an error for now.
if (templateFilePath === '') {
// TODO: Incorporate a TODO potentially.
console.error(`Found reference to field ${res.targetField.key} that cannot be ` +
`migrated because the template cannot be parsed with source map information ` +
`(in file: ${node.getSourceFile().fileName}).`);
continue;
}
result.references.push({
kind: exports.ReferenceKind.InTemplate,
from: {
read: res.read,
readAstPath: res.readAstPath,
node: res.context,
isObjectShorthandExpression: res.isObjectShorthandExpression,
originatingTsFile: project_paths.projectFile(node.getSourceFile(), programInfo),
templateFile: project_paths.projectFile(compilerCli.absoluteFrom(templateFilePath), programInfo),
isLikelyPartOfNarrowing: res.isLikelyNarrowed,
isWrite: res.isWrite,
},
target: res.targetField,
});
}
}
}
/**
* Attempts to extract a `@Component` template from the given class,
* without relying on the `NgCompiler` program analysis.
*
* This is useful for JIT components using `jit: true` which were not
* processed by the Angular compiler, but may still have templates that
* contain references to inputs that we can resolve via the fallback
* reference resolutions (that does not use the type check block).
*/
function extractTemplateWithoutCompilerAnalysis(node, checker, reflector, resourceLoader, evaluator, options) {
if (node.name === undefined) {
return null;
}
const tmplDef = attemptExtractTemplateDefinition(node, checker, reflector, resourceLoader);
if (tmplDef === null) {
return null;
}
return migrations.extractTemplate(node, tmplDef, evaluator, null, resourceLoader, {
enableBlockSyntax: true,
enableLetSyntax: true,
usePoisonedData: true,
enableI18nLegacyMessageIdFormat: options.enableI18nLegacyMessageIdFormat !== false,
i18nNormalizeLineEndingsInICUs: options.i18nNormalizeLineEndingsInICUs === true,
enableSelectorless: false,
}, migrations.CompilationMode.FULL).nodes;
}
/** Gets the pattern and property name for a given binding element. */
function resolveBindingElement(node) {
const name = node.propertyName ?? node.name;
// If we are discovering a non-analyzable element in the path, abort.
if (!ts.isStringLiteralLike(name) && !ts.isIdentifier(name)) {
return null;
}
return {
pattern: node.parent,
propertyName: name.text,
};
}
/** Gets the declaration node of the given binding element. */
function getBindingElementDeclaration(node) {
while (true) {
if (ts.isBindingElement(node.parent.parent)) {
node = node.parent.parent;
}
else {
return node.parent.parent;
}
}
}
/**
* Expands the given reference to its containing expression, capturing
* the full context.
*
* E.g. `traverseAccess(ref<`bla`>)` may return `this.bla`
* or `traverseAccess(ref<`bla`>)` may return `this.someObj.a.b.c.bla`.
*
* This helper is useful as we will replace the full access with a temporary
* variable for narrowing. Replacing just the identifier is wrong.
*/
function traverseAccess(access) {
if (ts.isPropertyAccessExpression(access.parent) && access.parent.name === access) {
return access.parent;
}
else if (ts.isElementAccessExpression(access.parent) &&
access.parent.argumentExpression === access) {
return access.parent;
}
return access;
}
/**
* Unwraps the parent of the given node, if it's a
* parenthesized expression or `as` expression.
*/
function unwrapParent(node) {
if (ts.isParenthesizedExpression(node.parent)) {
return unwrapParent(node.parent);
}
else if (ts.isAsExpression(node.parent)) {
return unwrapParent(node.parent);
}
return node;
}
/**
* List of binary operators that indicate a write operation.
*
* Useful for figuring out whether an expression assigns to
* something or not.
*/
const writeBinaryOperators = [
ts.SyntaxKind.EqualsToken,
ts.SyntaxKind.BarBarEqualsToken,
ts.SyntaxKind.BarEqualsToken,
ts.SyntaxKind.AmpersandEqualsToken,
ts.SyntaxKind.AmpersandAmpersandEqualsToken,
ts.SyntaxKind.SlashEqualsToken,
ts.SyntaxKind.MinusEqualsToken,
ts.SyntaxKind.PlusEqualsToken,
ts.SyntaxKind.CaretEqualsToken,
ts.SyntaxKind.PercentEqualsToken,
ts.SyntaxKind.AsteriskEqualsToken,
ts.SyntaxKind.ExclamationEqualsToken,
];
/**
* Checks whether given TypeScript reference refers to an Angular input, and captures
* the reference if possible.
*
* @param fieldNamesToConsiderForReferenceLookup List of field names that should be
* respected when expensively looking up references to known fields.
* May be null if all identifiers should be inspected.
*/
function identifyPotentialTypeScriptReference(node, programInfo, checker, knownFields, result, fieldNamesToConsiderForReferenceLookup, advisors) {
// Skip all identifiers that never can point to a migrated field.
// TODO: Capture these assumptions and performance optimizations in the design doc.
if (fieldNamesToConsiderForReferenceLookup !== null &&
!fieldNamesToConsiderForReferenceLookup.has(node.text)) {
return;
}
let target = undefined;
try {
// Resolve binding elements to their declaration symbol.
// Commonly inputs are accessed via object expansion. e.g. `const {input} = this;`.
if (ts.isBindingElement(node.parent)) {
// Skip binding elements that are using spread.
if (node.parent.dotDotDotToken !== undefined) {
return;
}
const bindingInfo = resolveBindingElement(node.parent);
if (bindingInfo === null) {
// The declaration could not be resolved. Skip analyzing this.
return;
}
const bindingType = checker.getTypeAtLocation(bindingInfo.pattern);
const resolved = lookupPropertyAccess(checker, bindingType, [bindingInfo.propertyName]);
target = resolved?.symbol;
}
else {
target = checker.getSymbolAtLocation(node);
}
}
catch (e) {
console.error('Unexpected error while trying to resolve identifier reference:');
console.error(e);
// Gracefully skip analyzing. This can happen when e.g. a reference is named similar
// to an input, but is dependant on `.d.ts` that is not necessarily available (clutz dts).
return;
}
noTargetSymbolCheck: if (target === undefined) {
if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
const propAccessSymbol = checker.getSymbolAtLocation(node.parent.expression);
if (propAccessSymbol !== undefined &&
propAccessSymbol.valueDeclaration !== undefined &&
ts.isVariableDeclaration(propAccessSymbol.valueDeclaration) &&
propAccessSymbol.valueDeclaration.initializer !== undefined) {
target = advisors.debugElComponentInstanceTracker
.detect(propAccessSymbol.valueDeclaration.initializer)
?.getProperty(node.text);
// We found a target in the fallback path. Break out.
if (target !== undefined) {
break noTargetSymbolCheck;
}
}
}
return;
}
let targetInput = knownFields.attemptRetrieveDescriptorFromSymbol(target);
if (targetInput === null) {
return;
}
const access = unwrapParent(traverseAccess(node));
const accessParent = access.parent;
const isWriteReference = ts.isBinaryExpression(accessParent) &&
accessParent.left === access &&
writeBinaryOperators.includes(accessParent.operatorToken.kind);
// track accesses from source files to known fields.
result.references.push({
kind: exports.ReferenceKind.TsReference,
from: {
node,
file: project_paths.projectFile(node.getSourceFile(), programInfo),
isWrite: isWriteReference,
isPartOfElementBinding: ts.isBindingElement(node.parent),
},
target: targetInput,
});
}
/**
* Phase where we iterate through all source file references and
* detect references to known fields (e.g. commonly inputs).
*
* This is useful, for example in the signal input migration whe
* references need to be migrated to unwrap signals, given that
* their target properties is no longer holding a raw value, but
* instead an `InputSignal`.
*
* This phase detects references in all types of locations:
* - TS source files
* - Angular templates (inline or external)
* - Host binding expressions.
*/
function createFindAllSourceFileReferencesVisitor(programInfo, checker, reflector, resourceLoader, evaluator, templateTypeChecker, knownFields, fieldNamesToConsiderForReferenceLookup, result) {
const debugElComponentInstanceTracker = new DebugElementComponentInstance(checker);
const partialDirectiveCatalystTracker = new PartialDirectiveTypeInCatalystTests(checker, knownFields);
const perfCounters = {
template: 0,
hostBindings: 0,
tsReferences: 0,
tsTypes: 0,
};
// Schematic NodeJS execution may not have `global.performance` defined.
const currentTimeInMs = () => typeof global.performance !== 'undefined' ? global.performance.now() : Date.now();
const visitor = (node) => {
let lastTime = currentTimeInMs();
// Note: If there is no template type checker and resource loader, we aren't processing
// an Angular program, and can skip template detection.
if (ts.isClassDeclaration(node) && templateTypeChecker !== null && resourceLoader !== null) {
identifyTemplateReferences(programInfo, node, reflector, checker, evaluator, templateTypeChecker, resourceLoader, programInfo.userOptions, result, knownFields, fieldNamesToConsiderForReferenceLookup);
perfCounters.template += (currentTimeInMs() - lastTime) / 1000;
lastTime = currentTimeInMs();
identifyHostBindingReferences(node, programInfo, checker, reflector, result, knownFields, fieldNamesToConsiderForReferenceLookup);
perfCounters.hostBindings += (currentTimeInMs() - lastTime) / 1000;
lastTime = currentTimeInMs();
}
lastTime = currentTimeInMs();
// find references, but do not capture input declarations itself.
if (ts.isIdentifier(node) &&
!(isInputContainerNode(node.parent) && node.parent.name === node)) {
identifyPotentialTypeScriptReference(node, programInfo, checker, knownFields, result, fieldNamesToConsiderForReferenceLookup, {
debugElComponentInstanceTracker,
});
}
perfCounters.tsReferences += (currentTimeInMs() - lastTime) / 1000;
lastTime = currentTimeInMs();
// Detect `Partial<T>` references.
// Those are relevant to be tracked as they may be updated in Catalyst to
// unwrap signal inputs. Commonly people use `Partial` in Catalyst to type
// some "component initial values".
const partialDirectiveInCatalyst = partialDirectiveCatalystTracker.detect(node);
if (partialDirectiveInCatalyst !== null) {
result.references.push({
kind: exports.ReferenceKind.TsClassTypeReference,
from: {
file: project_paths.projectFile(partialDirectiveInCatalyst.referenceNode.getSourceFile(), programInfo),
node: partialDirectiveInCatalyst.referenceNode,
},
isPartialReference: true,
isPartOfCatalystFile: true,
target: partialDirectiveInCatalyst.targetClass,
});
}
perfCounters.tsTypes += (currentTimeInMs() - lastTime) / 1000;
};
return {
visitor,
debugPrintMetrics: () => {
console.info('Source file analysis performance', perfCounters);
},
};
}
exports.createFindAllSourceFileReferencesVisitor = createFindAllSourceFileReferencesVisitor;
exports.getBindingElementDeclaration = getBindingElementDeclaration;
exports.getMemberName = getMemberName;
exports.isHostBindingReference = isHostBindingReference;
exports.isInputContainerNode = isInputContainerNode;
exports.isTemplateReference = isTemplateReference;
exports.isTsClassTypeReference = isTsClassTypeReference;
exports.isTsReference = isTsReference;
exports.traverseAccess = traverseAccess;
exports.unwrapParent = unwrapParent;

Sorry, the diff of this file is too big to display

+1
-1
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

{
"name": "@angular/core",
"version": "22.0.0-rc.0",
"version": "22.0.0-rc.1",
"description": "Angular - the core framework",

@@ -53,3 +53,3 @@ "author": "angular",

"peerDependencies": {
"@angular/compiler": "22.0.0-rc.0",
"@angular/compiler": "22.0.0-rc.1",
"rxjs": "^6.5.3 || ^7.4.0",

@@ -56,0 +56,0 @@ "zone.js": "~0.15.0 || ~0.16.0"

@@ -29,3 +29,2 @@ You are an expert in TypeScript, Angular, and scalable web application development. You write functional, maintainable, performant, and accessible code following Angular and TypeScript best practices.

- Use `computed()` for derived state
- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
- Prefer inline templates for small components

@@ -32,0 +31,0 @@ - Prefer Reactive forms instead of Template-driven ones

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -16,3 +16,3 @@ * License: MIT

var apply_import_manager = require('./apply_import_manager-CxA_YYgB.cjs');
var index = require('./index-DxFMpcXS.cjs');
var index = require('./index-B07c0BtS.cjs');
require('@angular-devkit/core');

@@ -515,3 +515,3 @@ require('node:path/posix');

const eventEmitterType = getEventEmitterArgumentType(propertyDeclaration);
if (!eventEmitterType)
if (!eventEmitterType || eventEmitterType === 'void')
return;

@@ -518,0 +518,0 @@ const id = getUniqueIdForProperty(info, propertyDeclaration);

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -15,5 +15,5 @@ * License: MIT

var apply_import_manager = require('./apply_import_manager-CxA_YYgB.cjs');
var migrate_ts_type_references = require('./migrate_ts_type_references-DMv-GSuu.cjs');
var migrate_ts_type_references = require('./migrate_ts_type_references-Bk_jxVNa.cjs');
var assert = require('assert');
var index = require('./index-DxFMpcXS.cjs');
var index = require('./index-B07c0BtS.cjs');
var compiler = require('@angular/compiler');

@@ -20,0 +20,0 @@ require('@angular-devkit/core');

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -22,5 +22,5 @@ * License: MIT

require('./apply_import_manager-CxA_YYgB.cjs');
require('./migrate_ts_type_references-DMv-GSuu.cjs');
require('./migrate_ts_type_references-Bk_jxVNa.cjs');
require('assert');
require('./index-DxFMpcXS.cjs');
require('./index-B07c0BtS.cjs');
require('@angular/compiler');

@@ -27,0 +27,0 @@ require('./leading_space-BTPRV0wu.cjs');

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -5,0 +5,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

/**
* @license Angular v22.0.0-rc.0
* @license Angular v22.0.0-rc.1
* (c) 2010-2026 Google LLC. https://angular.dev/

@@ -4,0 +4,0 @@ * License: MIT

'use strict';
/**
* @license Angular v22.0.0-rc.0
* (c) 2010-2026 Google LLC. https://angular.dev/
* License: MIT
*/
'use strict';
var ts = require('typescript');
var compilerCli = require('@angular/compiler-cli');
var migrations = require('@angular/compiler-cli/private/migrations');
require('node:path');
var project_paths = require('./project_paths-D2V-Uh2L.cjs');
var compiler = require('@angular/compiler');
function getMemberName(member) {
if (member.name === undefined) {
return null;
}
if (ts.isIdentifier(member.name) || ts.isStringLiteralLike(member.name)) {
return member.name.text;
}
if (ts.isPrivateIdentifier(member.name)) {
return `#${member.name.text}`;
}
return null;
}
/** Checks whether the given node can be an `@Input()` declaration node. */
function isInputContainerNode(node) {
return (((ts.isAccessor(node) && ts.isClassDeclaration(node.parent)) ||
ts.isPropertyDeclaration(node)) &&
getMemberName(node) !== null);
}
/**
* Detects `query(By.directive(T)).componentInstance` patterns and enhances
* them with information of `T`. This is important because `.componentInstance`
* is currently typed as `any` and may cause runtime test failures after input
* migrations then.
*
* The reference resolution pass leverages information from this pattern
* recognizer.
*/
class DebugElementComponentInstance {
checker;
cache = new WeakMap();
constructor(checker) {
this.checker = checker;
}
detect(node) {
if (this.cache.has(node)) {
return this.cache.get(node);
}
if (!ts.isPropertyAccessExpression(node)) {
return null;
}
// Check for `<>.componentInstance`.
if (!ts.isIdentifier(node.name) || node.name.text !== 'componentInstance') {
return null;
}
// Check for `<>.query(..).<>`.
if (!ts.isCallExpression(node.expression) ||
!ts.isPropertyAccessExpression(node.expression.expression) ||
!ts.isIdentifier(node.expression.expression.name) ||
node.expression.expression.name.text !== 'query') {
return null;
}
const queryCall = node.expression;
if (queryCall.arguments.length !== 1) {
return null;
}
const queryArg = queryCall.arguments[0];
let typeExpr;
if (ts.isCallExpression(queryArg) &&
queryArg.arguments.length === 1 &&
ts.isIdentifier(queryArg.arguments[0])) {
// Detect references, like: `query(By.directive(T))`.
typeExpr = queryArg.arguments[0];
}
else if (ts.isIdentifier(queryArg)) {
// Detect references, like: `harness.query(T)`.
typeExpr = queryArg;
}
else {
return null;
}
const symbol = this.checker.getSymbolAtLocation(typeExpr);
if (symbol?.valueDeclaration === undefined ||
!ts.isClassDeclaration(symbol?.valueDeclaration)) {
// Cache this as we use the expensive type checker.
this.cache.set(node, null);
return null;
}
const type = this.checker.getTypeAtLocation(symbol.valueDeclaration);
this.cache.set(node, type);
return type;
}
}
/**
* Recognizes `Partial<T>` instances in Catalyst tests. Those type queries
* are likely used for typing property initialization values for the given class `T`
* and we have a few scenarios:
*
* 1. The API does not unwrap signal inputs. In which case, the values are likely no
* longer assignable to an `InputSignal`.
* 2. The API does unwrap signal inputs, in which case we need to unwrap the `Partial`
* because the values are raw initial values, like they were before.
*
* We can enable this heuristic when we detect Catalyst as we know it supports unwrapping.
*/
class PartialDirectiveTypeInCatalystTests {
checker;
knownFields;
constructor(checker, knownFields) {
this.checker = checker;
this.knownFields = knownFields;
}
detect(node) {
// Detect `Partial<...>`
if (!ts.isTypeReferenceNode(node) ||
!ts.isIdentifier(node.typeName) ||
node.typeName.text !== 'Partial') {
return null;
}
// Ignore if the source file doesn't reference Catalyst.
if (!node.getSourceFile().text.includes('angular2/testing/catalyst')) {
return null;
}
// Extract T of `Partial<T>`.
const cmpTypeArg = node.typeArguments?.[0];
if (!cmpTypeArg ||
!ts.isTypeReferenceNode(cmpTypeArg) ||
!ts.isIdentifier(cmpTypeArg.typeName)) {
return null;
}
const cmpType = cmpTypeArg.typeName;
const symbol = this.checker.getSymbolAtLocation(cmpType);
// Note: Technically the class might be derived of an input-containing class,
// but this is out of scope for now. We can expand if we see it's a common case.
if (symbol?.valueDeclaration === undefined ||
!ts.isClassDeclaration(symbol.valueDeclaration) ||
!this.knownFields.shouldTrackClassReference(symbol.valueDeclaration)) {
return null;
}
return { referenceNode: node, targetClass: symbol.valueDeclaration };
}
}
/**
* Attempts to look up the given property access chain using
* the type checker.
*
* Notably this is not as safe as using the type checker directly to
* retrieve symbols of a given identifier, but in some cases this is
* a necessary approach to compensate e.g. for a lack of TCB information
* when processing Angular templates.
*
* The path is a list of properties to be accessed sequentially on the
* given type.
*/
function lookupPropertyAccess(checker, type, path, options = {}) {
let symbol = null;
for (const propName of path) {
// Note: We support assuming `NonNullable` for the pathl This is necessary
// in some situations as otherwise the lookups would fail to resolve the target
// symbol just because of e.g. a ternary. This is used in the signal input migration
// for host bindings.
type = options.ignoreNullability ? type.getNonNullableType() : type;
const propSymbol = type.getProperty(propName);
if (propSymbol === undefined) {
return null;
}
symbol = propSymbol;
type = checker.getTypeOfSymbol(propSymbol);
}
if (symbol === null) {
return null;
}
return { symbol, type };
}
/**
* AST visitor that iterates through a template and finds all
* input references.
*
* This resolution is important to be able to migrate references to inputs
* that will be migrated to signal inputs.
*/
class TemplateReferenceVisitor extends compiler.TmplAstRecursiveVisitor {
result = [];
/**
* Whether we are currently descending into HTML AST nodes
* where all bound attributes are considered potentially narrowing.
*
* Keeps track of all referenced inputs in such attribute expressions.
*/
templateAttributeReferencedFields = null;
expressionVisitor;
seenKnownFieldsCount = new Map();
constructor(typeChecker, templateTypeChecker, componentClass, knownFields, fieldNamesToConsiderForReferenceLookup) {
super();
this.expressionVisitor = new TemplateExpressionReferenceVisitor(typeChecker, templateTypeChecker, componentClass, knownFields, fieldNamesToConsiderForReferenceLookup);
}
checkExpressionForReferencedFields(activeNode, expressionNode) {
const referencedFields = this.expressionVisitor.checkTemplateExpression(activeNode, expressionNode);
// Add all references to the overall visitor result.
this.result.push(...referencedFields);
// Count usages of seen input references. We'll use this to make decisions
// based on whether inputs are potentially narrowed or not.
for (const input of referencedFields) {
this.seenKnownFieldsCount.set(input.targetField.key, (this.seenKnownFieldsCount.get(input.targetField.key) ?? 0) + 1);
}
return referencedFields;
}
descendAndCheckForNarrowedSimilarReferences(potentiallyNarrowedInputs, descend) {
const inputs = potentiallyNarrowedInputs.map((i) => ({
ref: i,
key: i.targetField.key,
pastCount: this.seenKnownFieldsCount.get(i.targetField.key) ?? 0,
}));
descend();
for (const input of inputs) {
// Input was referenced inside a narrowable spot, and is used in child nodes.
// This is a sign for the input to be narrowed. Mark it as such.
if ((this.seenKnownFieldsCount.get(input.key) ?? 0) > input.pastCount) {
input.ref.isLikelyNarrowed = true;
}
}
}
visitTemplate(template) {
// Note: We assume all bound expressions for templates may be subject
// to TCB narrowing. This is relevant for now until we support narrowing
// of signal calls in templates.
// TODO: Remove with: https://github.com/angular/angular/pull/55456.
this.templateAttributeReferencedFields = [];
compiler.tmplAstVisitAll(this, template.attributes);
compiler.tmplAstVisitAll(this, template.templateAttrs);
// If we are dealing with a microsyntax template, do not check
// inputs and outputs as those are already passed to the children.
// Template attributes may contain relevant expressions though.
if (template.tagName === 'ng-template') {
compiler.tmplAstVisitAll(this, template.inputs);
compiler.tmplAstVisitAll(this, template.outputs);
}
const referencedInputs = this.templateAttributeReferencedFields;
this.templateAttributeReferencedFields = null;
this.descendAndCheckForNarrowedSimilarReferences(referencedInputs, () => {
compiler.tmplAstVisitAll(this, template.children);
compiler.tmplAstVisitAll(this, template.references);
compiler.tmplAstVisitAll(this, template.variables);
});
}
visitIfBlockBranch(block) {
if (block.expression) {
const referencedFields = this.checkExpressionForReferencedFields(block, block.expression);
this.descendAndCheckForNarrowedSimilarReferences(referencedFields, () => {
super.visitIfBlockBranch(block);
});
}
else {
super.visitIfBlockBranch(block);
}
}
visitForLoopBlock(block) {
this.checkExpressionForReferencedFields(block, block.expression);
if (block.trackBy !== null) {
this.checkExpressionForReferencedFields(block, block.trackBy);
}
super.visitForLoopBlock(block);
}
visitSwitchBlock(block) {
const referencedFields = this.checkExpressionForReferencedFields(block, block.expression);
this.descendAndCheckForNarrowedSimilarReferences(referencedFields, () => {
super.visitSwitchBlock(block);
});
}
visitSwitchBlockCase(block) {
if (block.expression) {
const referencedFields = this.checkExpressionForReferencedFields(block, block.expression);
this.descendAndCheckForNarrowedSimilarReferences(referencedFields, () => {
super.visitSwitchBlockCase(block);
});
}
else {
super.visitSwitchBlockCase(block);
}
}
visitDeferredBlock(deferred) {
if (deferred.triggers.when) {
this.checkExpressionForReferencedFields(deferred, deferred.triggers.when.value);
}
if (deferred.prefetchTriggers.when) {
this.checkExpressionForReferencedFields(deferred, deferred.prefetchTriggers.when.value);
}
super.visitDeferredBlock(deferred);
}
visitBoundText(text) {
this.checkExpressionForReferencedFields(text, text.value);
}
visitBoundEvent(attribute) {
this.checkExpressionForReferencedFields(attribute, attribute.handler);
}
visitBoundAttribute(attribute) {
const referencedFields = this.checkExpressionForReferencedFields(attribute, attribute.value);
// Attributes inside templates are potentially "narrowed" and hence we
// keep track of all referenced inputs to see if they actually are.
if (this.templateAttributeReferencedFields !== null) {
this.templateAttributeReferencedFields.push(...referencedFields);
}
}
visitLetDeclaration(decl) {
this.checkExpressionForReferencedFields(decl, decl.value);
}
}
/**
* Expression AST visitor that checks whether a given expression references
* a known `@Input()`.
*
* This resolution is important to be able to migrate references to inputs
* that will be migrated to signal inputs.
*/
class TemplateExpressionReferenceVisitor extends compiler.RecursiveAstVisitor {
typeChecker;
templateTypeChecker;
componentClass;
knownFields;
fieldNamesToConsiderForReferenceLookup;
activeTmplAstNode = null;
detectedInputReferences = [];
isInsideObjectShorthandExpression = false;
insideConditionalExpressionsWithReads = [];
constructor(typeChecker, templateTypeChecker, componentClass, knownFields, fieldNamesToConsiderForReferenceLookup) {
super();
this.typeChecker = typeChecker;
this.templateTypeChecker = templateTypeChecker;
this.componentClass = componentClass;
this.knownFields = knownFields;
this.fieldNamesToConsiderForReferenceLookup = fieldNamesToConsiderForReferenceLookup;
}
/** Checks the given AST expression. */
checkTemplateExpression(activeNode, expressionNode) {
this.detectedInputReferences = [];
this.activeTmplAstNode = activeNode;
expressionNode.visit(this, []);
return this.detectedInputReferences;
}
visit(ast, context) {
super.visit(ast, [...context, ast]);
}
// Keep track when we are inside an object shorthand expression. This is
// necessary as we need to expand the shorthand to invoke a potential new signal.
// E.g. `{bla}` may be transformed to `{bla: bla()}`.
visitLiteralMap(ast, context) {
for (const [idx, key] of ast.keys.entries()) {
this.isInsideObjectShorthandExpression =
key.kind === 'property' && !!key.isShorthandInitialized;
ast.values[idx].visit(this, context);
this.isInsideObjectShorthandExpression = false;
}
}
visitPropertyRead(ast, context) {
this._inspectPropertyAccess(ast, false, context);
super.visitPropertyRead(ast, context);
}
visitSafePropertyRead(ast, context) {
this._inspectPropertyAccess(ast, false, context);
super.visitPropertyRead(ast, context);
}
visitBinary(ast, context) {
if (ast.operation === '=' && ast.left instanceof compiler.PropertyRead) {
this._inspectPropertyAccess(ast.left, true, [...context, ast, ast.left]);
}
else {
super.visitBinary(ast, context);
}
}
visitConditional(ast, context) {
this.visit(ast.condition, context);
this.insideConditionalExpressionsWithReads.push(ast.condition);
this.visit(ast.trueExp, context);
this.visit(ast.falseExp, context);
this.insideConditionalExpressionsWithReads.pop();
}
/**
* Inspects the property access and attempts to resolve whether they access
* a known field. If so, the result is captured.
*/
_inspectPropertyAccess(ast, isAssignment, astPath) {
if (this.fieldNamesToConsiderForReferenceLookup !== null &&
!this.fieldNamesToConsiderForReferenceLookup.has(ast.name)) {
return;
}
const isWrite = !!(isAssignment ||
(this.activeTmplAstNode && isTwoWayBindingNode(this.activeTmplAstNode)));
this._checkAccessViaTemplateTypeCheckBlock(ast, isWrite, astPath) ||
this._checkAccessViaOwningComponentClassType(ast, isWrite, astPath);
}
/**
* Checks whether the node refers to an input using the TCB information.
* Type check block may not exist for e.g. test components, so this can return `null`.
*/
_checkAccessViaTemplateTypeCheckBlock(ast, isWrite, astPath) {
// There might be no template type checker. E.g. if we check host bindings.
if (this.templateTypeChecker === null) {
return false;
}
const symbol = this.templateTypeChecker.getSymbolOfNode(ast, this.componentClass);
if (symbol?.kind !== migrations.SymbolKind.Expression) {
return false;
}
const tsSymbol = this.templateTypeChecker.getTsSymbolOfSymbol(symbol);
if (tsSymbol === null) {
return false;
}
// Dangerous: Type checking symbol retrieval is a totally different `ts.Program`,
// than the one where we analyzed `knownInputs`.
// --> Find the input via its input id.
const targetInput = this.knownFields.attemptRetrieveDescriptorFromSymbol(tsSymbol);
if (targetInput === null) {
return false;
}
this.detectedInputReferences.push({
targetNode: targetInput.node,
targetField: targetInput,
read: ast,
readAstPath: astPath,
context: this.activeTmplAstNode,
isLikelyNarrowed: this._isPartOfNarrowingTernary(ast),
isObjectShorthandExpression: this.isInsideObjectShorthandExpression,
isWrite,
});
return true;
}
/**
* Simple resolution checking whether the given AST refers to a known input.
* This is a fallback for when there is no type checking information (e.g. in host bindings).
*
* It attempts to resolve references by traversing accesses of the "component class" type.
* e.g. `this.bla` is resolved via `CompType#bla` and further.
*/
_checkAccessViaOwningComponentClassType(ast, isWrite, astPath) {
// We might check host bindings, which can never point to template variables or local refs.
const expressionTemplateTarget = this.templateTypeChecker === null
? null
: this.templateTypeChecker.getExpressionTarget(ast, this.componentClass);
// Skip checking if:
// - the reference resolves to a template variable or local ref. No way to resolve without TCB.
// - the owning component does not have a name (should not happen technically).
if (expressionTemplateTarget !== null || this.componentClass.name === undefined) {
return;
}
const property = traverseReceiverAndLookupSymbol(ast, this.componentClass, this.typeChecker);
if (property === null) {
return;
}
const matchingTarget = this.knownFields.attemptRetrieveDescriptorFromSymbol(property);
if (matchingTarget === null) {
return;
}
this.detectedInputReferences.push({
targetNode: matchingTarget.node,
targetField: matchingTarget,
read: ast,
readAstPath: astPath,
context: this.activeTmplAstNode,
isLikelyNarrowed: this._isPartOfNarrowingTernary(ast),
isObjectShorthandExpression: this.isInsideObjectShorthandExpression,
isWrite,
});
}
_isPartOfNarrowingTernary(read) {
// Note: We do not safe check that the reads are fully matching 1:1. This is acceptable
// as worst case we just skip an input from being migrated. This is very unlikely too.
return this.insideConditionalExpressionsWithReads.some((r) => (r instanceof compiler.PropertyRead || r instanceof compiler.SafePropertyRead) && r.name === read.name);
}
}
/**
* Emulates an access to a given field using the TypeScript `ts.Type`
* of the given class. The resolved symbol of the access is returned.
*/
function traverseReceiverAndLookupSymbol(readOrWrite, componentClass, checker) {
const path = [readOrWrite.name];
let node = readOrWrite;
while (node.receiver instanceof compiler.PropertyRead) {
node = node.receiver;
path.unshift(node.name);
}
if (!(node.receiver instanceof compiler.ImplicitReceiver)) {
return null;
}
const classType = checker.getTypeAtLocation(componentClass.name);
return (lookupPropertyAccess(checker, classType, path, {
// Necessary to avoid breaking the resolution if there is
// some narrowing involved. E.g. `myClass ? myClass.input`.
ignoreNullability: true,
})?.symbol ?? null);
}
/** Whether the given node refers to a two-way binding AST node. */
function isTwoWayBindingNode(node) {
return ((node instanceof compiler.TmplAstBoundAttribute && node.type === compiler.BindingType.TwoWay) ||
(node instanceof compiler.TmplAstBoundEvent && node.type === compiler.ParsedEventType.TwoWay));
}
/** Possible types of references to known fields detected. */
exports.ReferenceKind = void 0;
(function (ReferenceKind) {
ReferenceKind[ReferenceKind["InTemplate"] = 0] = "InTemplate";
ReferenceKind[ReferenceKind["InHostBinding"] = 1] = "InHostBinding";
ReferenceKind[ReferenceKind["TsReference"] = 2] = "TsReference";
ReferenceKind[ReferenceKind["TsClassTypeReference"] = 3] = "TsClassTypeReference";
})(exports.ReferenceKind || (exports.ReferenceKind = {}));
/** Whether the given reference is a TypeScript reference. */
function isTsReference(ref) {
return ref.kind === exports.ReferenceKind.TsReference;
}
/** Whether the given reference is a template reference. */
function isTemplateReference(ref) {
return ref.kind === exports.ReferenceKind.InTemplate;
}
/** Whether the given reference is a host binding reference. */
function isHostBindingReference(ref) {
return ref.kind === exports.ReferenceKind.InHostBinding;
}
/**
* Whether the given reference is a TypeScript `ts.Type` reference
* to a class containing known fields.
*/
function isTsClassTypeReference(ref) {
return ref.kind === exports.ReferenceKind.TsClassTypeReference;
}
/**
* Checks host bindings of the given class and tracks all
* references to inputs within bindings.
*/
function identifyHostBindingReferences(node, programInfo, checker, reflector, result, knownFields, fieldNamesToConsiderForReferenceLookup) {
if (node.name === undefined) {
return;
}
const decorators = reflector.getDecoratorsOfDeclaration(node);
if (decorators === null) {
return;
}
const angularDecorators = migrations.getAngularDecorators(decorators, ['Directive', 'Component'],
/* isAngularCore */ false);
if (angularDecorators.length === 0) {
return;
}
// Assume only one Angular decorator per class.
const ngDecorator = angularDecorators[0];
if (ngDecorator.args?.length !== 1) {
return;
}
const metadataNode = migrations.unwrapExpression(ngDecorator.args[0]);
if (!ts.isObjectLiteralExpression(metadataNode)) {
return;
}
const metadata = migrations.reflectObjectLiteral(metadataNode);
if (!metadata.has('host')) {
return;
}
let hostField = migrations.unwrapExpression(metadata.get('host'));
// Special-case in case host bindings are shared via a variable.
// e.g. Material button shares host bindings as a constant in the same target.
if (ts.isIdentifier(hostField)) {
let symbol = checker.getSymbolAtLocation(hostField);
// Plain identifier references can point to alias symbols (e.g. imports).
if (symbol !== undefined && symbol.flags & ts.SymbolFlags.Alias) {
symbol = checker.getAliasedSymbol(symbol);
}
if (symbol !== undefined &&
symbol.valueDeclaration !== undefined &&
ts.isVariableDeclaration(symbol.valueDeclaration)) {
hostField = symbol?.valueDeclaration.initializer;
}
}
if (hostField === undefined || !ts.isObjectLiteralExpression(hostField)) {
return;
}
const hostMap = migrations.reflectObjectLiteral(hostField);
const expressionResult = [];
const expressionVisitor = new TemplateExpressionReferenceVisitor(checker, null, node, knownFields, fieldNamesToConsiderForReferenceLookup);
for (const [rawName, expression] of hostMap.entries()) {
if (!ts.isStringLiteralLike(expression)) {
continue;
}
const isEventBinding = rawName.startsWith('(');
const isPropertyBinding = rawName.startsWith('[');
// Only migrate property or event bindings.
if (!isPropertyBinding && !isEventBinding) {
continue;
}
const parser = compiler.makeBindingParser();
const sourceSpan = new compiler.ParseSourceSpan(
// Fake source span to keep parsing offsets zero-based.
// We then later combine these with the expression TS node offsets.
new compiler.ParseLocation({ content: '', url: '' }, 0, 0, 0), new compiler.ParseLocation({ content: '', url: '' }, 0, 0, 0));
const name = rawName.substring(1, rawName.length - 1);
let parsed = undefined;
if (isEventBinding) {
const result = [];
parser.parseEvent(name.substring(1, name.length - 1), expression.text, false, sourceSpan, sourceSpan, [], result, sourceSpan);
parsed = result[0].handler;
}
else {
const result = [];
parser.parsePropertyBinding(name, expression.text, true,
/* isTwoWayBinding */ false, sourceSpan, 0, sourceSpan, [], result, sourceSpan);
parsed = result[0].expression;
}
if (parsed != null) {
expressionResult.push(...expressionVisitor.checkTemplateExpression(expression, parsed));
}
}
for (const ref of expressionResult) {
result.references.push({
kind: exports.ReferenceKind.InHostBinding,
from: {
read: ref.read,
readAstPath: ref.readAstPath,
isObjectShorthandExpression: ref.isObjectShorthandExpression,
isWrite: ref.isWrite,
file: project_paths.projectFile(ref.context.getSourceFile(), programInfo),
hostPropertyNode: ref.context,
},
target: ref.targetField,
});
}
}
/**
* Attempts to extract the `TemplateDefinition` for the given
* class, if possible.
*
* The definition can then be used with the Angular compiler to
* load/parse the given template.
*/
function attemptExtractTemplateDefinition(node, checker, reflector, resourceLoader) {
const classDecorators = reflector.getDecoratorsOfDeclaration(node);
const evaluator = new migrations.PartialEvaluator(reflector, checker, null);
const ngDecorators = classDecorators !== null
? migrations.getAngularDecorators(classDecorators, ['Component'], /* isAngularCore */ false)
: [];
if (ngDecorators.length === 0 ||
ngDecorators[0].args === null ||
ngDecorators[0].args.length === 0 ||
!ts.isObjectLiteralExpression(ngDecorators[0].args[0])) {
return null;
}
const properties = migrations.reflectObjectLiteral(ngDecorators[0].args[0]);
const templateProp = properties.get('template');
const templateUrlProp = properties.get('templateUrl');
const containingFile = node.getSourceFile().fileName;
// inline template.
if (templateProp !== undefined) {
const templateStr = evaluator.evaluate(templateProp);
if (typeof templateStr === 'string') {
return {
isInline: true,
expression: templateProp,
preserveWhitespaces: false,
resolvedTemplateUrl: containingFile,
templateUrl: containingFile,
};
}
}
try {
// external template.
if (templateUrlProp !== undefined) {
const templateUrl = evaluator.evaluate(templateUrlProp);
if (typeof templateUrl === 'string') {
return {
isInline: false,
preserveWhitespaces: false,
templateUrlExpression: templateUrlProp,
templateUrl,
resolvedTemplateUrl: resourceLoader.resolve(templateUrl, containingFile),
};
}
}
}
catch (e) {
console.error(`Could not parse external template: ${e}`);
}
return null;
}
/**
* Checks whether the given class has an Angular template, and resolves
* all of the references to inputs.
*/
function identifyTemplateReferences(programInfo, node, reflector, checker, evaluator, templateTypeChecker, resourceLoader, options, result, knownFields, fieldNamesToConsiderForReferenceLookup) {
const template = templateTypeChecker.getTemplate(node, compilerCli.OptimizeFor.WholeProgram) ??
// If there is no template registered in the TCB or compiler, the template may
// be skipped due to an explicit `jit: true` setting. We try to detect this case
// and parse the template manually.
extractTemplateWithoutCompilerAnalysis(node, checker, reflector, resourceLoader, evaluator, options);
if (template !== null) {
const visitor = new TemplateReferenceVisitor(checker, templateTypeChecker, node, knownFields, fieldNamesToConsiderForReferenceLookup);
template.forEach((node) => node.visit(visitor));
for (const res of visitor.result) {
const templateFilePath = res.context.sourceSpan.start.file.url;
// Templates without an URL are non-mappable artifacts of e.g.
// string concatenated templates. See the `indirect` template
// source mapping concept in the compiler. We skip such references
// as those cannot be migrated, but print an error for now.
if (templateFilePath === '') {
// TODO: Incorporate a TODO potentially.
console.error(`Found reference to field ${res.targetField.key} that cannot be ` +
`migrated because the template cannot be parsed with source map information ` +
`(in file: ${node.getSourceFile().fileName}).`);
continue;
}
result.references.push({
kind: exports.ReferenceKind.InTemplate,
from: {
read: res.read,
readAstPath: res.readAstPath,
node: res.context,
isObjectShorthandExpression: res.isObjectShorthandExpression,
originatingTsFile: project_paths.projectFile(node.getSourceFile(), programInfo),
templateFile: project_paths.projectFile(compilerCli.absoluteFrom(templateFilePath), programInfo),
isLikelyPartOfNarrowing: res.isLikelyNarrowed,
isWrite: res.isWrite,
},
target: res.targetField,
});
}
}
}
/**
* Attempts to extract a `@Component` template from the given class,
* without relying on the `NgCompiler` program analysis.
*
* This is useful for JIT components using `jit: true` which were not
* processed by the Angular compiler, but may still have templates that
* contain references to inputs that we can resolve via the fallback
* reference resolutions (that does not use the type check block).
*/
function extractTemplateWithoutCompilerAnalysis(node, checker, reflector, resourceLoader, evaluator, options) {
if (node.name === undefined) {
return null;
}
const tmplDef = attemptExtractTemplateDefinition(node, checker, reflector, resourceLoader);
if (tmplDef === null) {
return null;
}
return migrations.extractTemplate(node, tmplDef, evaluator, null, resourceLoader, {
enableBlockSyntax: true,
enableLetSyntax: true,
usePoisonedData: true,
enableI18nLegacyMessageIdFormat: options.enableI18nLegacyMessageIdFormat !== false,
i18nNormalizeLineEndingsInICUs: options.i18nNormalizeLineEndingsInICUs === true,
enableSelectorless: false,
}, migrations.CompilationMode.FULL).nodes;
}
/** Gets the pattern and property name for a given binding element. */
function resolveBindingElement(node) {
const name = node.propertyName ?? node.name;
// If we are discovering a non-analyzable element in the path, abort.
if (!ts.isStringLiteralLike(name) && !ts.isIdentifier(name)) {
return null;
}
return {
pattern: node.parent,
propertyName: name.text,
};
}
/** Gets the declaration node of the given binding element. */
function getBindingElementDeclaration(node) {
while (true) {
if (ts.isBindingElement(node.parent.parent)) {
node = node.parent.parent;
}
else {
return node.parent.parent;
}
}
}
/**
* Expands the given reference to its containing expression, capturing
* the full context.
*
* E.g. `traverseAccess(ref<`bla`>)` may return `this.bla`
* or `traverseAccess(ref<`bla`>)` may return `this.someObj.a.b.c.bla`.
*
* This helper is useful as we will replace the full access with a temporary
* variable for narrowing. Replacing just the identifier is wrong.
*/
function traverseAccess(access) {
if (ts.isPropertyAccessExpression(access.parent) && access.parent.name === access) {
return access.parent;
}
else if (ts.isElementAccessExpression(access.parent) &&
access.parent.argumentExpression === access) {
return access.parent;
}
return access;
}
/**
* Unwraps the parent of the given node, if it's a
* parenthesized expression or `as` expression.
*/
function unwrapParent(node) {
if (ts.isParenthesizedExpression(node.parent)) {
return unwrapParent(node.parent);
}
else if (ts.isAsExpression(node.parent)) {
return unwrapParent(node.parent);
}
return node;
}
/**
* List of binary operators that indicate a write operation.
*
* Useful for figuring out whether an expression assigns to
* something or not.
*/
const writeBinaryOperators = [
ts.SyntaxKind.EqualsToken,
ts.SyntaxKind.BarBarEqualsToken,
ts.SyntaxKind.BarEqualsToken,
ts.SyntaxKind.AmpersandEqualsToken,
ts.SyntaxKind.AmpersandAmpersandEqualsToken,
ts.SyntaxKind.SlashEqualsToken,
ts.SyntaxKind.MinusEqualsToken,
ts.SyntaxKind.PlusEqualsToken,
ts.SyntaxKind.CaretEqualsToken,
ts.SyntaxKind.PercentEqualsToken,
ts.SyntaxKind.AsteriskEqualsToken,
ts.SyntaxKind.ExclamationEqualsToken,
];
/**
* Checks whether given TypeScript reference refers to an Angular input, and captures
* the reference if possible.
*
* @param fieldNamesToConsiderForReferenceLookup List of field names that should be
* respected when expensively looking up references to known fields.
* May be null if all identifiers should be inspected.
*/
function identifyPotentialTypeScriptReference(node, programInfo, checker, knownFields, result, fieldNamesToConsiderForReferenceLookup, advisors) {
// Skip all identifiers that never can point to a migrated field.
// TODO: Capture these assumptions and performance optimizations in the design doc.
if (fieldNamesToConsiderForReferenceLookup !== null &&
!fieldNamesToConsiderForReferenceLookup.has(node.text)) {
return;
}
let target = undefined;
try {
// Resolve binding elements to their declaration symbol.
// Commonly inputs are accessed via object expansion. e.g. `const {input} = this;`.
if (ts.isBindingElement(node.parent)) {
// Skip binding elements that are using spread.
if (node.parent.dotDotDotToken !== undefined) {
return;
}
const bindingInfo = resolveBindingElement(node.parent);
if (bindingInfo === null) {
// The declaration could not be resolved. Skip analyzing this.
return;
}
const bindingType = checker.getTypeAtLocation(bindingInfo.pattern);
const resolved = lookupPropertyAccess(checker, bindingType, [bindingInfo.propertyName]);
target = resolved?.symbol;
}
else {
target = checker.getSymbolAtLocation(node);
}
}
catch (e) {
console.error('Unexpected error while trying to resolve identifier reference:');
console.error(e);
// Gracefully skip analyzing. This can happen when e.g. a reference is named similar
// to an input, but is dependant on `.d.ts` that is not necessarily available (clutz dts).
return;
}
noTargetSymbolCheck: if (target === undefined) {
if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
const propAccessSymbol = checker.getSymbolAtLocation(node.parent.expression);
if (propAccessSymbol !== undefined &&
propAccessSymbol.valueDeclaration !== undefined &&
ts.isVariableDeclaration(propAccessSymbol.valueDeclaration) &&
propAccessSymbol.valueDeclaration.initializer !== undefined) {
target = advisors.debugElComponentInstanceTracker
.detect(propAccessSymbol.valueDeclaration.initializer)
?.getProperty(node.text);
// We found a target in the fallback path. Break out.
if (target !== undefined) {
break noTargetSymbolCheck;
}
}
}
return;
}
let targetInput = knownFields.attemptRetrieveDescriptorFromSymbol(target);
if (targetInput === null) {
return;
}
const access = unwrapParent(traverseAccess(node));
const accessParent = access.parent;
const isWriteReference = ts.isBinaryExpression(accessParent) &&
accessParent.left === access &&
writeBinaryOperators.includes(accessParent.operatorToken.kind);
// track accesses from source files to known fields.
result.references.push({
kind: exports.ReferenceKind.TsReference,
from: {
node,
file: project_paths.projectFile(node.getSourceFile(), programInfo),
isWrite: isWriteReference,
isPartOfElementBinding: ts.isBindingElement(node.parent),
},
target: targetInput,
});
}
/**
* Phase where we iterate through all source file references and
* detect references to known fields (e.g. commonly inputs).
*
* This is useful, for example in the signal input migration whe
* references need to be migrated to unwrap signals, given that
* their target properties is no longer holding a raw value, but
* instead an `InputSignal`.
*
* This phase detects references in all types of locations:
* - TS source files
* - Angular templates (inline or external)
* - Host binding expressions.
*/
function createFindAllSourceFileReferencesVisitor(programInfo, checker, reflector, resourceLoader, evaluator, templateTypeChecker, knownFields, fieldNamesToConsiderForReferenceLookup, result) {
const debugElComponentInstanceTracker = new DebugElementComponentInstance(checker);
const partialDirectiveCatalystTracker = new PartialDirectiveTypeInCatalystTests(checker, knownFields);
const perfCounters = {
template: 0,
hostBindings: 0,
tsReferences: 0,
tsTypes: 0,
};
// Schematic NodeJS execution may not have `global.performance` defined.
const currentTimeInMs = () => typeof global.performance !== 'undefined' ? global.performance.now() : Date.now();
const visitor = (node) => {
let lastTime = currentTimeInMs();
// Note: If there is no template type checker and resource loader, we aren't processing
// an Angular program, and can skip template detection.
if (ts.isClassDeclaration(node) && templateTypeChecker !== null && resourceLoader !== null) {
identifyTemplateReferences(programInfo, node, reflector, checker, evaluator, templateTypeChecker, resourceLoader, programInfo.userOptions, result, knownFields, fieldNamesToConsiderForReferenceLookup);
perfCounters.template += (currentTimeInMs() - lastTime) / 1000;
lastTime = currentTimeInMs();
identifyHostBindingReferences(node, programInfo, checker, reflector, result, knownFields, fieldNamesToConsiderForReferenceLookup);
perfCounters.hostBindings += (currentTimeInMs() - lastTime) / 1000;
lastTime = currentTimeInMs();
}
lastTime = currentTimeInMs();
// find references, but do not capture input declarations itself.
if (ts.isIdentifier(node) &&
!(isInputContainerNode(node.parent) && node.parent.name === node)) {
identifyPotentialTypeScriptReference(node, programInfo, checker, knownFields, result, fieldNamesToConsiderForReferenceLookup, {
debugElComponentInstanceTracker,
});
}
perfCounters.tsReferences += (currentTimeInMs() - lastTime) / 1000;
lastTime = currentTimeInMs();
// Detect `Partial<T>` references.
// Those are relevant to be tracked as they may be updated in Catalyst to
// unwrap signal inputs. Commonly people use `Partial` in Catalyst to type
// some "component initial values".
const partialDirectiveInCatalyst = partialDirectiveCatalystTracker.detect(node);
if (partialDirectiveInCatalyst !== null) {
result.references.push({
kind: exports.ReferenceKind.TsClassTypeReference,
from: {
file: project_paths.projectFile(partialDirectiveInCatalyst.referenceNode.getSourceFile(), programInfo),
node: partialDirectiveInCatalyst.referenceNode,
},
isPartialReference: true,
isPartOfCatalystFile: true,
target: partialDirectiveInCatalyst.targetClass,
});
}
perfCounters.tsTypes += (currentTimeInMs() - lastTime) / 1000;
};
return {
visitor,
debugPrintMetrics: () => {
console.info('Source file analysis performance', perfCounters);
},
};
}
exports.createFindAllSourceFileReferencesVisitor = createFindAllSourceFileReferencesVisitor;
exports.getBindingElementDeclaration = getBindingElementDeclaration;
exports.getMemberName = getMemberName;
exports.isHostBindingReference = isHostBindingReference;
exports.isInputContainerNode = isInputContainerNode;
exports.isTemplateReference = isTemplateReference;
exports.isTsClassTypeReference = isTsClassTypeReference;
exports.isTsReference = isTsReference;
exports.traverseAccess = traverseAccess;
exports.unwrapParent = unwrapParent;

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display