@angular/core
Advanced tools
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
| * License: MIT | ||
| */ | ||
| 'use strict'; | ||
| var ts = require('typescript'); | ||
| var compilerCli = require('@angular/compiler-cli'); | ||
| var project_paths = require('./project_paths-C3_ORvxG.cjs'); | ||
| /** | ||
| * Applies import manager changes, and writes them as replacements the | ||
| * given result array. | ||
| */ | ||
| function applyImportManagerChanges(importManager, replacements, sourceFiles, info) { | ||
| const { newImports, updatedImports, deletedImports } = importManager.finalize(); | ||
| const printer = ts.createPrinter({}); | ||
| const pathToFile = new Map(sourceFiles.map((s) => [s.fileName, s])); | ||
| // Capture new imports | ||
| newImports.forEach((newImports, fileName) => { | ||
| newImports.forEach((newImport) => { | ||
| const printedImport = printer.printNode(ts.EmitHint.Unspecified, newImport, pathToFile.get(fileName)); | ||
| replacements.push(new project_paths.Replacement(project_paths.projectFile(compilerCli.absoluteFrom(fileName), info), new project_paths.TextUpdate({ position: 0, end: 0, toInsert: `${printedImport}\n` }))); | ||
| }); | ||
| }); | ||
| // Capture updated imports | ||
| for (const [oldBindings, newBindings] of updatedImports.entries()) { | ||
| // The import will be generated as multi-line if it already is multi-line, | ||
| // or if the number of elements significantly increased and it previously | ||
| // consisted of very few specifiers. | ||
| const isMultiline = oldBindings.getText().includes('\n') || | ||
| (newBindings.elements.length >= 6 && oldBindings.elements.length <= 3); | ||
| const hasSpaceBetweenBraces = oldBindings.getText().startsWith('{ '); | ||
| let formatFlags = ts.ListFormat.NamedImportsOrExportsElements | | ||
| ts.ListFormat.Indented | | ||
| ts.ListFormat.Braces | | ||
| ts.ListFormat.PreserveLines | | ||
| (isMultiline ? ts.ListFormat.MultiLine : ts.ListFormat.SingleLine); | ||
| if (hasSpaceBetweenBraces) { | ||
| formatFlags |= ts.ListFormat.SpaceBetweenBraces; | ||
| } | ||
| else { | ||
| formatFlags &= ~ts.ListFormat.SpaceBetweenBraces; | ||
| } | ||
| const printedBindings = printer.printList(formatFlags, newBindings.elements, oldBindings.getSourceFile()); | ||
| replacements.push(new project_paths.Replacement(project_paths.projectFile(oldBindings.getSourceFile(), info), new project_paths.TextUpdate({ | ||
| position: oldBindings.getStart(), | ||
| end: oldBindings.getEnd(), | ||
| // TS uses four spaces as indent. We migrate to two spaces as we | ||
| // assume this to be more common. | ||
| toInsert: printedBindings.replace(/^ {4}/gm, ' '), | ||
| }))); | ||
| } | ||
| // Update removed imports | ||
| for (const removedImport of deletedImports) { | ||
| replacements.push(new project_paths.Replacement(project_paths.projectFile(removedImport.getSourceFile(), info), new project_paths.TextUpdate({ | ||
| position: removedImport.getStart(), | ||
| end: removedImport.getEnd(), | ||
| toInsert: '', | ||
| }))); | ||
| } | ||
| } | ||
| exports.applyImportManagerChanges = applyImportManagerChanges; |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.2 | ||
| * (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-C3_ORvxG.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
Sorry, the diff of this file is too big to display
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
| * License: MIT | ||
| */ | ||
| 'use strict'; | ||
| var compilerCli = require('@angular/compiler-cli'); | ||
| var schematics = require('@angular-devkit/schematics'); | ||
| var core = require('@angular-devkit/core'); | ||
| var posixPath = require('node:path/posix'); | ||
| var migrations = require('@angular/compiler-cli/private/migrations'); | ||
| var ts = require('typescript'); | ||
| var path = require('node:path'); | ||
| var project_tsconfig_paths = require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
| function _interopNamespaceDefault(e) { | ||
| var n = Object.create(null); | ||
| if (e) { | ||
| Object.keys(e).forEach(function (k) { | ||
| if (k !== 'default') { | ||
| var d = Object.getOwnPropertyDescriptor(e, k); | ||
| Object.defineProperty(n, k, d.get ? d : { | ||
| enumerable: true, | ||
| get: function () { return e[k]; } | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
| n.default = e; | ||
| return Object.freeze(n); | ||
| } | ||
| var posixPath__namespace = /*#__PURE__*/_interopNamespaceDefault(posixPath); | ||
| var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path); | ||
| /** | ||
| * Angular compiler file system implementation that leverages an | ||
| * CLI schematic virtual file tree. | ||
| */ | ||
| class DevkitMigrationFilesystem { | ||
| tree; | ||
| constructor(tree) { | ||
| this.tree = tree; | ||
| } | ||
| extname(path) { | ||
| return core.extname(path); | ||
| } | ||
| isRoot(path) { | ||
| return path === core.normalize('/'); | ||
| } | ||
| isRooted(path) { | ||
| return this.normalize(path).startsWith('/'); | ||
| } | ||
| dirname(file) { | ||
| return this.normalize(core.dirname(file)); | ||
| } | ||
| join(basePath, ...paths) { | ||
| return this.normalize(core.join(basePath, ...paths)); | ||
| } | ||
| relative(from, to) { | ||
| return this.normalize(core.relative(from, to)); | ||
| } | ||
| basename(filePath, extension) { | ||
| return posixPath__namespace.basename(filePath, extension); | ||
| } | ||
| normalize(path) { | ||
| return core.normalize(path); | ||
| } | ||
| resolve(...paths) { | ||
| const normalizedPaths = paths.map((p) => core.normalize(p)); | ||
| // In dev-kit, the NodeJS working directory should never be | ||
| // considered, so `/` is the last resort over `cwd`. | ||
| return this.normalize(posixPath__namespace.resolve(core.normalize('/'), ...normalizedPaths)); | ||
| } | ||
| pwd() { | ||
| return '/'; | ||
| } | ||
| isCaseSensitive() { | ||
| return true; | ||
| } | ||
| exists(path) { | ||
| return statPath(this.tree, path) !== null; | ||
| } | ||
| readFile(path) { | ||
| return this.tree.readText(path); | ||
| } | ||
| readFileBuffer(path) { | ||
| const buffer = this.tree.read(path); | ||
| if (buffer === null) { | ||
| throw new Error(`File does not exist: ${path}`); | ||
| } | ||
| return buffer; | ||
| } | ||
| readdir(path) { | ||
| const dir = this.tree.getDir(path); | ||
| return [ | ||
| ...dir.subdirs, | ||
| ...dir.subfiles, | ||
| ]; | ||
| } | ||
| lstat(path) { | ||
| const stat = statPath(this.tree, path); | ||
| if (stat === null) { | ||
| throw new Error(`File does not exist for "lstat": ${path}`); | ||
| } | ||
| return stat; | ||
| } | ||
| stat(path) { | ||
| const stat = statPath(this.tree, path); | ||
| if (stat === null) { | ||
| throw new Error(`File does not exist for "stat": ${path}`); | ||
| } | ||
| return stat; | ||
| } | ||
| realpath(filePath) { | ||
| return filePath; | ||
| } | ||
| getDefaultLibLocation() { | ||
| return 'node_modules/typescript/lib'; | ||
| } | ||
| ensureDir(path) { | ||
| // Migrations should compute replacements and not write directly. | ||
| throw new Error('DevkitFilesystem#ensureDir is not supported.'); | ||
| } | ||
| writeFile(path, data) { | ||
| // Migrations should compute replacements and not write directly. | ||
| throw new Error('DevkitFilesystem#writeFile is not supported.'); | ||
| } | ||
| removeFile(path) { | ||
| // Migrations should compute replacements and not write directly. | ||
| throw new Error('DevkitFilesystem#removeFile is not supported.'); | ||
| } | ||
| copyFile(from, to) { | ||
| // Migrations should compute replacements and not write directly. | ||
| throw new Error('DevkitFilesystem#copyFile is not supported.'); | ||
| } | ||
| moveFile(from, to) { | ||
| // Migrations should compute replacements and not write directly. | ||
| throw new Error('DevkitFilesystem#moveFile is not supported.'); | ||
| } | ||
| removeDeep(path) { | ||
| // Migrations should compute replacements and not write directly. | ||
| throw new Error('DevkitFilesystem#removeDeep is not supported.'); | ||
| } | ||
| chdir(_path) { | ||
| throw new Error('FileSystem#chdir is not supported.'); | ||
| } | ||
| symlink() { | ||
| throw new Error('FileSystem#symlink is not supported.'); | ||
| } | ||
| } | ||
| /** Stats the given path in the virtual tree. */ | ||
| function statPath(tree, path) { | ||
| let fileInfo = null; | ||
| let dirInfo = null; | ||
| try { | ||
| fileInfo = tree.get(path); | ||
| } | ||
| catch (e) { | ||
| if (e.constructor.name === 'PathIsDirectoryException') { | ||
| dirInfo = tree.getDir(path); | ||
| } | ||
| else { | ||
| throw e; | ||
| } | ||
| } | ||
| if (fileInfo !== null || dirInfo !== null) { | ||
| return { | ||
| isDirectory: () => dirInfo !== null, | ||
| isFile: () => fileInfo !== null, | ||
| isSymbolicLink: () => false, | ||
| }; | ||
| } | ||
| return null; | ||
| } | ||
| /** | ||
| * Groups the given replacements per project relative | ||
| * file path. | ||
| * | ||
| * This allows for simple execution of the replacements | ||
| * against a given file. E.g. via {@link applyTextUpdates}. | ||
| */ | ||
| function groupReplacementsByFile(replacements) { | ||
| const result = new Map(); | ||
| for (const { projectFile, update } of replacements) { | ||
| if (!result.has(projectFile.rootRelativePath)) { | ||
| result.set(projectFile.rootRelativePath, []); | ||
| } | ||
| result.get(projectFile.rootRelativePath).push(update); | ||
| } | ||
| return result; | ||
| } | ||
| /** | ||
| * Synchronously combines unit data for the given migration. | ||
| * | ||
| * Note: This helper is useful for testing and execution of | ||
| * Tsurge migrations in non-batchable environments. In general, | ||
| * prefer parallel execution of combining via e.g. Beam combiners. | ||
| */ | ||
| async function synchronouslyCombineUnitData(migration, unitDatas) { | ||
| if (unitDatas.length === 0) { | ||
| return null; | ||
| } | ||
| if (unitDatas.length === 1) { | ||
| return unitDatas[0]; | ||
| } | ||
| let combined = unitDatas[0]; | ||
| for (let i = 1; i < unitDatas.length; i++) { | ||
| const other = unitDatas[i]; | ||
| combined = await migration.combine(combined, other); | ||
| } | ||
| return combined; | ||
| } | ||
| /** Whether we are executing inside Google */ | ||
| function isGoogle3() { | ||
| return process.env['GOOGLE3_TSURGE'] === '1'; | ||
| } | ||
| /** | ||
| * By default, Tsurge will always create an Angular compiler program | ||
| * for projects analyzed and migrated. This works perfectly fine in | ||
| * third-party where Tsurge migrations run in Angular CLI projects. | ||
| * | ||
| * In first party, when running against full Google3, creating an Angular | ||
| * program for e.g. plain `ts_library` targets is overly expensive and | ||
| * can result in out of memory issues for large TS targets. In 1P we can | ||
| * reliably distinguish between TS and Angular targets via the `angularCompilerOptions`. | ||
| */ | ||
| function google3UsePlainTsProgramIfNoKnownAngularOption() { | ||
| return process.env['GOOGLE3_TSURGE'] === '1'; | ||
| } | ||
| /** Options that are good defaults for Tsurge migrations. */ | ||
| const defaultMigrationTsOptions = { | ||
| // Avoid checking libraries to speed up migrations. | ||
| skipLibCheck: true, | ||
| skipDefaultLibCheck: true, | ||
| noEmit: true, | ||
| // Does not apply to g3 and externally is enforced when the app is built by the compiler. | ||
| disableTypeScriptVersionCheck: true, | ||
| }; | ||
| /** | ||
| * Creates an instance of a TypeScript program for the given project. | ||
| */ | ||
| function createPlainTsProgram(tsHost, tsconfig, optionOverrides) { | ||
| const program = ts.createProgram({ | ||
| rootNames: tsconfig.rootNames, | ||
| options: { | ||
| ...tsconfig.options, | ||
| ...defaultMigrationTsOptions, | ||
| ...optionOverrides, | ||
| }, | ||
| }); | ||
| return { | ||
| ngCompiler: null, | ||
| program, | ||
| userOptions: tsconfig.options, | ||
| __programAbsoluteRootFileNames: tsconfig.rootNames, | ||
| host: tsHost, | ||
| }; | ||
| } | ||
| /** | ||
| * Parses the configuration of the given TypeScript project and creates | ||
| * an instance of the Angular compiler for the project. | ||
| */ | ||
| function createNgtscProgram(tsHost, tsconfig, optionOverrides) { | ||
| const ngtscProgram = new compilerCli.NgtscProgram(tsconfig.rootNames, { | ||
| ...tsconfig.options, | ||
| ...defaultMigrationTsOptions, | ||
| ...optionOverrides, | ||
| }, tsHost); | ||
| // Expose an easy way to debug-print ng semantic diagnostics. | ||
| if (process.env['DEBUG_NG_SEMANTIC_DIAGNOSTICS'] === '1') { | ||
| console.error(ts.formatDiagnosticsWithColorAndContext(ngtscProgram.getNgSemanticDiagnostics(), tsHost)); | ||
| } | ||
| return { | ||
| ngCompiler: ngtscProgram.compiler, | ||
| program: ngtscProgram.getTsProgram(), | ||
| userOptions: tsconfig.options, | ||
| __programAbsoluteRootFileNames: tsconfig.rootNames, | ||
| host: tsHost, | ||
| }; | ||
| } | ||
| /** Code of the error raised by TypeScript when a tsconfig doesn't match any files. */ | ||
| const NO_INPUTS_ERROR_CODE = 18003; | ||
| /** Parses the given tsconfig file, supporting Angular compiler options. */ | ||
| function parseTsconfigOrDie(absoluteTsconfigPath, fs) { | ||
| const tsconfig = compilerCli.readConfiguration(absoluteTsconfigPath, {}, fs); | ||
| // Skip the "No inputs found..." error since we don't want to interrupt the migration if a | ||
| // tsconfig doesn't match a file. This will result in an empty `Program` which is still valid. | ||
| const errors = tsconfig.errors.filter((diag) => diag.code !== NO_INPUTS_ERROR_CODE); | ||
| if (errors.length) { | ||
| throw new Error(`Tsconfig could not be parsed or is invalid:\n\n` + `${errors.map((e) => e.messageText)}`); | ||
| } | ||
| return tsconfig; | ||
| } | ||
| // Note: Try to keep mostly in sync with | ||
| // //depot/google3/javascript/angular2/tools/ngc_wrapped/tsc_plugin.ts | ||
| // TODO: Consider moving this logic into the 1P launcher. | ||
| const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; | ||
| function fileNameToModuleNameFactory(rootDirs, workspaceName) { | ||
| return (importedFilePath) => { | ||
| let relativePath = ''; | ||
| for (const rootDir of rootDirs) { | ||
| const rel = path__namespace.posix.relative(rootDir, importedFilePath); | ||
| if (!rel.startsWith('.')) { | ||
| relativePath = rel; | ||
| break; | ||
| } | ||
| } | ||
| if (relativePath) { | ||
| return `${workspaceName}/${relativePath.replace(EXT, '')}`; | ||
| } | ||
| else { | ||
| return importedFilePath.replace(EXT, ''); | ||
| } | ||
| }; | ||
| } | ||
| /** Creates the base program info for the given tsconfig path. */ | ||
| function createBaseProgramInfo(absoluteTsconfigPath, fs, optionOverrides = {}) { | ||
| // Make sure the FS becomes globally available. Some code paths | ||
| // of the Angular compiler, or tsconfig parsing aren't leveraging | ||
| // the specified file system. | ||
| compilerCli.setFileSystem(fs); | ||
| const tsconfig = parseTsconfigOrDie(absoluteTsconfigPath, fs); | ||
| const tsHost = new compilerCli.NgtscCompilerHost(fs, tsconfig.options); | ||
| // When enabled, use a plain TS program if we are sure it's not | ||
| // an Angular project based on the `tsconfig.json`. | ||
| if (google3UsePlainTsProgramIfNoKnownAngularOption() && | ||
| tsconfig.options['_useHostForImportGeneration'] === undefined) { | ||
| return createPlainTsProgram(tsHost, tsconfig, optionOverrides); | ||
| } | ||
| // The Angular program may try to emit references during analysis or migration. | ||
| // To replicate the Google3 import emission here, ensure the unified module resolution | ||
| // can be enabled by the compiler. | ||
| if (isGoogle3() && tsconfig.options.rootDirs) { | ||
| tsHost.fileNameToModuleName = fileNameToModuleNameFactory(tsconfig.options.rootDirs, | ||
| /* workspaceName*/ 'google3'); | ||
| } | ||
| return createNgtscProgram(tsHost, tsconfig, optionOverrides); | ||
| } | ||
| /** | ||
| * Creates the {@link ProgramInfo} from the given base information. | ||
| * | ||
| * This function purely exists to support custom programs that are | ||
| * intended to be injected into Tsurge migrations. e.g. for language | ||
| * service refactorings. | ||
| */ | ||
| function getProgramInfoFromBaseInfo(baseInfo) { | ||
| const fullProgramSourceFiles = [...baseInfo.program.getSourceFiles()]; | ||
| const sourceFiles = fullProgramSourceFiles.filter((f) => !f.isDeclarationFile && | ||
| // Note `isShim` will work for the initial program, but for TCB programs, the shims are no longer annotated. | ||
| !migrations.isShim(f) && | ||
| !f.fileName.endsWith('.ngtypecheck.ts')); | ||
| // Sort it by length in reverse order (longest first). This speeds up lookups, | ||
| // since there's no need to keep going through the array once a match is found. | ||
| const sortedRootDirs = migrations.getRootDirs(baseInfo.host, baseInfo.userOptions).sort((a, b) => b.length - a.length); | ||
| // TODO: Consider also following TS's logic here, finding the common source root. | ||
| // See: Program#getCommonSourceDirectory. | ||
| const primaryRoot = compilerCli.absoluteFrom(baseInfo.userOptions.rootDir ?? sortedRootDirs.at(-1) ?? baseInfo.program.getCurrentDirectory()); | ||
| return { | ||
| ...baseInfo, | ||
| sourceFiles, | ||
| fullProgramSourceFiles, | ||
| sortedRootDirs, | ||
| projectRoot: primaryRoot, | ||
| }; | ||
| } | ||
| /** | ||
| * @private | ||
| * | ||
| * Base class for the possible Tsurge migration variants. | ||
| * | ||
| * For example, this class exposes methods to conveniently create | ||
| * TypeScript programs, while also allowing migration authors to override. | ||
| */ | ||
| class TsurgeBaseMigration { | ||
| /** | ||
| * Creates the TypeScript program for a given compilation unit. | ||
| * | ||
| * By default: | ||
| * - In 3P: Ngtsc programs are being created. | ||
| * - In 1P: Ngtsc or TS programs are created based on the Blaze target. | ||
| */ | ||
| createProgram(tsconfigAbsPath, fs, optionsOverride) { | ||
| return getProgramInfoFromBaseInfo(createBaseProgramInfo(tsconfigAbsPath, fs, optionsOverride)); | ||
| } | ||
| } | ||
| /** | ||
| * A simpler variant of a {@link TsurgeComplexMigration} that does not | ||
| * fan-out into multiple workers per compilation unit to compute | ||
| * the final migration replacements. | ||
| * | ||
| * This is faster and less resource intensive as workers and TS programs | ||
| * are only ever created once. | ||
| * | ||
| * This is commonly the case when migrations are refactored to eagerly | ||
| * compute replacements in the analyze stage, and then leverage the | ||
| * global unit data to filter replacements that turned out to be "invalid". | ||
| */ | ||
| class TsurgeFunnelMigration extends TsurgeBaseMigration { | ||
| } | ||
| /** | ||
| * Complex variant of a `Tsurge` migration. | ||
| * | ||
| * For example, every analyze worker may contribute to a list of TS | ||
| * references that are later combined. The migrate phase can then compute actual | ||
| * file updates for all individual compilation units, leveraging the global metadata | ||
| * to e.g. see if there are any references from other compilation units that may be | ||
| * problematic and prevent migration of a given file. | ||
| */ | ||
| class TsurgeComplexMigration extends TsurgeBaseMigration { | ||
| } | ||
| exports.MigrationStage = void 0; | ||
| (function (MigrationStage) { | ||
| /** The migration is analyzing an entrypoint */ | ||
| MigrationStage[MigrationStage["Analysis"] = 0] = "Analysis"; | ||
| /** The migration is about to migrate an entrypoint */ | ||
| MigrationStage[MigrationStage["Migrate"] = 1] = "Migrate"; | ||
| })(exports.MigrationStage || (exports.MigrationStage = {})); | ||
| /** Runs a Tsurge within an Angular Devkit context. */ | ||
| async function runMigrationInDevkit(config) { | ||
| const { buildPaths, testPaths } = await project_tsconfig_paths.getProjectTsConfigPaths(config.tree); | ||
| if (!buildPaths.length && !testPaths.length) { | ||
| throw new schematics.SchematicsException('Could not find any tsconfig file. Cannot run the migration.'); | ||
| } | ||
| const tsconfigPaths = [...buildPaths, ...testPaths]; | ||
| const fs = new DevkitMigrationFilesystem(config.tree); | ||
| compilerCli.setFileSystem(fs); | ||
| const migration = config.getMigration(fs); | ||
| const unitResults = []; | ||
| const isFunnelMigration = migration instanceof TsurgeFunnelMigration; | ||
| const compilationUnitAssignments = new Map(); | ||
| for (const tsconfigPath of tsconfigPaths) { | ||
| config.beforeProgramCreation?.(tsconfigPath, exports.MigrationStage.Analysis); | ||
| const info = migration.createProgram(tsconfigPath, fs); | ||
| modifyProgramInfoToEnsureNonOverlappingFiles(tsconfigPath, info, compilationUnitAssignments); | ||
| config.afterProgramCreation?.(info, fs, exports.MigrationStage.Analysis); | ||
| config.beforeUnitAnalysis?.(tsconfigPath); | ||
| unitResults.push(await migration.analyze(info)); | ||
| } | ||
| config.afterAllAnalyzed?.(); | ||
| const combined = await synchronouslyCombineUnitData(migration, unitResults); | ||
| if (combined === null) { | ||
| config.afterAnalysisFailure?.(); | ||
| return; | ||
| } | ||
| const globalMeta = await migration.globalMeta(combined); | ||
| let replacements; | ||
| if (isFunnelMigration) { | ||
| replacements = (await migration.migrate(globalMeta)).replacements; | ||
| } | ||
| else { | ||
| replacements = []; | ||
| for (const tsconfigPath of tsconfigPaths) { | ||
| config.beforeProgramCreation?.(tsconfigPath, exports.MigrationStage.Migrate); | ||
| const info = migration.createProgram(tsconfigPath, fs); | ||
| modifyProgramInfoToEnsureNonOverlappingFiles(tsconfigPath, info, compilationUnitAssignments); | ||
| config.afterProgramCreation?.(info, fs, exports.MigrationStage.Migrate); | ||
| const result = await migration.migrate(globalMeta, info); | ||
| replacements.push(...result.replacements); | ||
| } | ||
| } | ||
| const replacementsPerFile = new Map(); | ||
| const changesPerFile = groupReplacementsByFile(replacements); | ||
| for (const [file, changes] of changesPerFile) { | ||
| if (!replacementsPerFile.has(file)) { | ||
| replacementsPerFile.set(file, changes); | ||
| } | ||
| } | ||
| for (const [file, changes] of replacementsPerFile) { | ||
| const recorder = config.tree.beginUpdate(file); | ||
| for (const c of changes) { | ||
| recorder | ||
| .remove(c.data.position, c.data.end - c.data.position) | ||
| .insertRight(c.data.position, c.data.toInsert); | ||
| } | ||
| config.tree.commitUpdate(recorder); | ||
| } | ||
| config.whenDone?.(await migration.stats(globalMeta)); | ||
| } | ||
| /** | ||
| * Special logic for devkit migrations. In the Angular CLI, or in 3P precisely, | ||
| * projects can have tsconfigs with overlapping source files. i.e. two tsconfigs | ||
| * like e.g. build or test include the same `ts.SourceFile` (`.ts`). Migrations | ||
| * should never have 2+ compilation units with overlapping source files as this | ||
| * can result in duplicated replacements or analysis— hence we only ever assign a | ||
| * source file to a compilation unit *once*. | ||
| * | ||
| * Note that this is fine as we expect Tsurge migrations to work together as | ||
| * isolated compilation units— so it shouldn't matter if worst case a `.ts` | ||
| * file ends up in the e.g. test program. | ||
| */ | ||
| function modifyProgramInfoToEnsureNonOverlappingFiles(tsconfigPath, info, compilationUnitAssignments) { | ||
| const sourceFiles = []; | ||
| for (const sf of info.sourceFiles) { | ||
| const assignment = compilationUnitAssignments.get(sf.fileName); | ||
| // File is already assigned to a different compilation unit. | ||
| if (assignment !== undefined && assignment !== tsconfigPath) { | ||
| continue; | ||
| } | ||
| compilationUnitAssignments.set(sf.fileName, tsconfigPath); | ||
| sourceFiles.push(sf); | ||
| } | ||
| info.sourceFiles = sourceFiles; | ||
| } | ||
| /** A text replacement for the given file. */ | ||
| class Replacement { | ||
| projectFile; | ||
| update; | ||
| constructor(projectFile, update) { | ||
| this.projectFile = projectFile; | ||
| this.update = update; | ||
| } | ||
| } | ||
| /** An isolated text update that may be applied to a file. */ | ||
| class TextUpdate { | ||
| data; | ||
| constructor(data) { | ||
| this.data = data; | ||
| } | ||
| } | ||
| /** Confirms that the given data `T` is serializable. */ | ||
| function confirmAsSerializable(data) { | ||
| return data; | ||
| } | ||
| /** | ||
| * Gets a project file instance for the given file. | ||
| * | ||
| * Use this helper for dealing with project paths throughout your | ||
| * migration. The return type is serializable. | ||
| * | ||
| * See {@link ProjectFile}. | ||
| */ | ||
| function projectFile(file, { sortedRootDirs, projectRoot }) { | ||
| const fs = compilerCli.getFileSystem(); | ||
| const filePath = fs.resolve(typeof file === 'string' ? file : file.fileName); | ||
| // Sorted root directories are sorted longest to shortest. First match | ||
| // is the appropriate root directory for ID computation. | ||
| for (const rootDir of sortedRootDirs) { | ||
| if (!isWithinBasePath(fs, rootDir, filePath)) { | ||
| continue; | ||
| } | ||
| return { | ||
| id: fs.relative(rootDir, filePath), | ||
| rootRelativePath: fs.relative(projectRoot, filePath), | ||
| }; | ||
| } | ||
| // E.g. project directory may be `src/`, but files may be looked up | ||
| // from `node_modules/`. This is fine, but in those cases, no root | ||
| // directory matches. | ||
| const rootRelativePath = fs.relative(projectRoot, filePath); | ||
| return { | ||
| id: rootRelativePath, | ||
| rootRelativePath: rootRelativePath, | ||
| }; | ||
| } | ||
| /** | ||
| * Whether `path` is a descendant of the `base`? | ||
| * E.g. `a/b/c` is within `a/b` but not within `a/x`. | ||
| */ | ||
| function isWithinBasePath(fs, base, path) { | ||
| return compilerCli.isLocalRelativePath(fs.relative(base, path)); | ||
| } | ||
| exports.Replacement = Replacement; | ||
| exports.TextUpdate = TextUpdate; | ||
| exports.TsurgeComplexMigration = TsurgeComplexMigration; | ||
| exports.TsurgeFunnelMigration = TsurgeFunnelMigration; | ||
| exports.confirmAsSerializable = confirmAsSerializable; | ||
| exports.projectFile = projectFile; | ||
| exports.runMigrationInDevkit = runMigrationInDevkit; |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
| * License: MIT | ||
| */ | ||
| 'use strict'; | ||
| var core = require('@angular-devkit/core'); | ||
| /** | ||
| * Gets all tsconfig paths from a CLI project by reading the workspace configuration | ||
| * and looking for common tsconfig locations. | ||
| */ | ||
| async function getProjectTsConfigPaths(tree) { | ||
| // Start with some tsconfig paths that are generally used within CLI projects. Note | ||
| // that we are not interested in IDE-specific tsconfig files (e.g. /tsconfig.json) | ||
| const buildPaths = new Set(); | ||
| const testPaths = new Set(); | ||
| const workspace = await getWorkspace(tree); | ||
| for (const [, project] of workspace.projects) { | ||
| for (const [name, target] of project.targets) { | ||
| for (const [, options] of allTargetOptions(target)) { | ||
| const tsConfig = options['tsConfig']; | ||
| // Filter out tsconfig files that don't exist in the CLI project. | ||
| if (typeof tsConfig !== 'string' || !tree.exists(tsConfig)) { | ||
| continue; | ||
| } | ||
| if (name === 'test' || name.includes('test')) { | ||
| testPaths.add(core.normalize(tsConfig)); | ||
| } | ||
| else { | ||
| buildPaths.add(core.normalize(tsConfig)); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return { | ||
| buildPaths: [...buildPaths], | ||
| testPaths: [...testPaths], | ||
| }; | ||
| } | ||
| /** Get options for all configurations for the passed builder target. */ | ||
| function* allTargetOptions(target) { | ||
| if (target.options) { | ||
| yield [undefined, target.options]; | ||
| } | ||
| if (!target.configurations) { | ||
| return; | ||
| } | ||
| for (const [name, options] of Object.entries(target.configurations)) { | ||
| if (options) { | ||
| yield [name, options]; | ||
| } | ||
| } | ||
| } | ||
| function createHost(tree) { | ||
| return { | ||
| async readFile(path) { | ||
| const data = tree.read(path); | ||
| if (!data) { | ||
| throw new Error('File not found.'); | ||
| } | ||
| return core.virtualFs.fileBufferToString(data); | ||
| }, | ||
| async writeFile(path, data) { | ||
| return tree.overwrite(path, data); | ||
| }, | ||
| async isDirectory(path) { | ||
| // Approximate a directory check. | ||
| // We don't need to consider empty directories and hence this is a good enough approach. | ||
| // This is also per documentation, see: | ||
| // https://angular.dev/tools/cli/schematics-for-libraries#get-the-project-configuration | ||
| return !tree.exists(path) && tree.getDir(path).subfiles.length > 0; | ||
| }, | ||
| async isFile(path) { | ||
| return tree.exists(path); | ||
| }, | ||
| }; | ||
| } | ||
| async function getWorkspace(tree) { | ||
| const host = createHost(tree); | ||
| const { workspace } = await core.workspaces.readWorkspace('/', host); | ||
| return workspace; | ||
| } | ||
| exports.getProjectTsConfigPaths = getProjectTsConfigPaths; |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -4,0 +4,0 @@ * License: MIT |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -62,2 +62,3 @@ * License: MIT | ||
| nextProducerLink.lastReadVersion = node.version; | ||
| nextProducerLink.knownValidAtEpoch = epoch; | ||
| return; | ||
@@ -67,3 +68,3 @@ } | ||
| const prevConsumerLink = node.consumersTail; | ||
| if (prevConsumerLink !== undefined && prevConsumerLink.consumer === activeConsumer && (!isRecomputing || isValidLink(prevConsumerLink, activeConsumer))) { | ||
| if (prevConsumerLink !== undefined && prevConsumerLink.consumer === activeConsumer && (!isRecomputing || prevConsumerLink.knownValidAtEpoch === epoch)) { | ||
| return; | ||
@@ -77,2 +78,3 @@ } | ||
| prevConsumer: undefined, | ||
| knownValidAtEpoch: epoch, | ||
| lastReadVersion: node.version, | ||
@@ -142,2 +144,9 @@ nextConsumer: undefined | ||
| function resetConsumerBeforeComputation(node) { | ||
| if (node.producersTail?.knownValidAtEpoch === epoch) { | ||
| let producer = node.producers; | ||
| while (producer !== undefined) { | ||
| producer.knownValidAtEpoch = null; | ||
| producer = producer.nextProducer; | ||
| } | ||
| } | ||
| node.producersTail = undefined; | ||
@@ -247,18 +256,2 @@ node.recomputing = true; | ||
| } | ||
| function isValidLink(checkLink, consumer) { | ||
| const producersTail = consumer.producersTail; | ||
| if (producersTail !== undefined) { | ||
| let link = consumer.producers; | ||
| do { | ||
| if (link === checkLink) { | ||
| return true; | ||
| } | ||
| if (link === producersTail) { | ||
| break; | ||
| } | ||
| link = link.nextProducer; | ||
| } while (link !== undefined); | ||
| } | ||
| return false; | ||
| } | ||
@@ -265,0 +258,0 @@ function defaultEquals(a, b) { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"_effect-chunk.mjs","sources":["../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/core/primitives/signals/src/graph.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/core/primitives/signals/src/equality.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/core/primitives/signals/src/computed.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/core/primitives/signals/src/errors.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/core/primitives/signals/src/signal.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/core/primitives/signals/src/effect.ts"],"sourcesContent":["/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\n// Required as the signals library is in a separate package, so we need to explicitly ensure the\n// global `ngDevMode` type is defined.\ndeclare const ngDevMode: boolean | undefined;\n\n/**\n * The currently active consumer `ReactiveNode`, if running code in a reactive context.\n *\n * Change this via `setActiveConsumer`.\n */\nlet activeConsumer: ReactiveNode | null = null;\nlet inNotificationPhase = false;\n\nexport type Version = number & {__brand: 'Version'};\n\n/**\n * Global epoch counter. Incremented whenever a source signal is set.\n */\nlet epoch: Version = 1 as Version;\n\nexport type ReactiveHookFn = (node: ReactiveNode) => void;\n\n/**\n * If set, called after a producer `ReactiveNode` is created.\n */\nlet postProducerCreatedFn: ReactiveHookFn | null = null;\n\n/**\n * Symbol used to tell `Signal`s apart from other functions.\n *\n * This can be used to auto-unwrap signals in various cases, or to auto-wrap non-signal values.\n */\nexport const SIGNAL: unique symbol = /* @__PURE__ */ Symbol('SIGNAL');\n\nexport function setActiveConsumer(consumer: ReactiveNode | null): ReactiveNode | null {\n const prev = activeConsumer;\n activeConsumer = consumer;\n return prev;\n}\n\nexport function getActiveConsumer(): ReactiveNode | null {\n return activeConsumer;\n}\n\nexport function isInNotificationPhase(): boolean {\n return inNotificationPhase;\n}\n\nexport interface Reactive {\n [SIGNAL]: ReactiveNode;\n}\n\nexport function isReactive(value: unknown): value is Reactive {\n return (value as Partial<Reactive>)[SIGNAL] !== undefined;\n}\n\nexport const REACTIVE_NODE: ReactiveNode = {\n version: 0 as Version,\n lastCleanEpoch: 0 as Version,\n dirty: false,\n producers: undefined,\n producersTail: undefined,\n consumers: undefined,\n consumersTail: undefined,\n recomputing: false,\n consumerAllowSignalWrites: false,\n consumerIsAlwaysLive: false,\n kind: 'unknown',\n producerMustRecompute: () => false,\n producerRecomputeValue: () => {},\n consumerMarkedDirty: () => {},\n consumerOnSignalRead: () => {},\n};\n\ninterface ReactiveLink {\n producer: ReactiveNode;\n consumer: ReactiveNode;\n lastReadVersion: number;\n prevConsumer: ReactiveLink | undefined;\n nextConsumer: ReactiveLink | undefined;\n nextProducer: ReactiveLink | undefined;\n}\n\nexport type ReactiveNodeKind =\n | 'signal'\n | 'computed'\n | 'effect'\n | 'template'\n | 'linkedSignal'\n | 'afterRenderEffectPhase'\n | 'unknown';\n\n/**\n * A producer and/or consumer which participates in the reactive graph.\n *\n * Producer `ReactiveNode`s which are accessed when a consumer `ReactiveNode` is the\n * `activeConsumer` are tracked as dependencies of that consumer.\n *\n * Certain consumers are also tracked as \"live\" consumers and create edges in the other direction,\n * from producer to consumer. These edges are used to propagate change notifications when a\n * producer's value is updated.\n *\n * A `ReactiveNode` may be both a producer and consumer.\n */\nexport interface ReactiveNode {\n /**\n * Version of the value that this node produces.\n *\n * This is incremented whenever a new value is produced by this node which is not equal to the\n * previous value (by whatever definition of equality is in use).\n */\n version: Version;\n\n /**\n * Epoch at which this node is verified to be clean.\n *\n * This allows skipping of some polling operations in the case where no signals have been set\n * since this node was last read.\n */\n lastCleanEpoch: Version;\n\n /**\n * Whether this node (in its consumer capacity) is dirty.\n *\n * Only live consumers become dirty, when receiving a change notification from a dependency\n * producer.\n */\n dirty: boolean;\n\n /**\n * Whether this node is currently rebuilding its producer list.\n */\n recomputing: boolean;\n\n /**\n * Producers which are dependencies of this consumer.\n */\n producers: ReactiveLink | undefined;\n\n /**\n * Points to the last linked list node in the `producers` linked list.\n *\n * When this node is recomputing, this is used to track the producers that we have accessed so far.\n */\n producersTail: ReactiveLink | undefined;\n\n /**\n * Linked list of consumers of this producer that are \"live\" (they require push notifications).\n *\n * The length of this list is effectively our reference count for this node.\n */\n consumers: ReactiveLink | undefined;\n consumersTail: ReactiveLink | undefined;\n\n /**\n * Whether writes to signals are allowed when this consumer is the `activeConsumer`.\n *\n * This is used to enforce guardrails such as preventing writes to writable signals in the\n * computation function of computed signals, which is supposed to be pure.\n */\n consumerAllowSignalWrites: boolean;\n\n readonly consumerIsAlwaysLive: boolean;\n\n /**\n * Tracks whether producers need to recompute their value independently of the reactive graph (for\n * example, if no initial value has been computed).\n */\n producerMustRecompute(node: unknown): boolean;\n producerRecomputeValue(node: unknown): void;\n consumerMarkedDirty(node: unknown): void;\n\n /**\n * Called when a signal is read within this consumer.\n */\n consumerOnSignalRead(node: unknown): void;\n\n /**\n * A debug name for the reactive node. Used in Angular DevTools to identify the node.\n */\n debugName?: string;\n\n /**\n * Kind of node. Example: 'signal', 'computed', 'input', 'effect'.\n *\n * ReactiveNode has this as 'unknown' by default, but derived node types should override this to\n * make available the kind of signal that particular instance of a ReactiveNode represents.\n *\n * Used in Angular DevTools to identify the kind of signal.\n */\n kind: ReactiveNodeKind;\n}\n\n/**\n * Called by implementations when a producer's signal is read.\n */\nexport function producerAccessed(node: ReactiveNode): void {\n if (inNotificationPhase) {\n throw new Error(\n typeof ngDevMode !== 'undefined' && ngDevMode\n ? `Assertion error: signal read during notification phase`\n : '',\n );\n }\n\n if (activeConsumer === null) {\n // Accessed outside of a reactive context, so nothing to record.\n return;\n }\n\n activeConsumer.consumerOnSignalRead(node);\n\n const prevProducerLink = activeConsumer.producersTail;\n\n // If the last producer we accessed is the same as the current one, we can skip adding a new\n // link\n if (prevProducerLink !== undefined && prevProducerLink.producer === node) {\n return;\n }\n\n let nextProducerLink: ReactiveLink | undefined = undefined;\n const isRecomputing = activeConsumer.recomputing;\n if (isRecomputing) {\n // If we're incrementally rebuilding the producers list, we want to check if the next producer\n // in the list is the same as the one we're trying to add.\n\n // If the previous producer is defined, then the next producer is just the one that follows it.\n // Otherwise, we should check the head of the producers list (the first node that we accessed the last time this consumer was run).\n nextProducerLink =\n prevProducerLink !== undefined ? prevProducerLink.nextProducer : activeConsumer.producers;\n if (nextProducerLink !== undefined && nextProducerLink.producer === node) {\n // If the next producer is the same as the one we're trying to add, we can just update the\n // last read version, update the tail of the producers list of this rerun, and return.\n activeConsumer.producersTail = nextProducerLink;\n nextProducerLink.lastReadVersion = node.version;\n return;\n }\n }\n\n const prevConsumerLink = node.consumersTail;\n\n // If the producer we're accessing already has a link to this consumer, we can skip adding a new\n // link. This can short circuit the creation of a new link in the case where the consumer reads alternating ReeactiveNodes\n if (\n prevConsumerLink !== undefined &&\n prevConsumerLink.consumer === activeConsumer &&\n // However, we have to make sure that the link we've discovered isn't from a node that is incrementally rebuilding its producer list\n (!isRecomputing || isValidLink(prevConsumerLink, activeConsumer))\n ) {\n // If we found an existing link to the consumer we can just return.\n return;\n }\n\n // If we got here, it means that we need to create a new link between the producer and the consumer.\n const isLive = consumerIsLive(activeConsumer);\n const newLink = {\n producer: node,\n consumer: activeConsumer,\n // instead of eagerly destroying the previous link, we delay until we've finished recomputing\n // the producers list, so that we can destroy all of the old links at once.\n nextProducer: nextProducerLink,\n // Don't set prevConsumer here — it's only meaningful when the link is part of\n // the producer's consumer list. producerAddLiveConsumer sets it correctly when\n // the link is actually inserted. Setting it eagerly would create a dangling\n // reference into the consumer list that prevents GC of removed entries.\n prevConsumer: undefined,\n lastReadVersion: node.version,\n nextConsumer: undefined,\n };\n activeConsumer.producersTail = newLink;\n if (prevProducerLink !== undefined) {\n prevProducerLink.nextProducer = newLink;\n } else {\n activeConsumer.producers = newLink;\n }\n\n if (isLive) {\n producerAddLiveConsumer(node, newLink);\n }\n}\n\n/**\n * Increment the global epoch counter.\n *\n * Called by source producers (that is, not computeds) whenever their values change.\n */\nexport function producerIncrementEpoch(): void {\n epoch++;\n}\n\n/**\n * Ensure this producer's `version` is up-to-date.\n */\nexport function producerUpdateValueVersion(node: ReactiveNode): void {\n if (consumerIsLive(node) && !node.dirty) {\n // A live consumer will be marked dirty by producers, so a clean state means that its version\n // is guaranteed to be up-to-date.\n return;\n }\n\n if (!node.dirty && node.lastCleanEpoch === epoch) {\n // Even non-live consumers can skip polling if they previously found themselves to be clean at\n // the current epoch, since their dependencies could not possibly have changed (such a change\n // would've increased the epoch).\n return;\n }\n\n if (!node.producerMustRecompute(node) && !consumerPollProducersForChange(node)) {\n // None of our producers report a change since the last time they were read, so no\n // recomputation of our value is necessary, and we can consider ourselves clean.\n producerMarkClean(node);\n return;\n }\n\n node.producerRecomputeValue(node);\n\n // After recomputing the value, we're no longer dirty.\n producerMarkClean(node);\n}\n\n/**\n * Propagate a dirty notification to live consumers of this producer.\n */\nexport function producerNotifyConsumers(node: ReactiveNode): void {\n if (node.consumers === undefined) {\n return;\n }\n\n // Prevent signal reads when we're updating the graph\n const prev = inNotificationPhase;\n inNotificationPhase = true;\n try {\n for (\n let link: ReactiveLink | undefined = node.consumers;\n link !== undefined;\n link = link.nextConsumer\n ) {\n const consumer = link.consumer;\n if (!consumer.dirty) {\n consumerMarkDirty(consumer);\n }\n }\n } finally {\n inNotificationPhase = prev;\n }\n}\n\n/**\n * Whether this `ReactiveNode` in its producer capacity is currently allowed to initiate updates,\n * based on the current consumer context.\n */\nexport function producerUpdatesAllowed(): boolean {\n return activeConsumer?.consumerAllowSignalWrites !== false;\n}\n\nexport function consumerMarkDirty(node: ReactiveNode): void {\n node.dirty = true;\n producerNotifyConsumers(node);\n node.consumerMarkedDirty?.(node);\n}\n\nexport function producerMarkClean(node: ReactiveNode): void {\n node.dirty = false;\n node.lastCleanEpoch = epoch;\n}\n\n/**\n * Prepare this consumer to run a computation in its reactive context and set\n * it as the active consumer.\n *\n * Must be called by subclasses which represent reactive computations, before those computations\n * begin.\n */\nexport function consumerBeforeComputation(node: ReactiveNode | null): ReactiveNode | null {\n if (node) resetConsumerBeforeComputation(node);\n\n return setActiveConsumer(node);\n}\n\n/**\n * Prepare this consumer to run a computation in its reactive context.\n *\n * We expose this mainly for code where we manually batch effects into a single\n * consumer. In those cases we may wish to \"reopen\" a consumer multiple times\n * in initial render before finalizing it. Most code should just call\n * `consumerBeforeComputation` instead of calling this directly.\n */\nexport function resetConsumerBeforeComputation(node: ReactiveNode): void {\n node.producersTail = undefined;\n node.recomputing = true;\n}\n\n/**\n * Finalize this consumer's state and set previous consumer as the active consumer after a\n * reactive computation has run.\n *\n * Must be called by subclasses which represent reactive computations, after those computations\n * have finished.\n */\nexport function consumerAfterComputation(\n node: ReactiveNode | null,\n prevConsumer: ReactiveNode | null,\n): void {\n setActiveConsumer(prevConsumer);\n\n if (node) finalizeConsumerAfterComputation(node);\n}\n\n/**\n * Finalize this consumer's state after a reactive computation has run.\n *\n * We expose this mainly for code where we manually batch effects into a single\n * consumer. In those cases we may wish to \"reopen\" a consumer multiple times\n * in initial render before finalizing it. Most code should just call\n * `consumerAfterComputation` instead of calling this directly.\n */\nexport function finalizeConsumerAfterComputation(node: ReactiveNode): void {\n node.recomputing = false;\n\n // We've finished incrementally rebuilding the producers list, now if there are any producers\n // that are after producersTail, they are stale and should be removed.\n const producersTail = node.producersTail as ReactiveLink | undefined;\n let toRemove = producersTail !== undefined ? producersTail.nextProducer : node.producers;\n if (toRemove !== undefined) {\n if (consumerIsLive(node)) {\n // For each stale link, we first unlink it from the producers list of consumers\n do {\n toRemove = producerRemoveLiveConsumerLink(toRemove);\n } while (toRemove !== undefined);\n }\n\n // Now, we can truncate the producers list to remove all stale links.\n if (producersTail !== undefined) {\n producersTail.nextProducer = undefined;\n } else {\n node.producers = undefined;\n }\n }\n}\n\n/**\n * Determine whether this consumer has any dependencies which have changed since the last time\n * they were read.\n */\nexport function consumerPollProducersForChange(node: ReactiveNode): boolean {\n // Poll producers for change.\n for (let link = node.producers; link !== undefined; link = link.nextProducer) {\n const producer = link.producer;\n const seenVersion = link.lastReadVersion;\n\n // First check the versions. A mismatch means that the producer's value is known to have\n // changed since the last time we read it.\n if (seenVersion !== producer.version) {\n return true;\n }\n\n // The producer's version is the same as the last time we read it, but it might itself be\n // stale. Force the producer to recompute its version (calculating a new value if necessary).\n producerUpdateValueVersion(producer);\n\n // Now when we do this check, `producer.version` is guaranteed to be up to date, so if the\n // versions still match then it has not changed since the last time we read it.\n if (seenVersion !== producer.version) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Disconnect this consumer from the graph.\n */\nexport function consumerDestroy(node: ReactiveNode): void {\n if (consumerIsLive(node)) {\n // Drop all connections from the graph to this node.\n let link = node.producers;\n while (link !== undefined) {\n link = producerRemoveLiveConsumerLink(link);\n }\n }\n\n // Truncate all the linked lists to drop all connection from this node to the graph.\n node.producers = undefined;\n node.producersTail = undefined;\n node.consumers = undefined;\n node.consumersTail = undefined;\n}\n\n/**\n * Add `consumer` as a live consumer of this node.\n *\n * Note that this operation is potentially transitive. If this node becomes live, then it becomes\n * a live consumer of all of its current producers.\n */\nfunction producerAddLiveConsumer(node: ReactiveNode, link: ReactiveLink): void {\n const consumersTail = node.consumersTail;\n const wasLive = consumerIsLive(node);\n if (consumersTail !== undefined) {\n link.nextConsumer = consumersTail.nextConsumer;\n consumersTail.nextConsumer = link;\n } else {\n link.nextConsumer = undefined;\n node.consumers = link;\n }\n link.prevConsumer = consumersTail;\n node.consumersTail = link;\n if (!wasLive) {\n for (\n let link: ReactiveLink | undefined = node.producers;\n link !== undefined;\n link = link.nextProducer\n ) {\n producerAddLiveConsumer(link.producer, link);\n }\n }\n}\n\nfunction producerRemoveLiveConsumerLink(link: ReactiveLink): ReactiveLink | undefined {\n const producer = link.producer;\n const nextProducer = link.nextProducer;\n const nextConsumer = link.nextConsumer;\n const prevConsumer = link.prevConsumer;\n link.nextConsumer = undefined;\n link.prevConsumer = undefined;\n if (nextConsumer !== undefined) {\n nextConsumer.prevConsumer = prevConsumer;\n } else {\n producer.consumersTail = prevConsumer;\n }\n if (prevConsumer !== undefined) {\n prevConsumer.nextConsumer = nextConsumer;\n } else {\n producer.consumers = nextConsumer;\n if (!consumerIsLive(producer)) {\n let producerLink = producer.producers;\n while (producerLink !== undefined) {\n producerLink = producerRemoveLiveConsumerLink(producerLink);\n }\n }\n }\n return nextProducer;\n}\n\nfunction consumerIsLive(node: ReactiveNode): boolean {\n return node.consumerIsAlwaysLive || node.consumers !== undefined;\n}\n\nexport function runPostProducerCreatedFn(node: ReactiveNode): void {\n postProducerCreatedFn?.(node);\n}\n\nexport function setPostProducerCreatedFn(fn: ReactiveHookFn | null): ReactiveHookFn | null {\n const prev = postProducerCreatedFn;\n postProducerCreatedFn = fn;\n return prev;\n}\n\n// While a ReactiveNode is recomputing, it may not have destroyed previous links\n// This allows us to check if a given link will be destroyed by a reactivenode if it were to finish running immediately without accesing any more producers\nfunction isValidLink(checkLink: ReactiveLink, consumer: ReactiveNode): boolean {\n const producersTail = consumer.producersTail;\n if (producersTail !== undefined) {\n let link = consumer.producers!;\n do {\n if (link === checkLink) {\n return true;\n }\n if (link === producersTail) {\n break;\n }\n link = link.nextProducer!;\n } while (link !== undefined);\n }\n return false;\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\n/**\n * A comparison function which can determine if two values are equal.\n */\nexport type ValueEqualityFn<T> = (a: T, b: T) => boolean;\n\n/**\n * The default equality function used for `signal` and `computed`, which uses referential equality.\n */\nexport function defaultEquals<T>(a: T, b: T) {\n return Object.is(a, b);\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {defaultEquals, ValueEqualityFn} from './equality';\nimport {\n consumerAfterComputation,\n consumerBeforeComputation,\n producerAccessed,\n producerUpdateValueVersion,\n REACTIVE_NODE,\n ReactiveNode,\n runPostProducerCreatedFn,\n setActiveConsumer,\n SIGNAL,\n} from './graph';\n\n// Required as the signals library is in a separate package, so we need to explicitly ensure the\n// global `ngDevMode` type is defined.\ndeclare const ngDevMode: boolean | undefined;\n\n/**\n * A computation, which derives a value from a declarative reactive expression.\n *\n * `Computed`s are both producers and consumers of reactivity.\n */\nexport interface ComputedNode<T> extends ReactiveNode {\n /**\n * Current value of the computation, or one of the sentinel values above (`UNSET`, `COMPUTING`,\n * `ERROR`).\n */\n value: T;\n\n /**\n * If `value` is `ERRORED`, the error caught from the last computation attempt which will\n * be re-thrown.\n */\n error: unknown;\n\n /**\n * The computation function which will produce a new value.\n */\n computation: () => T;\n\n equal: ValueEqualityFn<T>;\n}\n\nexport type ComputedGetter<T> = (() => T) & {\n [SIGNAL]: ComputedNode<T>;\n};\n\n/**\n * Create a computed signal which derives a reactive value from an expression.\n */\nexport function createComputed<T>(\n computation: () => T,\n equal?: ValueEqualityFn<T>,\n): ComputedGetter<T> {\n const node: ComputedNode<T> = Object.create(COMPUTED_NODE);\n node.computation = computation;\n\n if (equal !== undefined) {\n node.equal = equal;\n }\n\n const computed = () => {\n // Check if the value needs updating before returning it.\n producerUpdateValueVersion(node);\n\n // Record that someone looked at this signal.\n producerAccessed(node);\n\n if (node.value === ERRORED) {\n throw node.error;\n }\n\n return node.value;\n };\n\n (computed as ComputedGetter<T>)[SIGNAL] = node;\n if (typeof ngDevMode !== 'undefined' && ngDevMode) {\n computed.toString = () =>\n `[Computed${node.debugName ? ' (' + node.debugName + ')' : ''}: ${String(node.value)}]`;\n }\n\n runPostProducerCreatedFn(node);\n\n return computed as unknown as ComputedGetter<T>;\n}\n\n/**\n * A dedicated symbol used before a computed value has been calculated for the first time.\n * Explicitly typed as `any` so we can use it as signal's value.\n */\nexport const UNSET: any = /* @__PURE__ */ Symbol('UNSET');\n\n/**\n * A dedicated symbol used in place of a computed signal value to indicate that a given computation\n * is in progress. Used to detect cycles in computation chains.\n * Explicitly typed as `any` so we can use it as signal's value.\n */\nexport const COMPUTING: any = /* @__PURE__ */ Symbol('COMPUTING');\n\n/**\n * A dedicated symbol used in place of a computed signal value to indicate that a given computation\n * failed. The thrown error is cached until the computation gets dirty again.\n * Explicitly typed as `any` so we can use it as signal's value.\n */\nexport const ERRORED: any = /* @__PURE__ */ Symbol('ERRORED');\n\n// Note: Using an IIFE here to ensure that the spread assignment is not considered\n// a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.\nconst COMPUTED_NODE: Omit<ComputedNode<unknown>, 'computation'> = /* @__PURE__ */ (() => {\n return {\n ...REACTIVE_NODE,\n value: UNSET,\n dirty: true,\n error: null,\n equal: defaultEquals,\n kind: 'computed',\n\n producerMustRecompute(node: ComputedNode<unknown>): boolean {\n // Force a recomputation if there's no current value, or if the current value is in the\n // process of being calculated (which should throw an error).\n return node.value === UNSET || node.value === COMPUTING;\n },\n\n producerRecomputeValue(node: ComputedNode<unknown>): void {\n if (node.value === COMPUTING) {\n // Our computation somehow led to a cyclic read of itself.\n throw new Error(\n typeof ngDevMode !== 'undefined' && ngDevMode ? 'Detected cycle in computations.' : '',\n );\n }\n\n const oldValue = node.value;\n node.value = COMPUTING;\n\n const prevConsumer = consumerBeforeComputation(node);\n let newValue: unknown;\n let wasEqual = false;\n try {\n newValue = node.computation();\n // We want to mark this node as errored if calling `equal` throws; however, we don't want\n // to track any reactive reads inside `equal`.\n setActiveConsumer(null);\n wasEqual =\n oldValue !== UNSET &&\n oldValue !== ERRORED &&\n newValue !== ERRORED &&\n node.equal(oldValue, newValue);\n } catch (err) {\n newValue = ERRORED;\n node.error = err;\n } finally {\n consumerAfterComputation(node, prevConsumer);\n }\n\n if (wasEqual) {\n // No change to `valueVersion` - old and new values are\n // semantically equivalent.\n node.value = oldValue;\n return;\n }\n\n node.value = newValue;\n node.version++;\n },\n };\n})();\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport type {SignalNode} from './signal';\n\nfunction defaultThrowError(): never {\n throw new Error();\n}\n\nlet throwInvalidWriteToSignalErrorFn: <T>(node: SignalNode<T>) => never = defaultThrowError;\n\nexport function throwInvalidWriteToSignalError<T>(node: SignalNode<T>) {\n throwInvalidWriteToSignalErrorFn(node);\n}\n\nexport function setThrowInvalidWriteToSignalError(fn: <T>(node: SignalNode<T>) => never): void {\n throwInvalidWriteToSignalErrorFn = fn;\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {defaultEquals, ValueEqualityFn} from './equality';\nimport {throwInvalidWriteToSignalError} from './errors';\nimport {\n producerAccessed,\n producerIncrementEpoch,\n producerNotifyConsumers,\n producerUpdatesAllowed,\n REACTIVE_NODE,\n ReactiveNode,\n ReactiveHookFn,\n runPostProducerCreatedFn,\n SIGNAL,\n} from './graph';\n\n// Required as the signals library is in a separate package, so we need to explicitly ensure the\n// global `ngDevMode` type is defined.\ndeclare const ngDevMode: boolean | undefined;\n\n/**\n * If set, called after `WritableSignal`s are updated.\n *\n * This hook can be used to achieve various effects, such as running effects synchronously as part\n * of setting a signal.\n */\nlet postSignalSetFn: ReactiveHookFn | null = null;\n\nexport interface SignalNode<T> extends ReactiveNode {\n value: T;\n equal: ValueEqualityFn<T>;\n}\n\nexport type SignalBaseGetter<T> = (() => T) & {readonly [SIGNAL]: unknown};\nexport type SignalSetter<T> = (newValue: T) => void;\nexport type SignalUpdater<T> = (updateFn: (value: T) => T) => void;\n\n// Note: Closure *requires* this to be an `interface` and not a type, which is why the\n// `SignalBaseGetter` type exists to provide the correct shape.\nexport interface SignalGetter<T> extends SignalBaseGetter<T> {\n readonly [SIGNAL]: SignalNode<T>;\n}\n\n/**\n * Creates a `Signal` getter, setter, and updater function.\n */\nexport function createSignal<T>(\n initialValue: T,\n equal?: ValueEqualityFn<T>,\n): [SignalGetter<T>, SignalSetter<T>, SignalUpdater<T>] {\n const node: SignalNode<T> = Object.create(SIGNAL_NODE);\n node.value = initialValue;\n if (equal !== undefined) {\n node.equal = equal;\n }\n const getter = (() => signalGetFn(node)) as SignalGetter<T>;\n (getter as any)[SIGNAL] = node;\n if (typeof ngDevMode !== 'undefined' && ngDevMode) {\n getter.toString = () =>\n `[Signal${node.debugName ? ' (' + node.debugName + ')' : ''}: ${String(node.value)}]`;\n }\n\n runPostProducerCreatedFn(node);\n const set = (newValue: T) => signalSetFn(node, newValue);\n const update = (updateFn: (value: T) => T) => signalUpdateFn(node, updateFn);\n return [getter, set, update];\n}\n\nexport function setPostSignalSetFn(fn: ReactiveHookFn | null): ReactiveHookFn | null {\n const prev = postSignalSetFn;\n postSignalSetFn = fn;\n return prev;\n}\n\nexport function signalGetFn<T>(node: SignalNode<T>): T {\n producerAccessed(node);\n return node.value;\n}\n\nexport function signalSetFn<T>(node: SignalNode<T>, newValue: T) {\n if (!producerUpdatesAllowed()) {\n throwInvalidWriteToSignalError(node);\n }\n\n if (!node.equal(node.value, newValue)) {\n node.value = newValue;\n signalValueChanged(node);\n }\n}\n\nexport function signalUpdateFn<T>(node: SignalNode<T>, updater: (value: T) => T): void {\n if (!producerUpdatesAllowed()) {\n throwInvalidWriteToSignalError(node);\n }\n\n signalSetFn(node, updater(node.value));\n}\n\nexport function runPostSignalSetFn<T>(node: SignalNode<T>): void {\n postSignalSetFn?.(node);\n}\n\n// Note: Using an IIFE here to ensure that the spread assignment is not considered\n// a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.\nexport const SIGNAL_NODE: SignalNode<unknown> = /* @__PURE__ */ (() => {\n return {\n ...REACTIVE_NODE,\n equal: defaultEquals,\n value: undefined,\n kind: 'signal',\n };\n})();\n\nfunction signalValueChanged<T>(node: SignalNode<T>): void {\n node.version++;\n producerIncrementEpoch();\n producerNotifyConsumers(node);\n postSignalSetFn?.(node);\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {\n consumerAfterComputation,\n consumerBeforeComputation,\n consumerPollProducersForChange,\n REACTIVE_NODE,\n ReactiveNode,\n} from './graph';\n\n/**\n * An effect can, optionally, register a cleanup function. If registered, the cleanup is executed\n * before the next effect run. The cleanup function makes it possible to \"cancel\" any work that the\n * previous effect run might have started.\n */\nexport type EffectCleanupFn = () => void;\n\n/**\n * A callback passed to the effect function that makes it possible to register cleanup logic.\n */\nexport type EffectCleanupRegisterFn = (cleanupFn: EffectCleanupFn) => void;\n\nexport interface BaseEffectNode extends ReactiveNode {\n fn: () => void;\n destroy(): void;\n cleanup(): void;\n run(): void;\n}\n\nexport const BASE_EFFECT_NODE: Omit<BaseEffectNode, 'fn' | 'destroy' | 'cleanup' | 'run'> =\n /* @__PURE__ */ (() => ({\n ...REACTIVE_NODE,\n consumerIsAlwaysLive: true,\n consumerAllowSignalWrites: true,\n dirty: true,\n kind: 'effect',\n }))();\n\nexport function runEffect(node: BaseEffectNode) {\n node.dirty = false;\n if (node.version > 0 && !consumerPollProducersForChange(node)) {\n return;\n }\n node.version++;\n const prevNode = consumerBeforeComputation(node);\n try {\n node.cleanup();\n node.fn();\n } finally {\n consumerAfterComputation(node, prevNode);\n }\n}\n"],"names":["activeConsumer","inNotificationPhase","epoch","postProducerCreatedFn","SIGNAL","Symbol","setActiveConsumer","consumer","prev","getActiveConsumer","isInNotificationPhase","isReactive","value","undefined","REACTIVE_NODE","version","lastCleanEpoch","dirty","producers","producersTail","consumers","consumersTail","recomputing","consumerAllowSignalWrites","consumerIsAlwaysLive","kind","producerMustRecompute","producerRecomputeValue","consumerMarkedDirty","consumerOnSignalRead","producerAccessed","node","Error","ngDevMode","prevProducerLink","producer","nextProducerLink","isRecomputing","nextProducer","lastReadVersion","prevConsumerLink","isValidLink","isLive","consumerIsLive","newLink","prevConsumer","nextConsumer","producerAddLiveConsumer","producerIncrementEpoch","producerUpdateValueVersion","consumerPollProducersForChange","producerMarkClean","producerNotifyConsumers","link","consumerMarkDirty","producerUpdatesAllowed","consumerBeforeComputation","resetConsumerBeforeComputation","consumerAfterComputation","finalizeConsumerAfterComputation","toRemove","producerRemoveLiveConsumerLink","seenVersion","consumerDestroy","wasLive","producerLink","runPostProducerCreatedFn","setPostProducerCreatedFn","fn","checkLink","defaultEquals","a","b","Object","is","createComputed","computation","equal","create","COMPUTED_NODE","computed","ERRORED","error","toString","debugName","String","UNSET","COMPUTING","oldValue","newValue","wasEqual","err","defaultThrowError","throwInvalidWriteToSignalErrorFn","throwInvalidWriteToSignalError","setThrowInvalidWriteToSignalError","postSignalSetFn","createSignal","initialValue","SIGNAL_NODE","getter","signalGetFn","set","signalSetFn","update","updateFn","signalUpdateFn","setPostSignalSetFn","signalValueChanged","updater","runPostSignalSetFn","BASE_EFFECT_NODE","runEffect","prevNode","cleanup"],"mappings":"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"} | ||
| {"version":3,"file":"_effect-chunk.mjs","sources":["../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/core/primitives/signals/src/graph.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/core/primitives/signals/src/equality.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/core/primitives/signals/src/computed.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/core/primitives/signals/src/errors.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/core/primitives/signals/src/signal.ts","../../../../../k8-fastbuild-ST-fdfa778d11ba/bin/packages/core/primitives/signals/src/effect.ts"],"sourcesContent":["/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\n// Required as the signals library is in a separate package, so we need to explicitly ensure the\n// global `ngDevMode` type is defined.\ndeclare const ngDevMode: boolean | undefined;\n\n/**\n * The currently active consumer `ReactiveNode`, if running code in a reactive context.\n *\n * Change this via `setActiveConsumer`.\n */\nlet activeConsumer: ReactiveNode | null = null;\nlet inNotificationPhase = false;\n\nexport type Version = number & {__brand: 'Version'};\n\n/**\n * Global epoch counter. Incremented whenever a source signal is set.\n */\nlet epoch: Version = 1 as Version;\n\nexport type ReactiveHookFn = (node: ReactiveNode) => void;\n\n/**\n * If set, called after a producer `ReactiveNode` is created.\n */\nlet postProducerCreatedFn: ReactiveHookFn | null = null;\n\n/**\n * Symbol used to tell `Signal`s apart from other functions.\n *\n * This can be used to auto-unwrap signals in various cases, or to auto-wrap non-signal values.\n */\nexport const SIGNAL: unique symbol = /* @__PURE__ */ Symbol('SIGNAL');\n\nexport function setActiveConsumer(consumer: ReactiveNode | null): ReactiveNode | null {\n const prev = activeConsumer;\n activeConsumer = consumer;\n return prev;\n}\n\nexport function getActiveConsumer(): ReactiveNode | null {\n return activeConsumer;\n}\n\nexport function isInNotificationPhase(): boolean {\n return inNotificationPhase;\n}\n\nexport interface Reactive {\n [SIGNAL]: ReactiveNode;\n}\n\nexport function isReactive(value: unknown): value is Reactive {\n return (value as Partial<Reactive>)[SIGNAL] !== undefined;\n}\n\nexport const REACTIVE_NODE: ReactiveNode = {\n version: 0 as Version,\n lastCleanEpoch: 0 as Version,\n dirty: false,\n producers: undefined,\n producersTail: undefined,\n consumers: undefined,\n consumersTail: undefined,\n recomputing: false,\n consumerAllowSignalWrites: false,\n consumerIsAlwaysLive: false,\n kind: 'unknown',\n producerMustRecompute: () => false,\n producerRecomputeValue: () => {},\n consumerMarkedDirty: () => {},\n consumerOnSignalRead: () => {},\n};\n\ninterface ReactiveLink {\n producer: ReactiveNode;\n consumer: ReactiveNode;\n\n /**\n * Stores the epoch that holds when this link was observed, allowing subsequent observations of the same producer to\n * realize that there's an existing link, avoiding the creation of a new, redundant link. A value of `null` indicates\n * that the link cannot be assumed to be valid based on the epoch counter.\n */\n knownValidAtEpoch: Version | null;\n lastReadVersion: number;\n prevConsumer: ReactiveLink | undefined;\n nextConsumer: ReactiveLink | undefined;\n nextProducer: ReactiveLink | undefined;\n}\n\nexport type ReactiveNodeKind =\n | 'signal'\n | 'computed'\n | 'effect'\n | 'template'\n | 'linkedSignal'\n | 'afterRenderEffectPhase'\n | 'childSignalProp' // Represents a signal passed as a prop to a child component in a CoW app\n | 'unknown';\n\n/**\n * A producer and/or consumer which participates in the reactive graph.\n *\n * Producer `ReactiveNode`s which are accessed when a consumer `ReactiveNode` is the\n * `activeConsumer` are tracked as dependencies of that consumer.\n *\n * Certain consumers are also tracked as \"live\" consumers and create edges in the other direction,\n * from producer to consumer. These edges are used to propagate change notifications when a\n * producer's value is updated.\n *\n * A `ReactiveNode` may be both a producer and consumer.\n */\nexport interface ReactiveNode {\n /**\n * Version of the value that this node produces.\n *\n * This is incremented whenever a new value is produced by this node which is not equal to the\n * previous value (by whatever definition of equality is in use).\n */\n version: Version;\n\n /**\n * Epoch at which this node is verified to be clean.\n *\n * This allows skipping of some polling operations in the case where no signals have been set\n * since this node was last read.\n */\n lastCleanEpoch: Version;\n\n /**\n * Whether this node (in its consumer capacity) is dirty.\n *\n * Only live consumers become dirty, when receiving a change notification from a dependency\n * producer.\n */\n dirty: boolean;\n\n /**\n * Whether this node is currently rebuilding its producer list.\n */\n recomputing: boolean;\n\n /**\n * Producers which are dependencies of this consumer.\n */\n producers: ReactiveLink | undefined;\n\n /**\n * Points to the last linked list node in the `producers` linked list.\n *\n * When this node is recomputing, this is used to track the producers that we have accessed so far.\n */\n producersTail: ReactiveLink | undefined;\n\n /**\n * Linked list of consumers of this producer that are \"live\" (they require push notifications).\n *\n * The length of this list is effectively our reference count for this node.\n */\n consumers: ReactiveLink | undefined;\n consumersTail: ReactiveLink | undefined;\n\n /**\n * Whether writes to signals are allowed when this consumer is the `activeConsumer`.\n *\n * This is used to enforce guardrails such as preventing writes to writable signals in the\n * computation function of computed signals, which is supposed to be pure.\n */\n consumerAllowSignalWrites: boolean;\n\n readonly consumerIsAlwaysLive: boolean;\n\n /**\n * Tracks whether producers need to recompute their value independently of the reactive graph (for\n * example, if no initial value has been computed).\n */\n producerMustRecompute(node: unknown): boolean;\n producerRecomputeValue(node: unknown): void;\n consumerMarkedDirty(node: unknown): void;\n\n /**\n * Called when a signal is read within this consumer.\n */\n consumerOnSignalRead(node: unknown): void;\n\n /**\n * A debug name for the reactive node. Used in Angular DevTools to identify the node.\n */\n debugName?: string;\n\n /**\n * Kind of node. Example: 'signal', 'computed', 'input', 'effect'.\n *\n * ReactiveNode has this as 'unknown' by default, but derived node types should override this to\n * make available the kind of signal that particular instance of a ReactiveNode represents.\n *\n * Used in Angular DevTools to identify the kind of signal.\n */\n kind: ReactiveNodeKind;\n}\n\n/**\n * Called by implementations when a producer's signal is read.\n */\nexport function producerAccessed(node: ReactiveNode): void {\n if (inNotificationPhase) {\n throw new Error(\n typeof ngDevMode !== 'undefined' && ngDevMode\n ? `Assertion error: signal read during notification phase`\n : '',\n );\n }\n\n if (activeConsumer === null) {\n // Accessed outside of a reactive context, so nothing to record.\n return;\n }\n\n activeConsumer.consumerOnSignalRead(node);\n\n const prevProducerLink = activeConsumer.producersTail;\n\n // If the last producer we accessed is the same as the current one, we can skip adding a new\n // link\n if (prevProducerLink !== undefined && prevProducerLink.producer === node) {\n return;\n }\n\n let nextProducerLink: ReactiveLink | undefined = undefined;\n const isRecomputing = activeConsumer.recomputing;\n if (isRecomputing) {\n // If we're incrementally rebuilding the producers list, we want to check if the next producer\n // in the list is the same as the one we're trying to add.\n\n // If the previous producer is defined, then the next producer is just the one that follows it.\n // Otherwise, we should check the head of the producers list (the first node that we accessed the last time this consumer was run).\n nextProducerLink =\n prevProducerLink !== undefined ? prevProducerLink.nextProducer : activeConsumer.producers;\n if (nextProducerLink !== undefined && nextProducerLink.producer === node) {\n // If the next producer is the same as the one we're trying to add, we can just update the\n // last read version, update the tail of the producers list of this rerun, and return.\n activeConsumer.producersTail = nextProducerLink;\n nextProducerLink.lastReadVersion = node.version;\n nextProducerLink.knownValidAtEpoch = epoch;\n return;\n }\n }\n\n const prevConsumerLink = node.consumersTail;\n\n // If the producer we're accessing already has a link to this consumer, we can skip adding a new\n // link. This can short circuit the creation of a new link in the case where the consumer reads alternating ReactiveNodes\n if (\n prevConsumerLink !== undefined &&\n prevConsumerLink.consumer === activeConsumer &&\n (!isRecomputing || prevConsumerLink.knownValidAtEpoch === epoch)\n ) {\n return;\n }\n\n // If we got here, it means that we need to create a new link between the producer and the consumer.\n const isLive = consumerIsLive(activeConsumer);\n const newLink: ReactiveLink = {\n producer: node,\n consumer: activeConsumer,\n // instead of eagerly destroying the previous link, we delay until we've finished recomputing\n // the producers list, so that we can destroy all of the old links at once.\n nextProducer: nextProducerLink,\n // Don't set prevConsumer here — it's only meaningful when the link is part of\n // the producer's consumer list. producerAddLiveConsumer sets it correctly when\n // the link is actually inserted. Setting it eagerly would create a dangling\n // reference into the consumer list that prevents GC of removed entries.\n prevConsumer: undefined,\n knownValidAtEpoch: epoch,\n lastReadVersion: node.version,\n nextConsumer: undefined,\n };\n activeConsumer.producersTail = newLink;\n if (prevProducerLink !== undefined) {\n prevProducerLink.nextProducer = newLink;\n } else {\n activeConsumer.producers = newLink;\n }\n\n if (isLive) {\n producerAddLiveConsumer(node, newLink);\n }\n}\n\n/**\n * Increment the global epoch counter.\n *\n * Called by source producers (that is, not computeds) whenever their values change.\n */\nexport function producerIncrementEpoch(): void {\n epoch++;\n}\n\n/**\n * Ensure this producer's `version` is up-to-date.\n */\nexport function producerUpdateValueVersion(node: ReactiveNode): void {\n if (consumerIsLive(node) && !node.dirty) {\n // A live consumer will be marked dirty by producers, so a clean state means that its version\n // is guaranteed to be up-to-date.\n return;\n }\n\n if (!node.dirty && node.lastCleanEpoch === epoch) {\n // Even non-live consumers can skip polling if they previously found themselves to be clean at\n // the current epoch, since their dependencies could not possibly have changed (such a change\n // would've increased the epoch).\n return;\n }\n\n if (!node.producerMustRecompute(node) && !consumerPollProducersForChange(node)) {\n // None of our producers report a change since the last time they were read, so no\n // recomputation of our value is necessary, and we can consider ourselves clean.\n producerMarkClean(node);\n return;\n }\n\n node.producerRecomputeValue(node);\n\n // After recomputing the value, we're no longer dirty.\n producerMarkClean(node);\n}\n\n/**\n * Propagate a dirty notification to live consumers of this producer.\n */\nexport function producerNotifyConsumers(node: ReactiveNode): void {\n if (node.consumers === undefined) {\n return;\n }\n\n // Prevent signal reads when we're updating the graph\n const prev = inNotificationPhase;\n inNotificationPhase = true;\n try {\n for (\n let link: ReactiveLink | undefined = node.consumers;\n link !== undefined;\n link = link.nextConsumer\n ) {\n const consumer = link.consumer;\n if (!consumer.dirty) {\n consumerMarkDirty(consumer);\n }\n }\n } finally {\n inNotificationPhase = prev;\n }\n}\n\n/**\n * Whether this `ReactiveNode` in its producer capacity is currently allowed to initiate updates,\n * based on the current consumer context.\n */\nexport function producerUpdatesAllowed(): boolean {\n return activeConsumer?.consumerAllowSignalWrites !== false;\n}\n\nexport function consumerMarkDirty(node: ReactiveNode): void {\n node.dirty = true;\n producerNotifyConsumers(node);\n node.consumerMarkedDirty?.(node);\n}\n\nexport function producerMarkClean(node: ReactiveNode): void {\n node.dirty = false;\n node.lastCleanEpoch = epoch;\n}\n\n/**\n * Prepare this consumer to run a computation in its reactive context and set\n * it as the active consumer.\n *\n * Must be called by subclasses which represent reactive computations, before those computations\n * begin.\n */\nexport function consumerBeforeComputation(node: ReactiveNode | null): ReactiveNode | null {\n if (node) resetConsumerBeforeComputation(node);\n\n return setActiveConsumer(node);\n}\n\n/**\n * Prepare this consumer to run a computation in its reactive context.\n *\n * We expose this mainly for code where we manually batch effects into a single\n * consumer. In those cases we may wish to \"reopen\" a consumer multiple times\n * in initial render before finalizing it. Most code should just call\n * `consumerBeforeComputation` instead of calling this directly.\n */\nexport function resetConsumerBeforeComputation(node: ReactiveNode): void {\n // Clear link validity state before running a computation, such that links that were captured in a prior computation\n // (which may have happened in the same epoch) are not mistakenly considered valid. This is only necessary if any of\n // the links has `knownValidAtEpoch` equal to the current epoch, for which the producer that was accessed last is used\n // as proxy: any earlier producers cannot exceed its epoch.\n if (node.producersTail?.knownValidAtEpoch === epoch) {\n let producer = node.producers;\n while (producer !== undefined) {\n producer.knownValidAtEpoch = null;\n producer = producer.nextProducer;\n }\n }\n\n node.producersTail = undefined;\n node.recomputing = true;\n}\n\n/**\n * Finalize this consumer's state and set previous consumer as the active consumer after a\n * reactive computation has run.\n *\n * Must be called by subclasses which represent reactive computations, after those computations\n * have finished.\n */\nexport function consumerAfterComputation(\n node: ReactiveNode | null,\n prevConsumer: ReactiveNode | null,\n): void {\n setActiveConsumer(prevConsumer);\n\n if (node) finalizeConsumerAfterComputation(node);\n}\n\n/**\n * Finalize this consumer's state after a reactive computation has run.\n *\n * We expose this mainly for code where we manually batch effects into a single\n * consumer. In those cases we may wish to \"reopen\" a consumer multiple times\n * in initial render before finalizing it. Most code should just call\n * `consumerAfterComputation` instead of calling this directly.\n */\nexport function finalizeConsumerAfterComputation(node: ReactiveNode): void {\n node.recomputing = false;\n\n // We've finished incrementally rebuilding the producers list, now if there are any producers\n // that are after producersTail, they are stale and should be removed.\n const producersTail = node.producersTail as ReactiveLink | undefined;\n let toRemove = producersTail !== undefined ? producersTail.nextProducer : node.producers;\n if (toRemove !== undefined) {\n if (consumerIsLive(node)) {\n // For each stale link, we first unlink it from the producers list of consumers\n do {\n toRemove = producerRemoveLiveConsumerLink(toRemove);\n } while (toRemove !== undefined);\n }\n\n // Now, we can truncate the producers list to remove all stale links.\n if (producersTail !== undefined) {\n producersTail.nextProducer = undefined;\n } else {\n node.producers = undefined;\n }\n }\n}\n\n/**\n * Determine whether this consumer has any dependencies which have changed since the last time\n * they were read.\n */\nexport function consumerPollProducersForChange(node: ReactiveNode): boolean {\n // Poll producers for change.\n for (let link = node.producers; link !== undefined; link = link.nextProducer) {\n const producer = link.producer;\n const seenVersion = link.lastReadVersion;\n\n // First check the versions. A mismatch means that the producer's value is known to have\n // changed since the last time we read it.\n if (seenVersion !== producer.version) {\n return true;\n }\n\n // The producer's version is the same as the last time we read it, but it might itself be\n // stale. Force the producer to recompute its version (calculating a new value if necessary).\n producerUpdateValueVersion(producer);\n\n // Now when we do this check, `producer.version` is guaranteed to be up to date, so if the\n // versions still match then it has not changed since the last time we read it.\n if (seenVersion !== producer.version) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Disconnect this consumer from the graph.\n */\nexport function consumerDestroy(node: ReactiveNode): void {\n if (consumerIsLive(node)) {\n // Drop all connections from the graph to this node.\n let link = node.producers;\n while (link !== undefined) {\n link = producerRemoveLiveConsumerLink(link);\n }\n }\n\n // Truncate all the linked lists to drop all connection from this node to the graph.\n node.producers = undefined;\n node.producersTail = undefined;\n node.consumers = undefined;\n node.consumersTail = undefined;\n}\n\n/**\n * Add `consumer` as a live consumer of this node.\n *\n * Note that this operation is potentially transitive. If this node becomes live, then it becomes\n * a live consumer of all of its current producers.\n */\nfunction producerAddLiveConsumer(node: ReactiveNode, link: ReactiveLink): void {\n const consumersTail = node.consumersTail;\n const wasLive = consumerIsLive(node);\n if (consumersTail !== undefined) {\n link.nextConsumer = consumersTail.nextConsumer;\n consumersTail.nextConsumer = link;\n } else {\n link.nextConsumer = undefined;\n node.consumers = link;\n }\n link.prevConsumer = consumersTail;\n node.consumersTail = link;\n if (!wasLive) {\n for (\n let link: ReactiveLink | undefined = node.producers;\n link !== undefined;\n link = link.nextProducer\n ) {\n producerAddLiveConsumer(link.producer, link);\n }\n }\n}\n\nfunction producerRemoveLiveConsumerLink(link: ReactiveLink): ReactiveLink | undefined {\n const producer = link.producer;\n const nextProducer = link.nextProducer;\n const nextConsumer = link.nextConsumer;\n const prevConsumer = link.prevConsumer;\n link.nextConsumer = undefined;\n link.prevConsumer = undefined;\n if (nextConsumer !== undefined) {\n nextConsumer.prevConsumer = prevConsumer;\n } else {\n producer.consumersTail = prevConsumer;\n }\n if (prevConsumer !== undefined) {\n prevConsumer.nextConsumer = nextConsumer;\n } else {\n producer.consumers = nextConsumer;\n if (!consumerIsLive(producer)) {\n let producerLink = producer.producers;\n while (producerLink !== undefined) {\n producerLink = producerRemoveLiveConsumerLink(producerLink);\n }\n }\n }\n return nextProducer;\n}\n\nfunction consumerIsLive(node: ReactiveNode): boolean {\n return node.consumerIsAlwaysLive || node.consumers !== undefined;\n}\n\nexport function runPostProducerCreatedFn(node: ReactiveNode): void {\n postProducerCreatedFn?.(node);\n}\n\nexport function setPostProducerCreatedFn(fn: ReactiveHookFn | null): ReactiveHookFn | null {\n const prev = postProducerCreatedFn;\n postProducerCreatedFn = fn;\n return prev;\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\n/**\n * A comparison function which can determine if two values are equal.\n */\nexport type ValueEqualityFn<T> = (a: T, b: T) => boolean;\n\n/**\n * The default equality function used for `signal` and `computed`, which uses referential equality.\n */\nexport function defaultEquals<T>(a: T, b: T) {\n return Object.is(a, b);\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {defaultEquals, ValueEqualityFn} from './equality';\nimport {\n consumerAfterComputation,\n consumerBeforeComputation,\n producerAccessed,\n producerUpdateValueVersion,\n REACTIVE_NODE,\n ReactiveNode,\n runPostProducerCreatedFn,\n setActiveConsumer,\n SIGNAL,\n} from './graph';\n\n// Required as the signals library is in a separate package, so we need to explicitly ensure the\n// global `ngDevMode` type is defined.\ndeclare const ngDevMode: boolean | undefined;\n\n/**\n * A computation, which derives a value from a declarative reactive expression.\n *\n * `Computed`s are both producers and consumers of reactivity.\n */\nexport interface ComputedNode<T> extends ReactiveNode {\n /**\n * Current value of the computation, or one of the sentinel values above (`UNSET`, `COMPUTING`,\n * `ERROR`).\n */\n value: T;\n\n /**\n * If `value` is `ERRORED`, the error caught from the last computation attempt which will\n * be re-thrown.\n */\n error: unknown;\n\n /**\n * The computation function which will produce a new value.\n */\n computation: () => T;\n\n equal: ValueEqualityFn<T>;\n}\n\nexport type ComputedGetter<T> = (() => T) & {\n [SIGNAL]: ComputedNode<T>;\n};\n\n/**\n * Create a computed signal which derives a reactive value from an expression.\n */\nexport function createComputed<T>(\n computation: () => T,\n equal?: ValueEqualityFn<T>,\n): ComputedGetter<T> {\n const node: ComputedNode<T> = Object.create(COMPUTED_NODE);\n node.computation = computation;\n\n if (equal !== undefined) {\n node.equal = equal;\n }\n\n const computed = () => {\n // Check if the value needs updating before returning it.\n producerUpdateValueVersion(node);\n\n // Record that someone looked at this signal.\n producerAccessed(node);\n\n if (node.value === ERRORED) {\n throw node.error;\n }\n\n return node.value;\n };\n\n (computed as ComputedGetter<T>)[SIGNAL] = node;\n if (typeof ngDevMode !== 'undefined' && ngDevMode) {\n computed.toString = () =>\n `[Computed${node.debugName ? ' (' + node.debugName + ')' : ''}: ${String(node.value)}]`;\n }\n\n runPostProducerCreatedFn(node);\n\n return computed as unknown as ComputedGetter<T>;\n}\n\n/**\n * A dedicated symbol used before a computed value has been calculated for the first time.\n * Explicitly typed as `any` so we can use it as signal's value.\n */\nexport const UNSET: any = /* @__PURE__ */ Symbol('UNSET');\n\n/**\n * A dedicated symbol used in place of a computed signal value to indicate that a given computation\n * is in progress. Used to detect cycles in computation chains.\n * Explicitly typed as `any` so we can use it as signal's value.\n */\nexport const COMPUTING: any = /* @__PURE__ */ Symbol('COMPUTING');\n\n/**\n * A dedicated symbol used in place of a computed signal value to indicate that a given computation\n * failed. The thrown error is cached until the computation gets dirty again.\n * Explicitly typed as `any` so we can use it as signal's value.\n */\nexport const ERRORED: any = /* @__PURE__ */ Symbol('ERRORED');\n\n// Note: Using an IIFE here to ensure that the spread assignment is not considered\n// a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.\nconst COMPUTED_NODE: Omit<ComputedNode<unknown>, 'computation'> = /* @__PURE__ */ (() => {\n return {\n ...REACTIVE_NODE,\n value: UNSET,\n dirty: true,\n error: null,\n equal: defaultEquals,\n kind: 'computed',\n\n producerMustRecompute(node: ComputedNode<unknown>): boolean {\n // Force a recomputation if there's no current value, or if the current value is in the\n // process of being calculated (which should throw an error).\n return node.value === UNSET || node.value === COMPUTING;\n },\n\n producerRecomputeValue(node: ComputedNode<unknown>): void {\n if (node.value === COMPUTING) {\n // Our computation somehow led to a cyclic read of itself.\n throw new Error(\n typeof ngDevMode !== 'undefined' && ngDevMode ? 'Detected cycle in computations.' : '',\n );\n }\n\n const oldValue = node.value;\n node.value = COMPUTING;\n\n const prevConsumer = consumerBeforeComputation(node);\n let newValue: unknown;\n let wasEqual = false;\n try {\n newValue = node.computation();\n // We want to mark this node as errored if calling `equal` throws; however, we don't want\n // to track any reactive reads inside `equal`.\n setActiveConsumer(null);\n wasEqual =\n oldValue !== UNSET &&\n oldValue !== ERRORED &&\n newValue !== ERRORED &&\n node.equal(oldValue, newValue);\n } catch (err) {\n newValue = ERRORED;\n node.error = err;\n } finally {\n consumerAfterComputation(node, prevConsumer);\n }\n\n if (wasEqual) {\n // No change to `valueVersion` - old and new values are\n // semantically equivalent.\n node.value = oldValue;\n return;\n }\n\n node.value = newValue;\n node.version++;\n },\n };\n})();\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport type {SignalNode} from './signal';\n\nfunction defaultThrowError(): never {\n throw new Error();\n}\n\nlet throwInvalidWriteToSignalErrorFn: <T>(node: SignalNode<T>) => never = defaultThrowError;\n\nexport function throwInvalidWriteToSignalError<T>(node: SignalNode<T>) {\n throwInvalidWriteToSignalErrorFn(node);\n}\n\nexport function setThrowInvalidWriteToSignalError(fn: <T>(node: SignalNode<T>) => never): void {\n throwInvalidWriteToSignalErrorFn = fn;\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {defaultEquals, ValueEqualityFn} from './equality';\nimport {throwInvalidWriteToSignalError} from './errors';\nimport {\n producerAccessed,\n producerIncrementEpoch,\n producerNotifyConsumers,\n producerUpdatesAllowed,\n REACTIVE_NODE,\n ReactiveNode,\n ReactiveHookFn,\n runPostProducerCreatedFn,\n SIGNAL,\n} from './graph';\n\n// Required as the signals library is in a separate package, so we need to explicitly ensure the\n// global `ngDevMode` type is defined.\ndeclare const ngDevMode: boolean | undefined;\n\n/**\n * If set, called after `WritableSignal`s are updated.\n *\n * This hook can be used to achieve various effects, such as running effects synchronously as part\n * of setting a signal.\n */\nlet postSignalSetFn: ReactiveHookFn | null = null;\n\nexport interface SignalNode<T> extends ReactiveNode {\n value: T;\n equal: ValueEqualityFn<T>;\n}\n\nexport type SignalBaseGetter<T> = (() => T) & {readonly [SIGNAL]: unknown};\nexport type SignalSetter<T> = (newValue: T) => void;\nexport type SignalUpdater<T> = (updateFn: (value: T) => T) => void;\n\n// Note: Closure *requires* this to be an `interface` and not a type, which is why the\n// `SignalBaseGetter` type exists to provide the correct shape.\nexport interface SignalGetter<T> extends SignalBaseGetter<T> {\n readonly [SIGNAL]: SignalNode<T>;\n}\n\n/**\n * Creates a `Signal` getter, setter, and updater function.\n */\nexport function createSignal<T>(\n initialValue: T,\n equal?: ValueEqualityFn<T>,\n): [SignalGetter<T>, SignalSetter<T>, SignalUpdater<T>] {\n const node: SignalNode<T> = Object.create(SIGNAL_NODE);\n node.value = initialValue;\n if (equal !== undefined) {\n node.equal = equal;\n }\n const getter = (() => signalGetFn(node)) as SignalGetter<T>;\n (getter as any)[SIGNAL] = node;\n if (typeof ngDevMode !== 'undefined' && ngDevMode) {\n getter.toString = () =>\n `[Signal${node.debugName ? ' (' + node.debugName + ')' : ''}: ${String(node.value)}]`;\n }\n\n runPostProducerCreatedFn(node);\n const set = (newValue: T) => signalSetFn(node, newValue);\n const update = (updateFn: (value: T) => T) => signalUpdateFn(node, updateFn);\n return [getter, set, update];\n}\n\nexport function setPostSignalSetFn(fn: ReactiveHookFn | null): ReactiveHookFn | null {\n const prev = postSignalSetFn;\n postSignalSetFn = fn;\n return prev;\n}\n\nexport function signalGetFn<T>(node: SignalNode<T>): T {\n producerAccessed(node);\n return node.value;\n}\n\nexport function signalSetFn<T>(node: SignalNode<T>, newValue: T) {\n if (!producerUpdatesAllowed()) {\n throwInvalidWriteToSignalError(node);\n }\n\n if (!node.equal(node.value, newValue)) {\n node.value = newValue;\n signalValueChanged(node);\n }\n}\n\nexport function signalUpdateFn<T>(node: SignalNode<T>, updater: (value: T) => T): void {\n if (!producerUpdatesAllowed()) {\n throwInvalidWriteToSignalError(node);\n }\n\n signalSetFn(node, updater(node.value));\n}\n\nexport function runPostSignalSetFn<T>(node: SignalNode<T>): void {\n postSignalSetFn?.(node);\n}\n\n// Note: Using an IIFE here to ensure that the spread assignment is not considered\n// a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.\nexport const SIGNAL_NODE: SignalNode<unknown> = /* @__PURE__ */ (() => {\n return {\n ...REACTIVE_NODE,\n equal: defaultEquals,\n value: undefined,\n kind: 'signal',\n };\n})();\n\nfunction signalValueChanged<T>(node: SignalNode<T>): void {\n node.version++;\n producerIncrementEpoch();\n producerNotifyConsumers(node);\n postSignalSetFn?.(node);\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {\n consumerAfterComputation,\n consumerBeforeComputation,\n consumerPollProducersForChange,\n REACTIVE_NODE,\n ReactiveNode,\n} from './graph';\n\n/**\n * An effect can, optionally, register a cleanup function. If registered, the cleanup is executed\n * before the next effect run. The cleanup function makes it possible to \"cancel\" any work that the\n * previous effect run might have started.\n */\nexport type EffectCleanupFn = () => void;\n\n/**\n * A callback passed to the effect function that makes it possible to register cleanup logic.\n */\nexport type EffectCleanupRegisterFn = (cleanupFn: EffectCleanupFn) => void;\n\nexport interface BaseEffectNode extends ReactiveNode {\n fn: () => void;\n destroy(): void;\n cleanup(): void;\n run(): void;\n}\n\nexport const BASE_EFFECT_NODE: Omit<BaseEffectNode, 'fn' | 'destroy' | 'cleanup' | 'run'> =\n /* @__PURE__ */ (() => ({\n ...REACTIVE_NODE,\n consumerIsAlwaysLive: true,\n consumerAllowSignalWrites: true,\n dirty: true,\n kind: 'effect',\n }))();\n\nexport function runEffect(node: BaseEffectNode) {\n node.dirty = false;\n if (node.version > 0 && !consumerPollProducersForChange(node)) {\n return;\n }\n node.version++;\n const prevNode = consumerBeforeComputation(node);\n try {\n node.cleanup();\n node.fn();\n } finally {\n consumerAfterComputation(node, prevNode);\n }\n}\n"],"names":["activeConsumer","inNotificationPhase","epoch","postProducerCreatedFn","SIGNAL","Symbol","setActiveConsumer","consumer","prev","getActiveConsumer","isInNotificationPhase","isReactive","value","undefined","REACTIVE_NODE","version","lastCleanEpoch","dirty","producers","producersTail","consumers","consumersTail","recomputing","consumerAllowSignalWrites","consumerIsAlwaysLive","kind","producerMustRecompute","producerRecomputeValue","consumerMarkedDirty","consumerOnSignalRead","producerAccessed","node","Error","ngDevMode","prevProducerLink","producer","nextProducerLink","isRecomputing","nextProducer","lastReadVersion","knownValidAtEpoch","prevConsumerLink","isLive","consumerIsLive","newLink","prevConsumer","nextConsumer","producerAddLiveConsumer","producerIncrementEpoch","producerUpdateValueVersion","consumerPollProducersForChange","producerMarkClean","producerNotifyConsumers","link","consumerMarkDirty","producerUpdatesAllowed","consumerBeforeComputation","resetConsumerBeforeComputation","consumerAfterComputation","finalizeConsumerAfterComputation","toRemove","producerRemoveLiveConsumerLink","seenVersion","consumerDestroy","wasLive","producerLink","runPostProducerCreatedFn","setPostProducerCreatedFn","fn","defaultEquals","a","b","Object","is","createComputed","computation","equal","create","COMPUTED_NODE","computed","ERRORED","error","toString","debugName","String","UNSET","COMPUTING","oldValue","newValue","wasEqual","err","defaultThrowError","throwInvalidWriteToSignalErrorFn","throwInvalidWriteToSignalError","setThrowInvalidWriteToSignalError","postSignalSetFn","createSignal","initialValue","SIGNAL_NODE","getter","signalGetFn","set","signalSetFn","update","updateFn","signalUpdateFn","setPostSignalSetFn","signalValueChanged","updater","runPostSignalSetFn","BASE_EFFECT_NODE","runEffect","prevNode","cleanup"],"mappings":";;;;;;AAiBA,IAAIA,cAAc,GAAwB,IAAI;AAC9C,IAAIC,mBAAmB,GAAG,KAAK;AAO/B,IAAIC,KAAK,GAAY,CAAY;AAOjC,IAAIC,qBAAqB,GAA0B,IAAI;MAO1CC,MAAM,kBAAkCC,MAAM,CAAC,QAAQ;AAE9D,SAAUC,iBAAiBA,CAACC,QAA6B,EAAA;EAC7D,MAAMC,IAAI,GAAGR,cAAc;AAC3BA,EAAAA,cAAc,GAAGO,QAAQ;AACzB,EAAA,OAAOC,IAAI;AACb;SAEgBC,iBAAiBA,GAAA;AAC/B,EAAA,OAAOT,cAAc;AACvB;SAEgBU,qBAAqBA,GAAA;AACnC,EAAA,OAAOT,mBAAmB;AAC5B;AAMM,SAAUU,UAAUA,CAACC,KAAc,EAAA;AACvC,EAAA,OAAQA,KAA2B,CAACR,MAAM,CAAC,KAAKS,SAAS;AAC3D;AAEO,MAAMC,aAAa,GAAiB;AACzCC,EAAAA,OAAO,EAAE,CAAY;AACrBC,EAAAA,cAAc,EAAE,CAAY;AAC5BC,EAAAA,KAAK,EAAE,KAAK;AACZC,EAAAA,SAAS,EAAEL,SAAS;AACpBM,EAAAA,aAAa,EAAEN,SAAS;AACxBO,EAAAA,SAAS,EAAEP,SAAS;AACpBQ,EAAAA,aAAa,EAAER,SAAS;AACxBS,EAAAA,WAAW,EAAE,KAAK;AAClBC,EAAAA,yBAAyB,EAAE,KAAK;AAChCC,EAAAA,oBAAoB,EAAE,KAAK;AAC3BC,EAAAA,IAAI,EAAE,SAAS;EACfC,qBAAqB,EAAEA,MAAM,KAAK;AAClCC,EAAAA,sBAAsB,EAAEA,MAAK,CAAE,CAAC;AAChCC,EAAAA,mBAAmB,EAAEA,MAAK,CAAE,CAAC;EAC7BC,oBAAoB,EAAEA,MAAK,CAAE;;AAqIzB,SAAUC,gBAAgBA,CAACC,IAAkB,EAAA;AACjD,EAAA,IAAI9B,mBAAmB,EAAE;AACvB,IAAA,MAAM,IAAI+B,KAAK,CACb,OAAOC,SAAS,KAAK,WAAW,IAAIA,SAAA,GAChC,CAAA,sDAAA,CAAA,GACA,EAAE,CACP;AACH,EAAA;EAEA,IAAIjC,cAAc,KAAK,IAAI,EAAE;AAE3B,IAAA;AACF,EAAA;AAEAA,EAAAA,cAAc,CAAC6B,oBAAoB,CAACE,IAAI,CAAC;AAEzC,EAAA,MAAMG,gBAAgB,GAAGlC,cAAc,CAACmB,aAAa;EAIrD,IAAIe,gBAAgB,KAAKrB,SAAS,IAAIqB,gBAAgB,CAACC,QAAQ,KAAKJ,IAAI,EAAE;AACxE,IAAA;AACF,EAAA;EAEA,IAAIK,gBAAgB,GAA6BvB,SAAS;AAC1D,EAAA,MAAMwB,aAAa,GAAGrC,cAAc,CAACsB,WAAW;AAChD,EAAA,IAAIe,aAAa,EAAE;IAMjBD,gBAAgB,GACdF,gBAAgB,KAAKrB,SAAS,GAAGqB,gBAAgB,CAACI,YAAY,GAAGtC,cAAc,CAACkB,SAAS;IAC3F,IAAIkB,gBAAgB,KAAKvB,SAAS,IAAIuB,gBAAgB,CAACD,QAAQ,KAAKJ,IAAI,EAAE;MAGxE/B,cAAc,CAACmB,aAAa,GAAGiB,gBAAgB;AAC/CA,MAAAA,gBAAgB,CAACG,eAAe,GAAGR,IAAI,CAAChB,OAAO;MAC/CqB,gBAAgB,CAACI,iBAAiB,GAAGtC,KAAK;AAC1C,MAAA;AACF,IAAA;AACF,EAAA;AAEA,EAAA,MAAMuC,gBAAgB,GAAGV,IAAI,CAACV,aAAa;AAI3C,EAAA,IACEoB,gBAAgB,KAAK5B,SAAS,IAC9B4B,gBAAgB,CAAClC,QAAQ,KAAKP,cAAc,KAC3C,CAACqC,aAAa,IAAII,gBAAgB,CAACD,iBAAiB,KAAKtC,KAAK,CAAC,EAChE;AACA,IAAA;AACF,EAAA;AAGA,EAAA,MAAMwC,MAAM,GAAGC,cAAc,CAAC3C,cAAc,CAAC;AAC7C,EAAA,MAAM4C,OAAO,GAAiB;AAC5BT,IAAAA,QAAQ,EAAEJ,IAAI;AACdxB,IAAAA,QAAQ,EAAEP,cAAc;AAGxBsC,IAAAA,YAAY,EAAEF,gBAAgB;AAK9BS,IAAAA,YAAY,EAAEhC,SAAS;AACvB2B,IAAAA,iBAAiB,EAAEtC,KAAK;IACxBqC,eAAe,EAAER,IAAI,CAAChB,OAAO;AAC7B+B,IAAAA,YAAY,EAAEjC;GACf;EACDb,cAAc,CAACmB,aAAa,GAAGyB,OAAO;EACtC,IAAIV,gBAAgB,KAAKrB,SAAS,EAAE;IAClCqB,gBAAgB,CAACI,YAAY,GAAGM,OAAO;AACzC,EAAA,CAAA,MAAO;IACL5C,cAAc,CAACkB,SAAS,GAAG0B,OAAO;AACpC,EAAA;AAEA,EAAA,IAAIF,MAAM,EAAE;AACVK,IAAAA,uBAAuB,CAAChB,IAAI,EAAEa,OAAO,CAAC;AACxC,EAAA;AACF;SAOgBI,sBAAsBA,GAAA;AACpC9C,EAAAA,KAAK,EAAE;AACT;AAKM,SAAU+C,0BAA0BA,CAAClB,IAAkB,EAAA;EAC3D,IAAIY,cAAc,CAACZ,IAAI,CAAC,IAAI,CAACA,IAAI,CAACd,KAAK,EAAE;AAGvC,IAAA;AACF,EAAA;EAEA,IAAI,CAACc,IAAI,CAACd,KAAK,IAAIc,IAAI,CAACf,cAAc,KAAKd,KAAK,EAAE;AAIhD,IAAA;AACF,EAAA;AAEA,EAAA,IAAI,CAAC6B,IAAI,CAACL,qBAAqB,CAACK,IAAI,CAAC,IAAI,CAACmB,8BAA8B,CAACnB,IAAI,CAAC,EAAE;IAG9EoB,iBAAiB,CAACpB,IAAI,CAAC;AACvB,IAAA;AACF,EAAA;AAEAA,EAAAA,IAAI,CAACJ,sBAAsB,CAACI,IAAI,CAAC;EAGjCoB,iBAAiB,CAACpB,IAAI,CAAC;AACzB;AAKM,SAAUqB,uBAAuBA,CAACrB,IAAkB,EAAA;AACxD,EAAA,IAAIA,IAAI,CAACX,SAAS,KAAKP,SAAS,EAAE;AAChC,IAAA;AACF,EAAA;EAGA,MAAML,IAAI,GAAGP,mBAAmB;AAChCA,EAAAA,mBAAmB,GAAG,IAAI;EAC1B,IAAI;AACF,IAAA,KACE,IAAIoD,IAAI,GAA6BtB,IAAI,CAACX,SAAS,EACnDiC,IAAI,KAAKxC,SAAS,EAClBwC,IAAI,GAAGA,IAAI,CAACP,YAAY,EACxB;AACA,MAAA,MAAMvC,QAAQ,GAAG8C,IAAI,CAAC9C,QAAQ;AAC9B,MAAA,IAAI,CAACA,QAAQ,CAACU,KAAK,EAAE;QACnBqC,iBAAiB,CAAC/C,QAAQ,CAAC;AAC7B,MAAA;AACF,IAAA;AACF,EAAA,CAAA,SAAU;AACRN,IAAAA,mBAAmB,GAAGO,IAAI;AAC5B,EAAA;AACF;SAMgB+C,sBAAsBA,GAAA;AACpC,EAAA,OAAOvD,cAAc,EAAEuB,yBAAyB,KAAK,KAAK;AAC5D;AAEM,SAAU+B,iBAAiBA,CAACvB,IAAkB,EAAA;EAClDA,IAAI,CAACd,KAAK,GAAG,IAAI;EACjBmC,uBAAuB,CAACrB,IAAI,CAAC;AAC7BA,EAAAA,IAAI,CAACH,mBAAmB,GAAGG,IAAI,CAAC;AAClC;AAEM,SAAUoB,iBAAiBA,CAACpB,IAAkB,EAAA;EAClDA,IAAI,CAACd,KAAK,GAAG,KAAK;EAClBc,IAAI,CAACf,cAAc,GAAGd,KAAK;AAC7B;AASM,SAAUsD,yBAAyBA,CAACzB,IAAyB,EAAA;AACjE,EAAA,IAAIA,IAAI,EAAE0B,8BAA8B,CAAC1B,IAAI,CAAC;EAE9C,OAAOzB,iBAAiB,CAACyB,IAAI,CAAC;AAChC;AAUM,SAAU0B,8BAA8BA,CAAC1B,IAAkB,EAAA;AAK/D,EAAA,IAAIA,IAAI,CAACZ,aAAa,EAAEqB,iBAAiB,KAAKtC,KAAK,EAAE;AACnD,IAAA,IAAIiC,QAAQ,GAAGJ,IAAI,CAACb,SAAS;IAC7B,OAAOiB,QAAQ,KAAKtB,SAAS,EAAE;MAC7BsB,QAAQ,CAACK,iBAAiB,GAAG,IAAI;MACjCL,QAAQ,GAAGA,QAAQ,CAACG,YAAY;AAClC,IAAA;AACF,EAAA;EAEAP,IAAI,CAACZ,aAAa,GAAGN,SAAS;EAC9BkB,IAAI,CAACT,WAAW,GAAG,IAAI;AACzB;AASM,SAAUoC,wBAAwBA,CACtC3B,IAAyB,EACzBc,YAAiC,EAAA;EAEjCvC,iBAAiB,CAACuC,YAAY,CAAC;AAE/B,EAAA,IAAId,IAAI,EAAE4B,gCAAgC,CAAC5B,IAAI,CAAC;AAClD;AAUM,SAAU4B,gCAAgCA,CAAC5B,IAAkB,EAAA;EACjEA,IAAI,CAACT,WAAW,GAAG,KAAK;AAIxB,EAAA,MAAMH,aAAa,GAAGY,IAAI,CAACZ,aAAyC;AACpE,EAAA,IAAIyC,QAAQ,GAAGzC,aAAa,KAAKN,SAAS,GAAGM,aAAa,CAACmB,YAAY,GAAGP,IAAI,CAACb,SAAS;EACxF,IAAI0C,QAAQ,KAAK/C,SAAS,EAAE;AAC1B,IAAA,IAAI8B,cAAc,CAACZ,IAAI,CAAC,EAAE;MAExB,GAAG;AACD6B,QAAAA,QAAQ,GAAGC,8BAA8B,CAACD,QAAQ,CAAC;MACrD,CAAC,QAAQA,QAAQ,KAAK/C,SAAS;AACjC,IAAA;IAGA,IAAIM,aAAa,KAAKN,SAAS,EAAE;MAC/BM,aAAa,CAACmB,YAAY,GAAGzB,SAAS;AACxC,IAAA,CAAA,MAAO;MACLkB,IAAI,CAACb,SAAS,GAAGL,SAAS;AAC5B,IAAA;AACF,EAAA;AACF;AAMM,SAAUqC,8BAA8BA,CAACnB,IAAkB,EAAA;AAE/D,EAAA,KAAK,IAAIsB,IAAI,GAAGtB,IAAI,CAACb,SAAS,EAAEmC,IAAI,KAAKxC,SAAS,EAAEwC,IAAI,GAAGA,IAAI,CAACf,YAAY,EAAE;AAC5E,IAAA,MAAMH,QAAQ,GAAGkB,IAAI,CAAClB,QAAQ;AAC9B,IAAA,MAAM2B,WAAW,GAAGT,IAAI,CAACd,eAAe;AAIxC,IAAA,IAAIuB,WAAW,KAAK3B,QAAQ,CAACpB,OAAO,EAAE;AACpC,MAAA,OAAO,IAAI;AACb,IAAA;IAIAkC,0BAA0B,CAACd,QAAQ,CAAC;AAIpC,IAAA,IAAI2B,WAAW,KAAK3B,QAAQ,CAACpB,OAAO,EAAE;AACpC,MAAA,OAAO,IAAI;AACb,IAAA;AACF,EAAA;AAEA,EAAA,OAAO,KAAK;AACd;AAKM,SAAUgD,eAAeA,CAAChC,IAAkB,EAAA;AAChD,EAAA,IAAIY,cAAc,CAACZ,IAAI,CAAC,EAAE;AAExB,IAAA,IAAIsB,IAAI,GAAGtB,IAAI,CAACb,SAAS;IACzB,OAAOmC,IAAI,KAAKxC,SAAS,EAAE;AACzBwC,MAAAA,IAAI,GAAGQ,8BAA8B,CAACR,IAAI,CAAC;AAC7C,IAAA;AACF,EAAA;EAGAtB,IAAI,CAACb,SAAS,GAAGL,SAAS;EAC1BkB,IAAI,CAACZ,aAAa,GAAGN,SAAS;EAC9BkB,IAAI,CAACX,SAAS,GAAGP,SAAS;EAC1BkB,IAAI,CAACV,aAAa,GAAGR,SAAS;AAChC;AAQA,SAASkC,uBAAuBA,CAAChB,IAAkB,EAAEsB,IAAkB,EAAA;AACrE,EAAA,MAAMhC,aAAa,GAAGU,IAAI,CAACV,aAAa;AACxC,EAAA,MAAM2C,OAAO,GAAGrB,cAAc,CAACZ,IAAI,CAAC;EACpC,IAAIV,aAAa,KAAKR,SAAS,EAAE;AAC/BwC,IAAAA,IAAI,CAACP,YAAY,GAAGzB,aAAa,CAACyB,YAAY;IAC9CzB,aAAa,CAACyB,YAAY,GAAGO,IAAI;AACnC,EAAA,CAAA,MAAO;IACLA,IAAI,CAACP,YAAY,GAAGjC,SAAS;IAC7BkB,IAAI,CAACX,SAAS,GAAGiC,IAAI;AACvB,EAAA;EACAA,IAAI,CAACR,YAAY,GAAGxB,aAAa;EACjCU,IAAI,CAACV,aAAa,GAAGgC,IAAI;EACzB,IAAI,CAACW,OAAO,EAAE;AACZ,IAAA,KACE,IAAIX,IAAI,GAA6BtB,IAAI,CAACb,SAAS,EACnDmC,IAAI,KAAKxC,SAAS,EAClBwC,IAAI,GAAGA,IAAI,CAACf,YAAY,EACxB;AACAS,MAAAA,uBAAuB,CAACM,IAAI,CAAClB,QAAQ,EAAEkB,IAAI,CAAC;AAC9C,IAAA;AACF,EAAA;AACF;AAEA,SAASQ,8BAA8BA,CAACR,IAAkB,EAAA;AACxD,EAAA,MAAMlB,QAAQ,GAAGkB,IAAI,CAAClB,QAAQ;AAC9B,EAAA,MAAMG,YAAY,GAAGe,IAAI,CAACf,YAAY;AACtC,EAAA,MAAMQ,YAAY,GAAGO,IAAI,CAACP,YAAY;AACtC,EAAA,MAAMD,YAAY,GAAGQ,IAAI,CAACR,YAAY;EACtCQ,IAAI,CAACP,YAAY,GAAGjC,SAAS;EAC7BwC,IAAI,CAACR,YAAY,GAAGhC,SAAS;EAC7B,IAAIiC,YAAY,KAAKjC,SAAS,EAAE;IAC9BiC,YAAY,CAACD,YAAY,GAAGA,YAAY;AAC1C,EAAA,CAAA,MAAO;IACLV,QAAQ,CAACd,aAAa,GAAGwB,YAAY;AACvC,EAAA;EACA,IAAIA,YAAY,KAAKhC,SAAS,EAAE;IAC9BgC,YAAY,CAACC,YAAY,GAAGA,YAAY;AAC1C,EAAA,CAAA,MAAO;IACLX,QAAQ,CAACf,SAAS,GAAG0B,YAAY;AACjC,IAAA,IAAI,CAACH,cAAc,CAACR,QAAQ,CAAC,EAAE;AAC7B,MAAA,IAAI8B,YAAY,GAAG9B,QAAQ,CAACjB,SAAS;MACrC,OAAO+C,YAAY,KAAKpD,SAAS,EAAE;AACjCoD,QAAAA,YAAY,GAAGJ,8BAA8B,CAACI,YAAY,CAAC;AAC7D,MAAA;AACF,IAAA;AACF,EAAA;AACA,EAAA,OAAO3B,YAAY;AACrB;AAEA,SAASK,cAAcA,CAACZ,IAAkB,EAAA;EACxC,OAAOA,IAAI,CAACP,oBAAoB,IAAIO,IAAI,CAACX,SAAS,KAAKP,SAAS;AAClE;AAEM,SAAUqD,wBAAwBA,CAACnC,IAAkB,EAAA;EACzD5B,qBAAqB,GAAG4B,IAAI,CAAC;AAC/B;AAEM,SAAUoC,wBAAwBA,CAACC,EAAyB,EAAA;EAChE,MAAM5D,IAAI,GAAGL,qBAAqB;AAClCA,EAAAA,qBAAqB,GAAGiE,EAAE;AAC1B,EAAA,OAAO5D,IAAI;AACb;;ACvjBM,SAAU6D,aAAaA,CAAIC,CAAI,EAAEC,CAAI,EAAA;AACzC,EAAA,OAAOC,MAAM,CAACC,EAAE,CAACH,CAAC,EAAEC,CAAC,CAAC;AACxB;;ACwCM,SAAUG,cAAcA,CAC5BC,WAAoB,EACpBC,KAA0B,EAAA;AAE1B,EAAA,MAAM7C,IAAI,GAAoByC,MAAM,CAACK,MAAM,CAACC,aAAa,CAAC;EAC1D/C,IAAI,CAAC4C,WAAW,GAAGA,WAAW;EAE9B,IAAIC,KAAK,KAAK/D,SAAS,EAAE;IACvBkB,IAAI,CAAC6C,KAAK,GAAGA,KAAK;AACpB,EAAA;EAEA,MAAMG,QAAQ,GAAGA,MAAK;IAEpB9B,0BAA0B,CAAClB,IAAI,CAAC;IAGhCD,gBAAgB,CAACC,IAAI,CAAC;AAEtB,IAAA,IAAIA,IAAI,CAACnB,KAAK,KAAKoE,OAAO,EAAE;MAC1B,MAAMjD,IAAI,CAACkD,KAAK;AAClB,IAAA;IAEA,OAAOlD,IAAI,CAACnB,KAAK;EACnB,CAAC;AAEAmE,EAAAA,QAA8B,CAAC3E,MAAM,CAAC,GAAG2B,IAAI;AAC9C,EAAA,IAAI,OAAOE,SAAS,KAAK,WAAW,IAAIA,SAAS,EAAE;IACjD8C,QAAQ,CAACG,QAAQ,GAAG,MAClB,CAAA,SAAA,EAAYnD,IAAI,CAACoD,SAAS,GAAG,IAAI,GAAGpD,IAAI,CAACoD,SAAS,GAAG,GAAG,GAAG,EAAE,CAAA,EAAA,EAAKC,MAAM,CAACrD,IAAI,CAACnB,KAAK,CAAC,CAAA,CAAA,CAAG;AAC3F,EAAA;EAEAsD,wBAAwB,CAACnC,IAAI,CAAC;AAE9B,EAAA,OAAOgD,QAAwC;AACjD;MAMaM,KAAK,kBAAwBhF,MAAM,CAAC,OAAO;MAO3CiF,SAAS,kBAAwBjF,MAAM,CAAC,WAAW;MAOnD2E,OAAO,kBAAwB3E,MAAM,CAAC,SAAS;AAI5D,MAAMyE,aAAa,kBAA+D,CAAC,MAAK;EACtF,OAAO;AACL,IAAA,GAAGhE,aAAa;AAChBF,IAAAA,KAAK,EAAEyE,KAAK;AACZpE,IAAAA,KAAK,EAAE,IAAI;AACXgE,IAAAA,KAAK,EAAE,IAAI;AACXL,IAAAA,KAAK,EAAEP,aAAa;AACpB5C,IAAAA,IAAI,EAAE,UAAU;IAEhBC,qBAAqBA,CAACK,IAA2B,EAAA;MAG/C,OAAOA,IAAI,CAACnB,KAAK,KAAKyE,KAAK,IAAItD,IAAI,CAACnB,KAAK,KAAK0E,SAAS;IACzD,CAAC;IAED3D,sBAAsBA,CAACI,IAA2B,EAAA;AAChD,MAAA,IAAIA,IAAI,CAACnB,KAAK,KAAK0E,SAAS,EAAE;AAE5B,QAAA,MAAM,IAAItD,KAAK,CACb,OAAOC,SAAS,KAAK,WAAW,IAAIA,SAAS,GAAG,iCAAiC,GAAG,EAAE,CACvF;AACH,MAAA;AAEA,MAAA,MAAMsD,QAAQ,GAAGxD,IAAI,CAACnB,KAAK;MAC3BmB,IAAI,CAACnB,KAAK,GAAG0E,SAAS;AAEtB,MAAA,MAAMzC,YAAY,GAAGW,yBAAyB,CAACzB,IAAI,CAAC;AACpD,MAAA,IAAIyD,QAAiB;MACrB,IAAIC,QAAQ,GAAG,KAAK;MACpB,IAAI;AACFD,QAAAA,QAAQ,GAAGzD,IAAI,CAAC4C,WAAW,EAAE;QAG7BrE,iBAAiB,CAAC,IAAI,CAAC;QACvBmF,QAAQ,GACNF,QAAQ,KAAKF,KAAK,IAClBE,QAAQ,KAAKP,OAAO,IACpBQ,QAAQ,KAAKR,OAAO,IACpBjD,IAAI,CAAC6C,KAAK,CAACW,QAAQ,EAAEC,QAAQ,CAAC;MAClC,CAAA,CAAE,OAAOE,GAAG,EAAE;AACZF,QAAAA,QAAQ,GAAGR,OAAO;QAClBjD,IAAI,CAACkD,KAAK,GAAGS,GAAG;AAClB,MAAA,CAAA,SAAU;AACRhC,QAAAA,wBAAwB,CAAC3B,IAAI,EAAEc,YAAY,CAAC;AAC9C,MAAA;AAEA,MAAA,IAAI4C,QAAQ,EAAE;QAGZ1D,IAAI,CAACnB,KAAK,GAAG2E,QAAQ;AACrB,QAAA;AACF,MAAA;MAEAxD,IAAI,CAACnB,KAAK,GAAG4E,QAAQ;MACrBzD,IAAI,CAAChB,OAAO,EAAE;AAChB,IAAA;GACD;AACH,CAAC,GAAG;;ACnKJ,SAAS4E,iBAAiBA,GAAA;EACxB,MAAM,IAAI3D,KAAK,EAAE;AACnB;AAEA,IAAI4D,gCAAgC,GAAsCD,iBAAiB;AAErF,SAAUE,8BAA8BA,CAAI9D,IAAmB,EAAA;EACnE6D,gCAAgC,CAAC7D,IAAI,CAAC;AACxC;AAEM,SAAU+D,iCAAiCA,CAAC1B,EAAqC,EAAA;AACrFwB,EAAAA,gCAAgC,GAAGxB,EAAE;AACvC;;ACUA,IAAI2B,eAAe,GAA0B,IAAI;AAoB3C,SAAUC,YAAYA,CAC1BC,YAAe,EACfrB,KAA0B,EAAA;AAE1B,EAAA,MAAM7C,IAAI,GAAkByC,MAAM,CAACK,MAAM,CAACqB,WAAW,CAAC;EACtDnE,IAAI,CAACnB,KAAK,GAAGqF,YAAY;EACzB,IAAIrB,KAAK,KAAK/D,SAAS,EAAE;IACvBkB,IAAI,CAAC6C,KAAK,GAAGA,KAAK;AACpB,EAAA;AACA,EAAA,MAAMuB,MAAM,GAAIA,MAAMC,WAAW,CAACrE,IAAI,CAAqB;AAC1DoE,EAAAA,MAAc,CAAC/F,MAAM,CAAC,GAAG2B,IAAI;AAC9B,EAAA,IAAI,OAAOE,SAAS,KAAK,WAAW,IAAIA,SAAS,EAAE;IACjDkE,MAAM,CAACjB,QAAQ,GAAG,MAChB,CAAA,OAAA,EAAUnD,IAAI,CAACoD,SAAS,GAAG,IAAI,GAAGpD,IAAI,CAACoD,SAAS,GAAG,GAAG,GAAG,EAAE,CAAA,EAAA,EAAKC,MAAM,CAACrD,IAAI,CAACnB,KAAK,CAAC,CAAA,CAAA,CAAG;AACzF,EAAA;EAEAsD,wBAAwB,CAACnC,IAAI,CAAC;EAC9B,MAAMsE,GAAG,GAAIb,QAAW,IAAKc,WAAW,CAACvE,IAAI,EAAEyD,QAAQ,CAAC;EACxD,MAAMe,MAAM,GAAIC,QAAyB,IAAKC,cAAc,CAAC1E,IAAI,EAAEyE,QAAQ,CAAC;AAC5E,EAAA,OAAO,CAACL,MAAM,EAAEE,GAAG,EAAEE,MAAM,CAAC;AAC9B;AAEM,SAAUG,kBAAkBA,CAACtC,EAAyB,EAAA;EAC1D,MAAM5D,IAAI,GAAGuF,eAAe;AAC5BA,EAAAA,eAAe,GAAG3B,EAAE;AACpB,EAAA,OAAO5D,IAAI;AACb;AAEM,SAAU4F,WAAWA,CAAIrE,IAAmB,EAAA;EAChDD,gBAAgB,CAACC,IAAI,CAAC;EACtB,OAAOA,IAAI,CAACnB,KAAK;AACnB;AAEM,SAAU0F,WAAWA,CAAIvE,IAAmB,EAAEyD,QAAW,EAAA;AAC7D,EAAA,IAAI,CAACjC,sBAAsB,EAAE,EAAE;IAC7BsC,8BAA8B,CAAC9D,IAAI,CAAC;AACtC,EAAA;EAEA,IAAI,CAACA,IAAI,CAAC6C,KAAK,CAAC7C,IAAI,CAACnB,KAAK,EAAE4E,QAAQ,CAAC,EAAE;IACrCzD,IAAI,CAACnB,KAAK,GAAG4E,QAAQ;IACrBmB,kBAAkB,CAAC5E,IAAI,CAAC;AAC1B,EAAA;AACF;AAEM,SAAU0E,cAAcA,CAAI1E,IAAmB,EAAE6E,OAAwB,EAAA;AAC7E,EAAA,IAAI,CAACrD,sBAAsB,EAAE,EAAE;IAC7BsC,8BAA8B,CAAC9D,IAAI,CAAC;AACtC,EAAA;EAEAuE,WAAW,CAACvE,IAAI,EAAE6E,OAAO,CAAC7E,IAAI,CAACnB,KAAK,CAAC,CAAC;AACxC;AAEM,SAAUiG,kBAAkBA,CAAI9E,IAAmB,EAAA;EACvDgE,eAAe,GAAGhE,IAAI,CAAC;AACzB;AAIO,MAAMmE,WAAW,kBAAwC,CAAC,MAAK;EACpE,OAAO;AACL,IAAA,GAAGpF,aAAa;AAChB8D,IAAAA,KAAK,EAAEP,aAAa;AACpBzD,IAAAA,KAAK,EAAEC,SAAS;AAChBY,IAAAA,IAAI,EAAE;GACP;AACH,CAAC;AAED,SAASkF,kBAAkBA,CAAI5E,IAAmB,EAAA;EAChDA,IAAI,CAAChB,OAAO,EAAE;AACdiC,EAAAA,sBAAsB,EAAE;EACxBI,uBAAuB,CAACrB,IAAI,CAAC;EAC7BgE,eAAe,GAAGhE,IAAI,CAAC;AACzB;;ACzFO,MAAM+E,gBAAgB,kBACX,CAAC,OAAO;AACtB,EAAA,GAAGhG,aAAa;AAChBU,EAAAA,oBAAoB,EAAE,IAAI;AAC1BD,EAAAA,yBAAyB,EAAE,IAAI;AAC/BN,EAAAA,KAAK,EAAE,IAAI;AACXQ,EAAAA,IAAI,EAAE;CACP,CAAC;AAEE,SAAUsF,SAASA,CAAChF,IAAoB,EAAA;EAC5CA,IAAI,CAACd,KAAK,GAAG,KAAK;EAClB,IAAIc,IAAI,CAAChB,OAAO,GAAG,CAAC,IAAI,CAACmC,8BAA8B,CAACnB,IAAI,CAAC,EAAE;AAC7D,IAAA;AACF,EAAA;EACAA,IAAI,CAAChB,OAAO,EAAE;AACd,EAAA,MAAMiG,QAAQ,GAAGxD,yBAAyB,CAACzB,IAAI,CAAC;EAChD,IAAI;IACFA,IAAI,CAACkF,OAAO,EAAE;IACdlF,IAAI,CAACqC,EAAE,EAAE;AACX,EAAA,CAAA,SAAU;AACRV,IAAAA,wBAAwB,CAAC3B,IAAI,EAAEiF,QAAQ,CAAC;AAC1C,EAAA;AACF;;;;"} |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -4,0 +4,0 @@ * License: MIT |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -17,2 +17,4 @@ * License: MIT | ||
| }); | ||
| isEmitting = false; | ||
| hasNullListeners = false; | ||
| destroyRef = inject(DestroyRef); | ||
@@ -32,5 +34,10 @@ constructor() { | ||
| unsubscribe: () => { | ||
| const idx = this.listeners?.indexOf(callback); | ||
| if (idx !== undefined && idx !== -1) { | ||
| this.listeners?.splice(idx, 1); | ||
| const index = this.listeners ? this.listeners.indexOf(callback) : -1; | ||
| if (index > -1) { | ||
| if (this.isEmitting) { | ||
| this.hasNullListeners = true; | ||
| this.listeners[index] = null; | ||
| } else { | ||
| this.listeners.splice(index, 1); | ||
| } | ||
| } | ||
@@ -48,2 +55,3 @@ } | ||
| } | ||
| this.isEmitting = true; | ||
| const previousConsumer = setActiveConsumer(null); | ||
@@ -53,3 +61,5 @@ try { | ||
| try { | ||
| listenerFn(value); | ||
| if (listenerFn !== null) { | ||
| listenerFn(value); | ||
| } | ||
| } catch (err) { | ||
@@ -60,6 +70,20 @@ this.errorHandler?.handleError(err); | ||
| } finally { | ||
| if (this.hasNullListeners) { | ||
| this.hasNullListeners = false; | ||
| this.listeners && removeNullValues(this.listeners); | ||
| } | ||
| setActiveConsumer(previousConsumer); | ||
| this.isEmitting = false; | ||
| } | ||
| } | ||
| } | ||
| function removeNullValues(arr) { | ||
| let i = arr.length - 1; | ||
| while (i > -1) { | ||
| if (arr[i] === null) { | ||
| arr.splice(i, 1); | ||
| } | ||
| i--; | ||
| } | ||
| } | ||
| function getOutputDestroyRef(ref) { | ||
@@ -66,0 +90,0 @@ return ref.destroyRef; |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -4,0 +4,0 @@ * License: MIT |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -4,0 +4,0 @@ * License: MIT |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -4,0 +4,0 @@ * License: MIT |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -4,0 +4,0 @@ * License: MIT |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -4,0 +4,0 @@ * License: MIT |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -4,0 +4,0 @@ * License: MIT |
+2
-2
| { | ||
| "name": "@angular/core", | ||
| "version": "22.0.1", | ||
| "version": "22.0.2", | ||
| "description": "Angular - the core framework", | ||
@@ -53,3 +53,3 @@ "author": "angular", | ||
| "peerDependencies": { | ||
| "@angular/compiler": "22.0.1", | ||
| "@angular/compiler": "22.0.2", | ||
| "rxjs": "^6.5.3 || ^7.4.0", | ||
@@ -56,0 +56,0 @@ "zone.js": "~0.15.0 || ~0.16.0" |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -11,3 +11,3 @@ * License: MIT | ||
| require('node:path/posix'); | ||
| var project_paths = require('./project_paths-D2V-Uh2L.cjs'); | ||
| var project_paths = require('./project_paths-C3_ORvxG.cjs'); | ||
| var ts = require('typescript'); | ||
@@ -18,3 +18,3 @@ require('@angular/compiler-cli'); | ||
| require('@angular-devkit/schematics'); | ||
| require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
@@ -21,0 +21,0 @@ class CanMatchSnapshotRequiredMigration extends project_paths.TsurgeFunnelMigration { |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -11,3 +11,3 @@ * License: MIT | ||
| require('node:path/posix'); | ||
| var project_paths = require('./project_paths-D2V-Uh2L.cjs'); | ||
| var project_paths = require('./project_paths-C3_ORvxG.cjs'); | ||
| var migrations = require('@angular/compiler-cli/private/migrations'); | ||
@@ -18,6 +18,6 @@ var ts = require('typescript'); | ||
| require('node:path'); | ||
| var apply_import_manager = require('./apply_import_manager-CxA_YYgB.cjs'); | ||
| var apply_import_manager = require('./apply_import_manager-aRrR-E5x.cjs'); | ||
| var leading_space = require('./leading_space-BTPRV0wu.cjs'); | ||
| require('@angular-devkit/schematics'); | ||
| require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
| require('./imports-CKV-ITqD.cjs'); | ||
@@ -24,0 +24,0 @@ |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -11,3 +11,3 @@ * License: MIT | ||
| require('node:path/posix'); | ||
| var project_paths = require('./project_paths-D2V-Uh2L.cjs'); | ||
| var project_paths = require('./project_paths-C3_ORvxG.cjs'); | ||
| var ts = require('typescript'); | ||
@@ -17,5 +17,5 @@ var compilerCli = require('@angular/compiler-cli'); | ||
| require('node:path'); | ||
| var apply_import_manager = require('./apply_import_manager-CxA_YYgB.cjs'); | ||
| var apply_import_manager = require('./apply_import_manager-aRrR-E5x.cjs'); | ||
| require('@angular-devkit/schematics'); | ||
| require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
@@ -22,0 +22,0 @@ /** Migration that cleans up unused imports from a project. */ |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -13,6 +13,6 @@ * License: MIT | ||
| require('node:path'); | ||
| var project_paths = require('./project_paths-D2V-Uh2L.cjs'); | ||
| var project_paths = require('./project_paths-C3_ORvxG.cjs'); | ||
| var ng_component_template = require('./ng_component_template-DPAF1aEA.cjs'); | ||
| var ng_decorators = require('./ng_decorators-IVztR9rk.cjs'); | ||
| var apply_import_manager = require('./apply_import_manager-CxA_YYgB.cjs'); | ||
| var apply_import_manager = require('./apply_import_manager-aRrR-E5x.cjs'); | ||
| var imports = require('./imports-CKV-ITqD.cjs'); | ||
@@ -22,3 +22,3 @@ require('@angular-devkit/core'); | ||
| require('@angular-devkit/schematics'); | ||
| require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
| require('./property_name-BCpALNpZ.cjs'); | ||
@@ -25,0 +25,0 @@ |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -5,0 +5,0 @@ * License: MIT |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -11,3 +11,3 @@ * License: MIT | ||
| require('node:path/posix'); | ||
| var project_paths = require('./project_paths-D2V-Uh2L.cjs'); | ||
| var project_paths = require('./project_paths-C3_ORvxG.cjs'); | ||
| var migrations = require('@angular/compiler-cli/private/migrations'); | ||
@@ -17,6 +17,6 @@ var ts = require('typescript'); | ||
| require('node:path'); | ||
| var apply_import_manager = require('./apply_import_manager-CxA_YYgB.cjs'); | ||
| var apply_import_manager = require('./apply_import_manager-aRrR-E5x.cjs'); | ||
| var imports = require('./imports-CKV-ITqD.cjs'); | ||
| require('@angular-devkit/schematics'); | ||
| require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
@@ -23,0 +23,0 @@ const HTTP = '@angular/common/http'; |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -5,0 +5,0 @@ * License: MIT |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -11,3 +11,3 @@ * License: MIT | ||
| require('node:path/posix'); | ||
| var project_paths = require('./project_paths-D2V-Uh2L.cjs'); | ||
| var project_paths = require('./project_paths-C3_ORvxG.cjs'); | ||
| var migrations = require('@angular/compiler-cli/private/migrations'); | ||
@@ -17,5 +17,5 @@ var ts = require('typescript'); | ||
| require('node:path'); | ||
| var apply_import_manager = require('./apply_import_manager-CxA_YYgB.cjs'); | ||
| var apply_import_manager = require('./apply_import_manager-aRrR-E5x.cjs'); | ||
| require('@angular-devkit/schematics'); | ||
| require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
@@ -22,0 +22,0 @@ class IncrementalHydrationMigration extends project_paths.TsurgeFunnelMigration { |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -17,3 +17,3 @@ * License: MIT | ||
| var leading_space = require('./leading_space-BTPRV0wu.cjs'); | ||
| var project_tsconfig_paths = require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| var project_tsconfig_paths = require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
| require('@angular/compiler-cli/private/migrations'); | ||
@@ -20,0 +20,0 @@ require('@angular-devkit/core'); |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -5,0 +5,0 @@ * License: MIT |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -13,9 +13,9 @@ * License: MIT | ||
| require('node:path'); | ||
| var project_paths = require('./project_paths-D2V-Uh2L.cjs'); | ||
| var project_paths = require('./project_paths-C3_ORvxG.cjs'); | ||
| var imports = require('./imports-CKV-ITqD.cjs'); | ||
| var apply_import_manager = require('./apply_import_manager-CxA_YYgB.cjs'); | ||
| var apply_import_manager = require('./apply_import_manager-aRrR-E5x.cjs'); | ||
| require('@angular-devkit/core'); | ||
| require('node:path/posix'); | ||
| require('@angular-devkit/schematics'); | ||
| require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
@@ -22,0 +22,0 @@ class ModelOutputMigration extends project_paths.TsurgeFunnelMigration { |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -5,0 +5,0 @@ * License: MIT |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -5,0 +5,0 @@ * License: MIT |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -13,5 +13,5 @@ * License: MIT | ||
| require('node:path'); | ||
| var project_paths = require('./project_paths-D2V-Uh2L.cjs'); | ||
| var project_paths = require('./project_paths-C3_ORvxG.cjs'); | ||
| var compiler = require('@angular/compiler'); | ||
| var apply_import_manager = require('./apply_import_manager-CxA_YYgB.cjs'); | ||
| var apply_import_manager = require('./apply_import_manager-aRrR-E5x.cjs'); | ||
| var imports = require('./imports-CKV-ITqD.cjs'); | ||
@@ -23,3 +23,3 @@ var parse_html = require('./parse_html-C8eKA9px.cjs'); | ||
| require('@angular-devkit/schematics'); | ||
| require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
| require('./ng_decorators-IVztR9rk.cjs'); | ||
@@ -26,0 +26,0 @@ require('./property_name-BCpALNpZ.cjs'); |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -13,5 +13,5 @@ * License: MIT | ||
| require('node:path'); | ||
| var project_paths = require('./project_paths-D2V-Uh2L.cjs'); | ||
| var project_paths = require('./project_paths-C3_ORvxG.cjs'); | ||
| var compiler = require('@angular/compiler'); | ||
| var apply_import_manager = require('./apply_import_manager-CxA_YYgB.cjs'); | ||
| var apply_import_manager = require('./apply_import_manager-aRrR-E5x.cjs'); | ||
| var imports = require('./imports-CKV-ITqD.cjs'); | ||
@@ -23,3 +23,3 @@ var parse_html = require('./parse_html-C8eKA9px.cjs'); | ||
| require('@angular-devkit/schematics'); | ||
| require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
| require('./ng_decorators-IVztR9rk.cjs'); | ||
@@ -26,0 +26,0 @@ require('./property_name-BCpALNpZ.cjs'); |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -5,0 +5,0 @@ * License: MIT |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -13,10 +13,10 @@ * License: MIT | ||
| require('node:path'); | ||
| var project_paths = require('./project_paths-D2V-Uh2L.cjs'); | ||
| var project_paths = require('./project_paths-C3_ORvxG.cjs'); | ||
| var compiler = require('@angular/compiler'); | ||
| var apply_import_manager = require('./apply_import_manager-CxA_YYgB.cjs'); | ||
| var index = require('./index-B07c0BtS.cjs'); | ||
| var apply_import_manager = require('./apply_import_manager-aRrR-E5x.cjs'); | ||
| var index = require('./index-CdcoySD9.cjs'); | ||
| require('@angular-devkit/core'); | ||
| require('node:path/posix'); | ||
| require('@angular-devkit/schematics'); | ||
| require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
@@ -23,0 +23,0 @@ function isOutputDeclarationEligibleForMigration(node) { |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -5,0 +5,0 @@ * License: MIT |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -5,0 +5,0 @@ * License: MIT |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -13,3 +13,3 @@ * License: MIT | ||
| var compiler_host = require('./compiler_host-CY14HvaP.cjs'); | ||
| var project_tsconfig_paths = require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| var project_tsconfig_paths = require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
| var ts = require('typescript'); | ||
@@ -16,0 +16,0 @@ var migrations = require('@angular/compiler-cli/private/migrations'); |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -11,3 +11,3 @@ * License: MIT | ||
| require('node:path/posix'); | ||
| var project_paths = require('./project_paths-D2V-Uh2L.cjs'); | ||
| var project_paths = require('./project_paths-C3_ORvxG.cjs'); | ||
| require('@angular/compiler-cli'); | ||
@@ -17,5 +17,5 @@ var migrations = require('@angular/compiler-cli/private/migrations'); | ||
| require('node:path'); | ||
| var apply_import_manager = require('./apply_import_manager-CxA_YYgB.cjs'); | ||
| var apply_import_manager = require('./apply_import_manager-aRrR-E5x.cjs'); | ||
| require('@angular-devkit/schematics'); | ||
| require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
@@ -22,0 +22,0 @@ const ROUTER_TESTING_MODULE = 'RouterTestingModule'; |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -11,3 +11,3 @@ * License: MIT | ||
| require('node:path/posix'); | ||
| var project_paths = require('./project_paths-D2V-Uh2L.cjs'); | ||
| var project_paths = require('./project_paths-C3_ORvxG.cjs'); | ||
| var compiler = require('@angular/compiler'); | ||
@@ -22,3 +22,3 @@ var ts = require('typescript'); | ||
| require('@angular-devkit/schematics'); | ||
| require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
| require('./imports-CKV-ITqD.cjs'); | ||
@@ -25,0 +25,0 @@ |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -13,3 +13,3 @@ * License: MIT | ||
| require('node:path'); | ||
| var project_paths = require('./project_paths-D2V-Uh2L.cjs'); | ||
| var project_paths = require('./project_paths-C3_ORvxG.cjs'); | ||
| var ng_component_template = require('./ng_component_template-DPAF1aEA.cjs'); | ||
@@ -21,3 +21,3 @@ var compiler = require('@angular/compiler'); | ||
| require('@angular-devkit/schematics'); | ||
| require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
| require('./ng_decorators-IVztR9rk.cjs'); | ||
@@ -24,0 +24,0 @@ require('./imports-CKV-ITqD.cjs'); |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -13,7 +13,7 @@ * License: MIT | ||
| require('node:path'); | ||
| var project_paths = require('./project_paths-D2V-Uh2L.cjs'); | ||
| var apply_import_manager = require('./apply_import_manager-CxA_YYgB.cjs'); | ||
| var migrate_ts_type_references = require('./migrate_ts_type_references-Bk_jxVNa.cjs'); | ||
| var project_paths = require('./project_paths-C3_ORvxG.cjs'); | ||
| var apply_import_manager = require('./apply_import_manager-aRrR-E5x.cjs'); | ||
| var migrate_ts_type_references = require('./migrate_ts_type_references-B1EC9FSn.cjs'); | ||
| var assert = require('assert'); | ||
| var index = require('./index-B07c0BtS.cjs'); | ||
| var index = require('./index-CdcoySD9.cjs'); | ||
| var compiler = require('@angular/compiler'); | ||
@@ -23,3 +23,3 @@ require('@angular-devkit/core'); | ||
| require('@angular-devkit/schematics'); | ||
| require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
| require('./leading_space-BTPRV0wu.cjs'); | ||
@@ -26,0 +26,0 @@ |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -17,10 +17,10 @@ * License: MIT | ||
| require('node:path'); | ||
| require('./project_paths-D2V-Uh2L.cjs'); | ||
| require('./project_paths-C3_ORvxG.cjs'); | ||
| require('@angular-devkit/core'); | ||
| require('node:path/posix'); | ||
| require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| require('./apply_import_manager-CxA_YYgB.cjs'); | ||
| require('./migrate_ts_type_references-Bk_jxVNa.cjs'); | ||
| require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
| require('./apply_import_manager-aRrR-E5x.cjs'); | ||
| require('./migrate_ts_type_references-B1EC9FSn.cjs'); | ||
| require('assert'); | ||
| require('./index-B07c0BtS.cjs'); | ||
| require('./index-CdcoySD9.cjs'); | ||
| require('@angular/compiler'); | ||
@@ -27,0 +27,0 @@ require('./leading_space-BTPRV0wu.cjs'); |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -9,4 +9,4 @@ * License: MIT | ||
| var project_tsconfig_paths = require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| var jsonFile = require('./json-file-Drblb4E1.cjs'); | ||
| var project_tsconfig_paths = require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
| var jsonFile = require('./json-file-RJY0pCW_.cjs'); | ||
| require('@angular-devkit/core'); | ||
@@ -13,0 +13,0 @@ require('node:os'); |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -9,4 +9,4 @@ * License: MIT | ||
| var project_tsconfig_paths = require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| var jsonFile = require('./json-file-Drblb4E1.cjs'); | ||
| var project_tsconfig_paths = require('./project_tsconfig_paths-BejwmdOG.cjs'); | ||
| var jsonFile = require('./json-file-RJY0pCW_.cjs'); | ||
| var ts = require('typescript'); | ||
@@ -13,0 +13,0 @@ var path = require('node:path'); |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -28,2 +28,4 @@ * License: MIT | ||
| private errorHandler; | ||
| private isEmitting; | ||
| private hasNullListeners; | ||
| constructor(); | ||
@@ -30,0 +32,0 @@ subscribe(callback: (value: T) => void): OutputRefSubscription; |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -4,0 +4,0 @@ * License: MIT |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -4,0 +4,0 @@ * License: MIT |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -4,0 +4,0 @@ * License: MIT |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -37,2 +37,8 @@ * License: MIT | ||
| consumer: ReactiveNode; | ||
| /** | ||
| * Stores the epoch that holds when this link was observed, allowing subsequent observations of the same producer to | ||
| * realize that there's an existing link, avoiding the creation of a new, redundant link. A value of `null` indicates | ||
| * that the link cannot be assumed to be valid based on the epoch counter. | ||
| */ | ||
| knownValidAtEpoch: Version | null; | ||
| lastReadVersion: number; | ||
@@ -43,3 +49,3 @@ prevConsumer: ReactiveLink | undefined; | ||
| } | ||
| type ReactiveNodeKind = 'signal' | 'computed' | 'effect' | 'template' | 'linkedSignal' | 'afterRenderEffectPhase' | 'unknown'; | ||
| type ReactiveNodeKind = 'signal' | 'computed' | 'effect' | 'template' | 'linkedSignal' | 'afterRenderEffectPhase' | 'childSignalProp' | 'unknown'; | ||
| /** | ||
@@ -46,0 +52,0 @@ * A producer and/or consumer which participates in the reactive graph. |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -4,0 +4,0 @@ * License: MIT |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -4,0 +4,0 @@ * License: MIT |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -4,0 +4,0 @@ * License: MIT |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -4,0 +4,0 @@ * License: MIT |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -4,0 +4,0 @@ * License: MIT |
| /** | ||
| * @license Angular v22.0.1 | ||
| * @license Angular v22.0.2 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
@@ -4,0 +4,0 @@ * License: MIT |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
| * License: MIT | ||
| */ | ||
| 'use strict'; | ||
| var ts = require('typescript'); | ||
| var compilerCli = require('@angular/compiler-cli'); | ||
| var project_paths = require('./project_paths-D2V-Uh2L.cjs'); | ||
| /** | ||
| * Applies import manager changes, and writes them as replacements the | ||
| * given result array. | ||
| */ | ||
| function applyImportManagerChanges(importManager, replacements, sourceFiles, info) { | ||
| const { newImports, updatedImports, deletedImports } = importManager.finalize(); | ||
| const printer = ts.createPrinter({}); | ||
| const pathToFile = new Map(sourceFiles.map((s) => [s.fileName, s])); | ||
| // Capture new imports | ||
| newImports.forEach((newImports, fileName) => { | ||
| newImports.forEach((newImport) => { | ||
| const printedImport = printer.printNode(ts.EmitHint.Unspecified, newImport, pathToFile.get(fileName)); | ||
| replacements.push(new project_paths.Replacement(project_paths.projectFile(compilerCli.absoluteFrom(fileName), info), new project_paths.TextUpdate({ position: 0, end: 0, toInsert: `${printedImport}\n` }))); | ||
| }); | ||
| }); | ||
| // Capture updated imports | ||
| for (const [oldBindings, newBindings] of updatedImports.entries()) { | ||
| // The import will be generated as multi-line if it already is multi-line, | ||
| // or if the number of elements significantly increased and it previously | ||
| // consisted of very few specifiers. | ||
| const isMultiline = oldBindings.getText().includes('\n') || | ||
| (newBindings.elements.length >= 6 && oldBindings.elements.length <= 3); | ||
| const hasSpaceBetweenBraces = oldBindings.getText().startsWith('{ '); | ||
| let formatFlags = ts.ListFormat.NamedImportsOrExportsElements | | ||
| ts.ListFormat.Indented | | ||
| ts.ListFormat.Braces | | ||
| ts.ListFormat.PreserveLines | | ||
| (isMultiline ? ts.ListFormat.MultiLine : ts.ListFormat.SingleLine); | ||
| if (hasSpaceBetweenBraces) { | ||
| formatFlags |= ts.ListFormat.SpaceBetweenBraces; | ||
| } | ||
| else { | ||
| formatFlags &= ~ts.ListFormat.SpaceBetweenBraces; | ||
| } | ||
| const printedBindings = printer.printList(formatFlags, newBindings.elements, oldBindings.getSourceFile()); | ||
| replacements.push(new project_paths.Replacement(project_paths.projectFile(oldBindings.getSourceFile(), info), new project_paths.TextUpdate({ | ||
| position: oldBindings.getStart(), | ||
| end: oldBindings.getEnd(), | ||
| // TS uses four spaces as indent. We migrate to two spaces as we | ||
| // assume this to be more common. | ||
| toInsert: printedBindings.replace(/^ {4}/gm, ' '), | ||
| }))); | ||
| } | ||
| // Update removed imports | ||
| for (const removedImport of deletedImports) { | ||
| replacements.push(new project_paths.Replacement(project_paths.projectFile(removedImport.getSourceFile(), info), new project_paths.TextUpdate({ | ||
| position: removedImport.getStart(), | ||
| end: removedImport.getEnd(), | ||
| toInsert: '', | ||
| }))); | ||
| } | ||
| } | ||
| exports.applyImportManagerChanges = applyImportManagerChanges; |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.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
Sorry, the diff of this file is too big to display
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
| * License: MIT | ||
| */ | ||
| 'use strict'; | ||
| var compilerCli = require('@angular/compiler-cli'); | ||
| var schematics = require('@angular-devkit/schematics'); | ||
| var core = require('@angular-devkit/core'); | ||
| var posixPath = require('node:path/posix'); | ||
| var migrations = require('@angular/compiler-cli/private/migrations'); | ||
| var ts = require('typescript'); | ||
| var path = require('node:path'); | ||
| var project_tsconfig_paths = require('./project_tsconfig_paths-DkkMibv-.cjs'); | ||
| function _interopNamespaceDefault(e) { | ||
| var n = Object.create(null); | ||
| if (e) { | ||
| Object.keys(e).forEach(function (k) { | ||
| if (k !== 'default') { | ||
| var d = Object.getOwnPropertyDescriptor(e, k); | ||
| Object.defineProperty(n, k, d.get ? d : { | ||
| enumerable: true, | ||
| get: function () { return e[k]; } | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
| n.default = e; | ||
| return Object.freeze(n); | ||
| } | ||
| var posixPath__namespace = /*#__PURE__*/_interopNamespaceDefault(posixPath); | ||
| var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path); | ||
| /** | ||
| * Angular compiler file system implementation that leverages an | ||
| * CLI schematic virtual file tree. | ||
| */ | ||
| class DevkitMigrationFilesystem { | ||
| tree; | ||
| constructor(tree) { | ||
| this.tree = tree; | ||
| } | ||
| extname(path) { | ||
| return core.extname(path); | ||
| } | ||
| isRoot(path) { | ||
| return path === core.normalize('/'); | ||
| } | ||
| isRooted(path) { | ||
| return this.normalize(path).startsWith('/'); | ||
| } | ||
| dirname(file) { | ||
| return this.normalize(core.dirname(file)); | ||
| } | ||
| join(basePath, ...paths) { | ||
| return this.normalize(core.join(basePath, ...paths)); | ||
| } | ||
| relative(from, to) { | ||
| return this.normalize(core.relative(from, to)); | ||
| } | ||
| basename(filePath, extension) { | ||
| return posixPath__namespace.basename(filePath, extension); | ||
| } | ||
| normalize(path) { | ||
| return core.normalize(path); | ||
| } | ||
| resolve(...paths) { | ||
| const normalizedPaths = paths.map((p) => core.normalize(p)); | ||
| // In dev-kit, the NodeJS working directory should never be | ||
| // considered, so `/` is the last resort over `cwd`. | ||
| return this.normalize(posixPath__namespace.resolve(core.normalize('/'), ...normalizedPaths)); | ||
| } | ||
| pwd() { | ||
| return '/'; | ||
| } | ||
| isCaseSensitive() { | ||
| return true; | ||
| } | ||
| exists(path) { | ||
| return statPath(this.tree, path) !== null; | ||
| } | ||
| readFile(path) { | ||
| return this.tree.readText(path); | ||
| } | ||
| readFileBuffer(path) { | ||
| const buffer = this.tree.read(path); | ||
| if (buffer === null) { | ||
| throw new Error(`File does not exist: ${path}`); | ||
| } | ||
| return buffer; | ||
| } | ||
| readdir(path) { | ||
| const dir = this.tree.getDir(path); | ||
| return [ | ||
| ...dir.subdirs, | ||
| ...dir.subfiles, | ||
| ]; | ||
| } | ||
| lstat(path) { | ||
| const stat = statPath(this.tree, path); | ||
| if (stat === null) { | ||
| throw new Error(`File does not exist for "lstat": ${path}`); | ||
| } | ||
| return stat; | ||
| } | ||
| stat(path) { | ||
| const stat = statPath(this.tree, path); | ||
| if (stat === null) { | ||
| throw new Error(`File does not exist for "stat": ${path}`); | ||
| } | ||
| return stat; | ||
| } | ||
| realpath(filePath) { | ||
| return filePath; | ||
| } | ||
| getDefaultLibLocation() { | ||
| return 'node_modules/typescript/lib'; | ||
| } | ||
| ensureDir(path) { | ||
| // Migrations should compute replacements and not write directly. | ||
| throw new Error('DevkitFilesystem#ensureDir is not supported.'); | ||
| } | ||
| writeFile(path, data) { | ||
| // Migrations should compute replacements and not write directly. | ||
| throw new Error('DevkitFilesystem#writeFile is not supported.'); | ||
| } | ||
| removeFile(path) { | ||
| // Migrations should compute replacements and not write directly. | ||
| throw new Error('DevkitFilesystem#removeFile is not supported.'); | ||
| } | ||
| copyFile(from, to) { | ||
| // Migrations should compute replacements and not write directly. | ||
| throw new Error('DevkitFilesystem#copyFile is not supported.'); | ||
| } | ||
| moveFile(from, to) { | ||
| // Migrations should compute replacements and not write directly. | ||
| throw new Error('DevkitFilesystem#moveFile is not supported.'); | ||
| } | ||
| removeDeep(path) { | ||
| // Migrations should compute replacements and not write directly. | ||
| throw new Error('DevkitFilesystem#removeDeep is not supported.'); | ||
| } | ||
| chdir(_path) { | ||
| throw new Error('FileSystem#chdir is not supported.'); | ||
| } | ||
| symlink() { | ||
| throw new Error('FileSystem#symlink is not supported.'); | ||
| } | ||
| } | ||
| /** Stats the given path in the virtual tree. */ | ||
| function statPath(tree, path) { | ||
| let fileInfo = null; | ||
| let dirInfo = null; | ||
| try { | ||
| fileInfo = tree.get(path); | ||
| } | ||
| catch (e) { | ||
| if (e.constructor.name === 'PathIsDirectoryException') { | ||
| dirInfo = tree.getDir(path); | ||
| } | ||
| else { | ||
| throw e; | ||
| } | ||
| } | ||
| if (fileInfo !== null || dirInfo !== null) { | ||
| return { | ||
| isDirectory: () => dirInfo !== null, | ||
| isFile: () => fileInfo !== null, | ||
| isSymbolicLink: () => false, | ||
| }; | ||
| } | ||
| return null; | ||
| } | ||
| /** | ||
| * Groups the given replacements per project relative | ||
| * file path. | ||
| * | ||
| * This allows for simple execution of the replacements | ||
| * against a given file. E.g. via {@link applyTextUpdates}. | ||
| */ | ||
| function groupReplacementsByFile(replacements) { | ||
| const result = new Map(); | ||
| for (const { projectFile, update } of replacements) { | ||
| if (!result.has(projectFile.rootRelativePath)) { | ||
| result.set(projectFile.rootRelativePath, []); | ||
| } | ||
| result.get(projectFile.rootRelativePath).push(update); | ||
| } | ||
| return result; | ||
| } | ||
| /** | ||
| * Synchronously combines unit data for the given migration. | ||
| * | ||
| * Note: This helper is useful for testing and execution of | ||
| * Tsurge migrations in non-batchable environments. In general, | ||
| * prefer parallel execution of combining via e.g. Beam combiners. | ||
| */ | ||
| async function synchronouslyCombineUnitData(migration, unitDatas) { | ||
| if (unitDatas.length === 0) { | ||
| return null; | ||
| } | ||
| if (unitDatas.length === 1) { | ||
| return unitDatas[0]; | ||
| } | ||
| let combined = unitDatas[0]; | ||
| for (let i = 1; i < unitDatas.length; i++) { | ||
| const other = unitDatas[i]; | ||
| combined = await migration.combine(combined, other); | ||
| } | ||
| return combined; | ||
| } | ||
| /** Whether we are executing inside Google */ | ||
| function isGoogle3() { | ||
| return process.env['GOOGLE3_TSURGE'] === '1'; | ||
| } | ||
| /** | ||
| * By default, Tsurge will always create an Angular compiler program | ||
| * for projects analyzed and migrated. This works perfectly fine in | ||
| * third-party where Tsurge migrations run in Angular CLI projects. | ||
| * | ||
| * In first party, when running against full Google3, creating an Angular | ||
| * program for e.g. plain `ts_library` targets is overly expensive and | ||
| * can result in out of memory issues for large TS targets. In 1P we can | ||
| * reliably distinguish between TS and Angular targets via the `angularCompilerOptions`. | ||
| */ | ||
| function google3UsePlainTsProgramIfNoKnownAngularOption() { | ||
| return process.env['GOOGLE3_TSURGE'] === '1'; | ||
| } | ||
| /** Options that are good defaults for Tsurge migrations. */ | ||
| const defaultMigrationTsOptions = { | ||
| // Avoid checking libraries to speed up migrations. | ||
| skipLibCheck: true, | ||
| skipDefaultLibCheck: true, | ||
| noEmit: true, | ||
| // Does not apply to g3 and externally is enforced when the app is built by the compiler. | ||
| disableTypeScriptVersionCheck: true, | ||
| }; | ||
| /** | ||
| * Creates an instance of a TypeScript program for the given project. | ||
| */ | ||
| function createPlainTsProgram(tsHost, tsconfig, optionOverrides) { | ||
| const program = ts.createProgram({ | ||
| rootNames: tsconfig.rootNames, | ||
| options: { | ||
| ...tsconfig.options, | ||
| ...defaultMigrationTsOptions, | ||
| ...optionOverrides, | ||
| }, | ||
| }); | ||
| return { | ||
| ngCompiler: null, | ||
| program, | ||
| userOptions: tsconfig.options, | ||
| __programAbsoluteRootFileNames: tsconfig.rootNames, | ||
| host: tsHost, | ||
| }; | ||
| } | ||
| /** | ||
| * Parses the configuration of the given TypeScript project and creates | ||
| * an instance of the Angular compiler for the project. | ||
| */ | ||
| function createNgtscProgram(tsHost, tsconfig, optionOverrides) { | ||
| const ngtscProgram = new compilerCli.NgtscProgram(tsconfig.rootNames, { | ||
| ...tsconfig.options, | ||
| ...defaultMigrationTsOptions, | ||
| ...optionOverrides, | ||
| }, tsHost); | ||
| // Expose an easy way to debug-print ng semantic diagnostics. | ||
| if (process.env['DEBUG_NG_SEMANTIC_DIAGNOSTICS'] === '1') { | ||
| console.error(ts.formatDiagnosticsWithColorAndContext(ngtscProgram.getNgSemanticDiagnostics(), tsHost)); | ||
| } | ||
| return { | ||
| ngCompiler: ngtscProgram.compiler, | ||
| program: ngtscProgram.getTsProgram(), | ||
| userOptions: tsconfig.options, | ||
| __programAbsoluteRootFileNames: tsconfig.rootNames, | ||
| host: tsHost, | ||
| }; | ||
| } | ||
| /** Code of the error raised by TypeScript when a tsconfig doesn't match any files. */ | ||
| const NO_INPUTS_ERROR_CODE = 18003; | ||
| /** Parses the given tsconfig file, supporting Angular compiler options. */ | ||
| function parseTsconfigOrDie(absoluteTsconfigPath, fs) { | ||
| const tsconfig = compilerCli.readConfiguration(absoluteTsconfigPath, {}, fs); | ||
| // Skip the "No inputs found..." error since we don't want to interrupt the migration if a | ||
| // tsconfig doesn't match a file. This will result in an empty `Program` which is still valid. | ||
| const errors = tsconfig.errors.filter((diag) => diag.code !== NO_INPUTS_ERROR_CODE); | ||
| if (errors.length) { | ||
| throw new Error(`Tsconfig could not be parsed or is invalid:\n\n` + `${errors.map((e) => e.messageText)}`); | ||
| } | ||
| return tsconfig; | ||
| } | ||
| // Note: Try to keep mostly in sync with | ||
| // //depot/google3/javascript/angular2/tools/ngc_wrapped/tsc_plugin.ts | ||
| // TODO: Consider moving this logic into the 1P launcher. | ||
| const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; | ||
| function fileNameToModuleNameFactory(rootDirs, workspaceName) { | ||
| return (importedFilePath) => { | ||
| let relativePath = ''; | ||
| for (const rootDir of rootDirs) { | ||
| const rel = path__namespace.posix.relative(rootDir, importedFilePath); | ||
| if (!rel.startsWith('.')) { | ||
| relativePath = rel; | ||
| break; | ||
| } | ||
| } | ||
| if (relativePath) { | ||
| return `${workspaceName}/${relativePath.replace(EXT, '')}`; | ||
| } | ||
| else { | ||
| return importedFilePath.replace(EXT, ''); | ||
| } | ||
| }; | ||
| } | ||
| /** Creates the base program info for the given tsconfig path. */ | ||
| function createBaseProgramInfo(absoluteTsconfigPath, fs, optionOverrides = {}) { | ||
| // Make sure the FS becomes globally available. Some code paths | ||
| // of the Angular compiler, or tsconfig parsing aren't leveraging | ||
| // the specified file system. | ||
| compilerCli.setFileSystem(fs); | ||
| const tsconfig = parseTsconfigOrDie(absoluteTsconfigPath, fs); | ||
| const tsHost = new compilerCli.NgtscCompilerHost(fs, tsconfig.options); | ||
| // When enabled, use a plain TS program if we are sure it's not | ||
| // an Angular project based on the `tsconfig.json`. | ||
| if (google3UsePlainTsProgramIfNoKnownAngularOption() && | ||
| tsconfig.options['_useHostForImportGeneration'] === undefined) { | ||
| return createPlainTsProgram(tsHost, tsconfig, optionOverrides); | ||
| } | ||
| // The Angular program may try to emit references during analysis or migration. | ||
| // To replicate the Google3 import emission here, ensure the unified module resolution | ||
| // can be enabled by the compiler. | ||
| if (isGoogle3() && tsconfig.options.rootDirs) { | ||
| tsHost.fileNameToModuleName = fileNameToModuleNameFactory(tsconfig.options.rootDirs, | ||
| /* workspaceName*/ 'google3'); | ||
| } | ||
| return createNgtscProgram(tsHost, tsconfig, optionOverrides); | ||
| } | ||
| /** | ||
| * Creates the {@link ProgramInfo} from the given base information. | ||
| * | ||
| * This function purely exists to support custom programs that are | ||
| * intended to be injected into Tsurge migrations. e.g. for language | ||
| * service refactorings. | ||
| */ | ||
| function getProgramInfoFromBaseInfo(baseInfo) { | ||
| const fullProgramSourceFiles = [...baseInfo.program.getSourceFiles()]; | ||
| const sourceFiles = fullProgramSourceFiles.filter((f) => !f.isDeclarationFile && | ||
| // Note `isShim` will work for the initial program, but for TCB programs, the shims are no longer annotated. | ||
| !migrations.isShim(f) && | ||
| !f.fileName.endsWith('.ngtypecheck.ts')); | ||
| // Sort it by length in reverse order (longest first). This speeds up lookups, | ||
| // since there's no need to keep going through the array once a match is found. | ||
| const sortedRootDirs = migrations.getRootDirs(baseInfo.host, baseInfo.userOptions).sort((a, b) => b.length - a.length); | ||
| // TODO: Consider also following TS's logic here, finding the common source root. | ||
| // See: Program#getCommonSourceDirectory. | ||
| const primaryRoot = compilerCli.absoluteFrom(baseInfo.userOptions.rootDir ?? sortedRootDirs.at(-1) ?? baseInfo.program.getCurrentDirectory()); | ||
| return { | ||
| ...baseInfo, | ||
| sourceFiles, | ||
| fullProgramSourceFiles, | ||
| sortedRootDirs, | ||
| projectRoot: primaryRoot, | ||
| }; | ||
| } | ||
| /** | ||
| * @private | ||
| * | ||
| * Base class for the possible Tsurge migration variants. | ||
| * | ||
| * For example, this class exposes methods to conveniently create | ||
| * TypeScript programs, while also allowing migration authors to override. | ||
| */ | ||
| class TsurgeBaseMigration { | ||
| /** | ||
| * Creates the TypeScript program for a given compilation unit. | ||
| * | ||
| * By default: | ||
| * - In 3P: Ngtsc programs are being created. | ||
| * - In 1P: Ngtsc or TS programs are created based on the Blaze target. | ||
| */ | ||
| createProgram(tsconfigAbsPath, fs, optionsOverride) { | ||
| return getProgramInfoFromBaseInfo(createBaseProgramInfo(tsconfigAbsPath, fs, optionsOverride)); | ||
| } | ||
| } | ||
| /** | ||
| * A simpler variant of a {@link TsurgeComplexMigration} that does not | ||
| * fan-out into multiple workers per compilation unit to compute | ||
| * the final migration replacements. | ||
| * | ||
| * This is faster and less resource intensive as workers and TS programs | ||
| * are only ever created once. | ||
| * | ||
| * This is commonly the case when migrations are refactored to eagerly | ||
| * compute replacements in the analyze stage, and then leverage the | ||
| * global unit data to filter replacements that turned out to be "invalid". | ||
| */ | ||
| class TsurgeFunnelMigration extends TsurgeBaseMigration { | ||
| } | ||
| /** | ||
| * Complex variant of a `Tsurge` migration. | ||
| * | ||
| * For example, every analyze worker may contribute to a list of TS | ||
| * references that are later combined. The migrate phase can then compute actual | ||
| * file updates for all individual compilation units, leveraging the global metadata | ||
| * to e.g. see if there are any references from other compilation units that may be | ||
| * problematic and prevent migration of a given file. | ||
| */ | ||
| class TsurgeComplexMigration extends TsurgeBaseMigration { | ||
| } | ||
| exports.MigrationStage = void 0; | ||
| (function (MigrationStage) { | ||
| /** The migration is analyzing an entrypoint */ | ||
| MigrationStage[MigrationStage["Analysis"] = 0] = "Analysis"; | ||
| /** The migration is about to migrate an entrypoint */ | ||
| MigrationStage[MigrationStage["Migrate"] = 1] = "Migrate"; | ||
| })(exports.MigrationStage || (exports.MigrationStage = {})); | ||
| /** Runs a Tsurge within an Angular Devkit context. */ | ||
| async function runMigrationInDevkit(config) { | ||
| const { buildPaths, testPaths } = await project_tsconfig_paths.getProjectTsConfigPaths(config.tree); | ||
| if (!buildPaths.length && !testPaths.length) { | ||
| throw new schematics.SchematicsException('Could not find any tsconfig file. Cannot run the migration.'); | ||
| } | ||
| const tsconfigPaths = [...buildPaths, ...testPaths]; | ||
| const fs = new DevkitMigrationFilesystem(config.tree); | ||
| compilerCli.setFileSystem(fs); | ||
| const migration = config.getMigration(fs); | ||
| const unitResults = []; | ||
| const isFunnelMigration = migration instanceof TsurgeFunnelMigration; | ||
| const compilationUnitAssignments = new Map(); | ||
| for (const tsconfigPath of tsconfigPaths) { | ||
| config.beforeProgramCreation?.(tsconfigPath, exports.MigrationStage.Analysis); | ||
| const info = migration.createProgram(tsconfigPath, fs); | ||
| modifyProgramInfoToEnsureNonOverlappingFiles(tsconfigPath, info, compilationUnitAssignments); | ||
| config.afterProgramCreation?.(info, fs, exports.MigrationStage.Analysis); | ||
| config.beforeUnitAnalysis?.(tsconfigPath); | ||
| unitResults.push(await migration.analyze(info)); | ||
| } | ||
| config.afterAllAnalyzed?.(); | ||
| const combined = await synchronouslyCombineUnitData(migration, unitResults); | ||
| if (combined === null) { | ||
| config.afterAnalysisFailure?.(); | ||
| return; | ||
| } | ||
| const globalMeta = await migration.globalMeta(combined); | ||
| let replacements; | ||
| if (isFunnelMigration) { | ||
| replacements = (await migration.migrate(globalMeta)).replacements; | ||
| } | ||
| else { | ||
| replacements = []; | ||
| for (const tsconfigPath of tsconfigPaths) { | ||
| config.beforeProgramCreation?.(tsconfigPath, exports.MigrationStage.Migrate); | ||
| const info = migration.createProgram(tsconfigPath, fs); | ||
| modifyProgramInfoToEnsureNonOverlappingFiles(tsconfigPath, info, compilationUnitAssignments); | ||
| config.afterProgramCreation?.(info, fs, exports.MigrationStage.Migrate); | ||
| const result = await migration.migrate(globalMeta, info); | ||
| replacements.push(...result.replacements); | ||
| } | ||
| } | ||
| const replacementsPerFile = new Map(); | ||
| const changesPerFile = groupReplacementsByFile(replacements); | ||
| for (const [file, changes] of changesPerFile) { | ||
| if (!replacementsPerFile.has(file)) { | ||
| replacementsPerFile.set(file, changes); | ||
| } | ||
| } | ||
| for (const [file, changes] of replacementsPerFile) { | ||
| const recorder = config.tree.beginUpdate(file); | ||
| for (const c of changes) { | ||
| recorder | ||
| .remove(c.data.position, c.data.end - c.data.position) | ||
| .insertRight(c.data.position, c.data.toInsert); | ||
| } | ||
| config.tree.commitUpdate(recorder); | ||
| } | ||
| config.whenDone?.(await migration.stats(globalMeta)); | ||
| } | ||
| /** | ||
| * Special logic for devkit migrations. In the Angular CLI, or in 3P precisely, | ||
| * projects can have tsconfigs with overlapping source files. i.e. two tsconfigs | ||
| * like e.g. build or test include the same `ts.SourceFile` (`.ts`). Migrations | ||
| * should never have 2+ compilation units with overlapping source files as this | ||
| * can result in duplicated replacements or analysis— hence we only ever assign a | ||
| * source file to a compilation unit *once*. | ||
| * | ||
| * Note that this is fine as we expect Tsurge migrations to work together as | ||
| * isolated compilation units— so it shouldn't matter if worst case a `.ts` | ||
| * file ends up in the e.g. test program. | ||
| */ | ||
| function modifyProgramInfoToEnsureNonOverlappingFiles(tsconfigPath, info, compilationUnitAssignments) { | ||
| const sourceFiles = []; | ||
| for (const sf of info.sourceFiles) { | ||
| const assignment = compilationUnitAssignments.get(sf.fileName); | ||
| // File is already assigned to a different compilation unit. | ||
| if (assignment !== undefined && assignment !== tsconfigPath) { | ||
| continue; | ||
| } | ||
| compilationUnitAssignments.set(sf.fileName, tsconfigPath); | ||
| sourceFiles.push(sf); | ||
| } | ||
| info.sourceFiles = sourceFiles; | ||
| } | ||
| /** A text replacement for the given file. */ | ||
| class Replacement { | ||
| projectFile; | ||
| update; | ||
| constructor(projectFile, update) { | ||
| this.projectFile = projectFile; | ||
| this.update = update; | ||
| } | ||
| } | ||
| /** An isolated text update that may be applied to a file. */ | ||
| class TextUpdate { | ||
| data; | ||
| constructor(data) { | ||
| this.data = data; | ||
| } | ||
| } | ||
| /** Confirms that the given data `T` is serializable. */ | ||
| function confirmAsSerializable(data) { | ||
| return data; | ||
| } | ||
| /** | ||
| * Gets a project file instance for the given file. | ||
| * | ||
| * Use this helper for dealing with project paths throughout your | ||
| * migration. The return type is serializable. | ||
| * | ||
| * See {@link ProjectFile}. | ||
| */ | ||
| function projectFile(file, { sortedRootDirs, projectRoot }) { | ||
| const fs = compilerCli.getFileSystem(); | ||
| const filePath = fs.resolve(typeof file === 'string' ? file : file.fileName); | ||
| // Sorted root directories are sorted longest to shortest. First match | ||
| // is the appropriate root directory for ID computation. | ||
| for (const rootDir of sortedRootDirs) { | ||
| if (!isWithinBasePath(fs, rootDir, filePath)) { | ||
| continue; | ||
| } | ||
| return { | ||
| id: fs.relative(rootDir, filePath), | ||
| rootRelativePath: fs.relative(projectRoot, filePath), | ||
| }; | ||
| } | ||
| // E.g. project directory may be `src/`, but files may be looked up | ||
| // from `node_modules/`. This is fine, but in those cases, no root | ||
| // directory matches. | ||
| const rootRelativePath = fs.relative(projectRoot, filePath); | ||
| return { | ||
| id: rootRelativePath, | ||
| rootRelativePath: rootRelativePath, | ||
| }; | ||
| } | ||
| /** | ||
| * Whether `path` is a descendant of the `base`? | ||
| * E.g. `a/b/c` is within `a/b` but not within `a/x`. | ||
| */ | ||
| function isWithinBasePath(fs, base, path) { | ||
| return compilerCli.isLocalRelativePath(fs.relative(base, path)); | ||
| } | ||
| exports.Replacement = Replacement; | ||
| exports.TextUpdate = TextUpdate; | ||
| exports.TsurgeComplexMigration = TsurgeComplexMigration; | ||
| exports.TsurgeFunnelMigration = TsurgeFunnelMigration; | ||
| exports.confirmAsSerializable = confirmAsSerializable; | ||
| exports.projectFile = projectFile; | ||
| exports.runMigrationInDevkit = runMigrationInDevkit; |
| 'use strict'; | ||
| /** | ||
| * @license Angular v22.0.1 | ||
| * (c) 2010-2026 Google LLC. https://angular.dev/ | ||
| * License: MIT | ||
| */ | ||
| 'use strict'; | ||
| var core = require('@angular-devkit/core'); | ||
| /** | ||
| * Gets all tsconfig paths from a CLI project by reading the workspace configuration | ||
| * and looking for common tsconfig locations. | ||
| */ | ||
| async function getProjectTsConfigPaths(tree) { | ||
| // Start with some tsconfig paths that are generally used within CLI projects. Note | ||
| // that we are not interested in IDE-specific tsconfig files (e.g. /tsconfig.json) | ||
| const buildPaths = new Set(); | ||
| const testPaths = new Set(); | ||
| const workspace = await getWorkspace(tree); | ||
| for (const [, project] of workspace.projects) { | ||
| for (const [name, target] of project.targets) { | ||
| if (name !== 'build' && name !== 'test') { | ||
| continue; | ||
| } | ||
| for (const [, options] of allTargetOptions(target)) { | ||
| const tsConfig = options['tsConfig']; | ||
| // Filter out tsconfig files that don't exist in the CLI project. | ||
| if (typeof tsConfig !== 'string' || !tree.exists(tsConfig)) { | ||
| continue; | ||
| } | ||
| if (name === 'build') { | ||
| buildPaths.add(core.normalize(tsConfig)); | ||
| } | ||
| else { | ||
| testPaths.add(core.normalize(tsConfig)); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return { | ||
| buildPaths: [...buildPaths], | ||
| testPaths: [...testPaths], | ||
| }; | ||
| } | ||
| /** Get options for all configurations for the passed builder target. */ | ||
| function* allTargetOptions(target) { | ||
| if (target.options) { | ||
| yield [undefined, target.options]; | ||
| } | ||
| if (!target.configurations) { | ||
| return; | ||
| } | ||
| for (const [name, options] of Object.entries(target.configurations)) { | ||
| if (options) { | ||
| yield [name, options]; | ||
| } | ||
| } | ||
| } | ||
| function createHost(tree) { | ||
| return { | ||
| async readFile(path) { | ||
| const data = tree.read(path); | ||
| if (!data) { | ||
| throw new Error('File not found.'); | ||
| } | ||
| return core.virtualFs.fileBufferToString(data); | ||
| }, | ||
| async writeFile(path, data) { | ||
| return tree.overwrite(path, data); | ||
| }, | ||
| async isDirectory(path) { | ||
| // Approximate a directory check. | ||
| // We don't need to consider empty directories and hence this is a good enough approach. | ||
| // This is also per documentation, see: | ||
| // https://angular.dev/tools/cli/schematics-for-libraries#get-the-project-configuration | ||
| return !tree.exists(path) && tree.getDir(path).subfiles.length > 0; | ||
| }, | ||
| async isFile(path) { | ||
| return tree.exists(path); | ||
| }, | ||
| }; | ||
| } | ||
| async function getWorkspace(tree) { | ||
| const host = createHost(tree); | ||
| const { workspace } = await core.workspaces.readWorkspace('/', host); | ||
| return workspace; | ||
| } | ||
| exports.getProjectTsConfigPaths = getProjectTsConfigPaths; |
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
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
7104374
0.15%70827
0.09%