Skip to content

Commit e99bbd3

Browse files
aparziAndrewKushnir
authored andcommitted
fix(migrations): migrating input with more than 1 usage in a method (#64367)
When the migration command was run for inputs, if the input had more than one reference in a method the migration would generate incorrect code Fixes #63018 PR Close #64367
1 parent 4b965f5 commit e99bbd3

File tree

4 files changed

+100
-1
lines changed

4 files changed

+100
-1
lines changed

packages/core/schematics/migrations/signal-migration/src/passes/reference_migration/helpers/standard_reference.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {analyzeControlFlow, ControlFlowAnalysisNode} from '../../../flow_analysi
1111
import {ProgramInfo, projectFile, Replacement, TextUpdate} from '../../../../../../utils/tsurge';
1212
import {traverseAccess} from '../../../utils/traverse_access';
1313
import {UniqueNamesGenerator} from '../../../utils/unique_names';
14-
import {createNewBlockToInsertVariable} from '../helpers/create_block_arrow_function';
14+
import {createNewBlockToInsertVariable} from './create_block_arrow_function';
1515
import assert from 'assert';
1616

1717
export interface NarrowableTsReferences {
@@ -123,6 +123,23 @@ export function migrateStandardTsReference(
123123
replacements.push(
124124
...createNewBlockToInsertVariable(parent, filePath, temporaryVariableStr),
125125
);
126+
} else if (shouldInsertAtMethodStart(reference, recommendedNode, referenceNodeInBlock)) {
127+
const blockNode = recommendedNode as ts.Block;
128+
const firstStatement = blockNode.statements[0];
129+
const leadingSpace = firstStatement
130+
? ts.getLineAndCharacterOfPosition(sf, firstStatement.getStart())
131+
: ts.getLineAndCharacterOfPosition(sf, referenceNodeInBlock.getStart());
132+
133+
replacements.push(
134+
new Replacement(
135+
filePath,
136+
new TextUpdate({
137+
position: firstStatement.getStart(),
138+
end: firstStatement.getStart(),
139+
toInsert: `${temporaryVariableStr}\n${' '.repeat(leadingSpace.character)}`,
140+
}),
141+
),
142+
);
126143
} else {
127144
const leadingSpace = ts.getLineAndCharacterOfPosition(sf, referenceNodeInBlock.getStart());
128145

@@ -151,3 +168,43 @@ export function migrateStandardTsReference(
151168
}
152169
}
153170
}
171+
172+
/**
173+
* Determines if a temporary variable should be inserted at the start of a method.
174+
*
175+
* This function performs several checks to ensure it's safe to insert a temporary variable:
176+
* 1. Verifies the recommended node is a method declaration block
177+
* 2. Ensures all references are contained within the method body
178+
* 3. Confirms the reference node is the first statement in the method
179+
* 4. Validates the reference node is an expression statement with an assignment operation
180+
*/
181+
function shouldInsertAtMethodStart(
182+
references: NarrowableTsReferences,
183+
recommendedNode: ts.Node,
184+
referenceNodeInBlock: ts.Node,
185+
): boolean {
186+
if (!ts.isBlock(recommendedNode) || !ts.isMethodDeclaration(recommendedNode.parent)) {
187+
return false;
188+
}
189+
190+
const methodBody = recommendedNode;
191+
const allReferencesInMethod = references.accesses.every((access) => {
192+
let current: ts.Node | undefined = access;
193+
while (current && current !== methodBody) {
194+
current = current.parent;
195+
}
196+
return current === methodBody;
197+
});
198+
199+
if (!allReferencesInMethod) {
200+
return false;
201+
}
202+
203+
return (
204+
methodBody.statements.length > 0 &&
205+
ts.isExpressionStatement(referenceNodeInBlock) &&
206+
methodBody.statements[0] === referenceNodeInBlock &&
207+
ts.isBinaryExpression(referenceNodeInBlock.expression) &&
208+
referenceNodeInBlock.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken
209+
);
210+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// tslint:disable
2+
3+
import {Input} from '@angular/core';
4+
5+
export class TestMigrationComponent {
6+
@Input() public model: any;
7+
8+
public onSaveClick(): void {
9+
this.model.requisitionId = 145;
10+
this.model.comment = 'value';
11+
}
12+
}

packages/core/schematics/migrations/signal-migration/test/golden.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,21 @@ class ModifierScenarios {
848848
@CustomDecorator()
849849
protected readonly usingCustomDecorator = input(true);
850850
}
851+
@@@@@@ multiple_references_in_method.ts @@@@@@
852+
853+
// tslint:disable
854+
855+
import {input} from '@angular/core';
856+
857+
export class TestMigrationComponent {
858+
public readonly model = input<any>();
859+
860+
public onSaveClick(): void {
861+
const model = this.model();
862+
model.requisitionId = 145;
863+
model.comment = 'value';
864+
}
865+
}
851866
@@@@@@ mutate.ts @@@@@@
852867

853868
// tslint:disable

packages/core/schematics/migrations/signal-migration/test/golden_best_effort.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,21 @@ class ModifierScenarios {
815815
@CustomDecorator()
816816
protected readonly usingCustomDecorator = input(true);
817817
}
818+
@@@@@@ multiple_references_in_method.ts @@@@@@
819+
820+
// tslint:disable
821+
822+
import {input} from '@angular/core';
823+
824+
export class TestMigrationComponent {
825+
public readonly model = input<any>();
826+
827+
public onSaveClick(): void {
828+
const model = this.model();
829+
model.requisitionId = 145;
830+
model.comment = 'value';
831+
}
832+
}
818833
@@@@@@ mutate.ts @@@@@@
819834

820835
// tslint:disable

0 commit comments

Comments
 (0)