mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
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
This commit is contained in:
parent
4b965f5b77
commit
e99bbd3d2a
4 changed files with 100 additions and 1 deletions
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
@ -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<any>();
|
||||
|
||||
public onSaveClick(): void {
|
||||
const model = this.model();
|
||||
model.requisitionId = 145;
|
||||
model.comment = 'value';
|
||||
}
|
||||
}
|
||||
@@@@@@ mutate.ts @@@@@@
|
||||
|
||||
// tslint:disable
|
||||
|
|
|
|||
|
|
@ -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<any>();
|
||||
|
||||
public onSaveClick(): void {
|
||||
const model = this.model();
|
||||
model.requisitionId = 145;
|
||||
model.comment = 'value';
|
||||
}
|
||||
}
|
||||
@@@@@@ mutate.ts @@@@@@
|
||||
|
||||
// tslint:disable
|
||||
|
|
|
|||
Loading…
Reference in a new issue