diff --git a/packages/core/schematics/migrations/signal-migration/src/passes/reference_migration/helpers/standard_reference.ts b/packages/core/schematics/migrations/signal-migration/src/passes/reference_migration/helpers/standard_reference.ts index cc073e50d3db..180b295ca7e8 100644 --- a/packages/core/schematics/migrations/signal-migration/src/passes/reference_migration/helpers/standard_reference.ts +++ b/packages/core/schematics/migrations/signal-migration/src/passes/reference_migration/helpers/standard_reference.ts @@ -11,7 +11,7 @@ import {analyzeControlFlow, ControlFlowAnalysisNode} from '../../../flow_analysi import {ProgramInfo, projectFile, Replacement, TextUpdate} from '../../../../../../utils/tsurge'; import {traverseAccess} from '../../../utils/traverse_access'; import {UniqueNamesGenerator} from '../../../utils/unique_names'; -import {createNewBlockToInsertVariable} from '../helpers/create_block_arrow_function'; +import {createNewBlockToInsertVariable} from './create_block_arrow_function'; import assert from 'assert'; export interface NarrowableTsReferences { @@ -123,6 +123,23 @@ export function migrateStandardTsReference( replacements.push( ...createNewBlockToInsertVariable(parent, filePath, temporaryVariableStr), ); + } else if (shouldInsertAtMethodStart(reference, recommendedNode, referenceNodeInBlock)) { + const blockNode = recommendedNode as ts.Block; + const firstStatement = blockNode.statements[0]; + const leadingSpace = firstStatement + ? ts.getLineAndCharacterOfPosition(sf, firstStatement.getStart()) + : ts.getLineAndCharacterOfPosition(sf, referenceNodeInBlock.getStart()); + + replacements.push( + new Replacement( + filePath, + new TextUpdate({ + position: firstStatement.getStart(), + end: firstStatement.getStart(), + toInsert: `${temporaryVariableStr}\n${' '.repeat(leadingSpace.character)}`, + }), + ), + ); } else { const leadingSpace = ts.getLineAndCharacterOfPosition(sf, referenceNodeInBlock.getStart()); @@ -151,3 +168,43 @@ export function migrateStandardTsReference( } } } + +/** + * Determines if a temporary variable should be inserted at the start of a method. + * + * This function performs several checks to ensure it's safe to insert a temporary variable: + * 1. Verifies the recommended node is a method declaration block + * 2. Ensures all references are contained within the method body + * 3. Confirms the reference node is the first statement in the method + * 4. Validates the reference node is an expression statement with an assignment operation + */ +function shouldInsertAtMethodStart( + references: NarrowableTsReferences, + recommendedNode: ts.Node, + referenceNodeInBlock: ts.Node, +): boolean { + if (!ts.isBlock(recommendedNode) || !ts.isMethodDeclaration(recommendedNode.parent)) { + return false; + } + + const methodBody = recommendedNode; + const allReferencesInMethod = references.accesses.every((access) => { + let current: ts.Node | undefined = access; + while (current && current !== methodBody) { + current = current.parent; + } + return current === methodBody; + }); + + if (!allReferencesInMethod) { + return false; + } + + return ( + methodBody.statements.length > 0 && + ts.isExpressionStatement(referenceNodeInBlock) && + methodBody.statements[0] === referenceNodeInBlock && + ts.isBinaryExpression(referenceNodeInBlock.expression) && + referenceNodeInBlock.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken + ); +} diff --git a/packages/core/schematics/migrations/signal-migration/test/golden-test/multiple_references_in_method.ts b/packages/core/schematics/migrations/signal-migration/test/golden-test/multiple_references_in_method.ts new file mode 100644 index 000000000000..d7ced2e8fcc9 --- /dev/null +++ b/packages/core/schematics/migrations/signal-migration/test/golden-test/multiple_references_in_method.ts @@ -0,0 +1,12 @@ +// tslint:disable + +import {Input} from '@angular/core'; + +export class TestMigrationComponent { + @Input() public model: any; + + public onSaveClick(): void { + this.model.requisitionId = 145; + this.model.comment = 'value'; + } +} diff --git a/packages/core/schematics/migrations/signal-migration/test/golden.txt b/packages/core/schematics/migrations/signal-migration/test/golden.txt index 4f228814b863..d2eeac5272fe 100644 --- a/packages/core/schematics/migrations/signal-migration/test/golden.txt +++ b/packages/core/schematics/migrations/signal-migration/test/golden.txt @@ -848,6 +848,21 @@ class ModifierScenarios { @CustomDecorator() protected readonly usingCustomDecorator = input(true); } +@@@@@@ multiple_references_in_method.ts @@@@@@ + +// tslint:disable + +import {input} from '@angular/core'; + +export class TestMigrationComponent { + public readonly model = input(); + + public onSaveClick(): void { + const model = this.model(); + model.requisitionId = 145; + model.comment = 'value'; + } +} @@@@@@ mutate.ts @@@@@@ // tslint:disable diff --git a/packages/core/schematics/migrations/signal-migration/test/golden_best_effort.txt b/packages/core/schematics/migrations/signal-migration/test/golden_best_effort.txt index 828b5df2d86b..233e65c939ed 100644 --- a/packages/core/schematics/migrations/signal-migration/test/golden_best_effort.txt +++ b/packages/core/schematics/migrations/signal-migration/test/golden_best_effort.txt @@ -815,6 +815,21 @@ class ModifierScenarios { @CustomDecorator() protected readonly usingCustomDecorator = input(true); } +@@@@@@ multiple_references_in_method.ts @@@@@@ + +// tslint:disable + +import {input} from '@angular/core'; + +export class TestMigrationComponent { + public readonly model = input(); + + public onSaveClick(): void { + const model = this.model(); + model.requisitionId = 145; + model.comment = 'value'; + } +} @@@@@@ mutate.ts @@@@@@ // tslint:disable