mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
refactor(migrations): share reference migration code of signal input migration (#57766)
The reference migration code of the signal input migration should be re-usable as the queries migration needs the exact same. This builds on top of the shared reference resolution logic from previous commits. Similarly this commit introduces a small "host" for providing necessary configurable information about what references should be migrated; supporting e.g. "input incompatibilities". This is important as e.g. queries may also be incompatible for migration and references to such, should not be migrated. PR Close #57766
This commit is contained in:
parent
acafa3b91d
commit
ebe0c168e2
14 changed files with 395 additions and 139 deletions
|
|
@ -7,89 +7,23 @@
|
|||
*/
|
||||
|
||||
import ts from 'typescript';
|
||||
import {KnownInputs} from '../input_detection/known_inputs';
|
||||
import {MigrationResult} from '../result';
|
||||
import {
|
||||
migrateBindingElementInputReference,
|
||||
IdentifierOfBindingElement,
|
||||
} from './migrate_ts_reference/object_expansion_refs';
|
||||
import {
|
||||
migrateStandardTsReference,
|
||||
NarrowableTsReferences,
|
||||
} from './migrate_ts_reference/standard_reference';
|
||||
import {migrateTypeScriptReferences} from './reference_migration/migrate_ts_references';
|
||||
import {ProgramInfo} from '../../../../utils/tsurge';
|
||||
import {ClassFieldUniqueKey} from './reference_resolution/known_fields';
|
||||
import {isTsReference} from './reference_resolution/reference_kinds';
|
||||
import {Reference} from './reference_resolution/reference_kinds';
|
||||
import {ClassFieldDescriptor} from './reference_resolution/known_fields';
|
||||
import {ReferenceMigrationHost} from './reference_migration/reference_migration_host';
|
||||
|
||||
/**
|
||||
* Phase that migrates TypeScript input references to be signal compatible.
|
||||
*
|
||||
* The phase takes care of control flow analysis and generates temporary variables
|
||||
* where needed to ensure narrowing continues to work. E.g.
|
||||
*
|
||||
* ```
|
||||
* someMethod() {
|
||||
* if (this.input) {
|
||||
* this.input.charAt(0);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* will be transformed into:
|
||||
*
|
||||
* ```
|
||||
* someMethod() {
|
||||
* const input_1 = this.input();
|
||||
* if (input_1) {
|
||||
* input_1.charAt(0);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function pass5__migrateTypeScriptReferences(
|
||||
result: MigrationResult,
|
||||
export function pass5__migrateTypeScriptReferences<D extends ClassFieldDescriptor>(
|
||||
host: ReferenceMigrationHost<D>,
|
||||
references: Reference<D>[],
|
||||
checker: ts.TypeChecker,
|
||||
knownInputs: KnownInputs,
|
||||
info: ProgramInfo,
|
||||
) {
|
||||
const tsReferencesWithNarrowing = new Map<ClassFieldUniqueKey, NarrowableTsReferences>();
|
||||
const tsReferencesInBindingElements = new Set<IdentifierOfBindingElement>();
|
||||
|
||||
const seenIdentifiers = new WeakSet<ts.Identifier>();
|
||||
|
||||
for (const reference of result.references) {
|
||||
// This pass only deals with TS references.
|
||||
if (!isTsReference(reference)) {
|
||||
continue;
|
||||
}
|
||||
// Skip references to incompatible inputs.
|
||||
if (knownInputs.get(reference.target)!.isIncompatible()) {
|
||||
continue;
|
||||
}
|
||||
// Never attempt to migrate write references.
|
||||
// Those usually invalidate the target input most of the time, but in
|
||||
// best-effort mode they are not.
|
||||
if (reference.from.isWrite) {
|
||||
continue;
|
||||
}
|
||||
// Skip duplicate references. E.g. in batching.
|
||||
if (seenIdentifiers.has(reference.from.node)) {
|
||||
continue;
|
||||
}
|
||||
seenIdentifiers.add(reference.from.node);
|
||||
|
||||
const targetKey = reference.target.key;
|
||||
|
||||
if (reference.from.isPartOfElementBinding) {
|
||||
tsReferencesInBindingElements.add(reference.from.node as IdentifierOfBindingElement);
|
||||
} else {
|
||||
if (!tsReferencesWithNarrowing.has(targetKey)) {
|
||||
tsReferencesWithNarrowing.set(targetKey, {accesses: []});
|
||||
}
|
||||
tsReferencesWithNarrowing.get(targetKey)!.accesses.push(reference.from.node);
|
||||
}
|
||||
}
|
||||
|
||||
migrateBindingElementInputReference(tsReferencesInBindingElements, info, result);
|
||||
migrateStandardTsReference(tsReferencesWithNarrowing, checker, result, info);
|
||||
migrateTypeScriptReferences(host, references, checker, info);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,28 +6,27 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {MigrationResult} from '../result';
|
||||
import {KnownInputs} from '../input_detection/known_inputs';
|
||||
import {Replacement, TextUpdate} from '../../../../utils/tsurge';
|
||||
import {isTemplateReference} from './reference_resolution/reference_kinds';
|
||||
|
||||
import {ReferenceMigrationHost} from './reference_migration/reference_migration_host';
|
||||
import {ClassFieldDescriptor} from './reference_resolution/known_fields';
|
||||
import {isTemplateReference, Reference} from './reference_resolution/reference_kinds';
|
||||
/**
|
||||
* Phase that migrates Angular template references to
|
||||
* unwrap signals.
|
||||
*/
|
||||
export function pass7__migrateTemplateReferences(
|
||||
result: MigrationResult,
|
||||
knownInputs: KnownInputs,
|
||||
export function pass7__migrateTemplateReferences<D extends ClassFieldDescriptor>(
|
||||
host: ReferenceMigrationHost<D>,
|
||||
references: Reference<D>[],
|
||||
) {
|
||||
const seenFileReferences = new Set<string>();
|
||||
|
||||
for (const reference of result.references) {
|
||||
for (const reference of references) {
|
||||
// This pass only deals with HTML template references.
|
||||
if (!isTemplateReference(reference)) {
|
||||
continue;
|
||||
}
|
||||
// Skip references to incompatible inputs.
|
||||
if (knownInputs.get(reference.target)!.isIncompatible()) {
|
||||
if (!host.shouldMigrateReferencesToField(reference.target)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -43,7 +42,7 @@ export function pass7__migrateTemplateReferences(
|
|||
? `: ${reference.from.read.name}()`
|
||||
: `()`;
|
||||
|
||||
result.replacements.push(
|
||||
host.replacements.push(
|
||||
new Replacement(
|
||||
reference.from.templateFile,
|
||||
new TextUpdate({
|
||||
|
|
|
|||
|
|
@ -7,29 +7,29 @@
|
|||
*/
|
||||
|
||||
import ts from 'typescript';
|
||||
import {ReferenceMigrationHost} from './reference_migration/reference_migration_host';
|
||||
import {ClassFieldDescriptor} from './reference_resolution/known_fields';
|
||||
import {isHostBindingReference, Reference} from './reference_resolution/reference_kinds';
|
||||
import {ProgramInfo, projectFile, Replacement, TextUpdate} from '../../../../utils/tsurge';
|
||||
import {MigrationResult} from '../result';
|
||||
import {KnownInputs} from '../input_detection/known_inputs';
|
||||
import {isHostBindingReference} from './reference_resolution/reference_kinds';
|
||||
|
||||
/**
|
||||
* Phase that migrates Angular host binding references to
|
||||
* unwrap signals.
|
||||
*/
|
||||
export function pass8__migrateHostBindings(
|
||||
result: MigrationResult,
|
||||
knownInputs: KnownInputs,
|
||||
export function pass8__migrateHostBindings<D extends ClassFieldDescriptor>(
|
||||
host: ReferenceMigrationHost<D>,
|
||||
references: Reference<D>[],
|
||||
info: ProgramInfo,
|
||||
) {
|
||||
const seenReferences = new WeakMap<ts.Node, Set<number>>();
|
||||
|
||||
for (const reference of result.references) {
|
||||
for (const reference of references) {
|
||||
// This pass only deals with host binding references.
|
||||
if (!isHostBindingReference(reference)) {
|
||||
continue;
|
||||
}
|
||||
// Skip references to incompatible inputs.
|
||||
if (knownInputs.get(reference.target)!.isIncompatible()) {
|
||||
if (!host.shouldMigrateReferencesToField(reference.target)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ export function pass8__migrateHostBindings(
|
|||
? `: ${reference.from.read.name}()`
|
||||
: `()`;
|
||||
|
||||
result.replacements.push(
|
||||
host.replacements.push(
|
||||
new Replacement(
|
||||
projectFile(bindingField.getSourceFile(), info),
|
||||
new TextUpdate({position: readEndPos, end: readEndPos, toInsert: appendText}),
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import ts from 'typescript';
|
||||
import {KnownInputs} from '../input_detection/known_inputs';
|
||||
import {MigrationResult} from '../result';
|
||||
import {ProgramInfo, projectFile, Replacement, TextUpdate} from '../../../../utils/tsurge';
|
||||
import assert from 'assert';
|
||||
import {ImportManager} from '@angular/compiler-cli/src/ngtsc/translator';
|
||||
import {isTsClassTypeReference} from './reference_resolution/reference_kinds';
|
||||
import assert from 'assert';
|
||||
import ts from 'typescript';
|
||||
import {ReferenceMigrationHost} from './reference_migration/reference_migration_host';
|
||||
import {ClassFieldDescriptor} from './reference_resolution/known_fields';
|
||||
import {ProgramInfo, projectFile, Replacement, TextUpdate} from '../../../../utils/tsurge';
|
||||
import {isTsClassTypeReference, Reference} from './reference_resolution/reference_kinds';
|
||||
|
||||
/**
|
||||
* Migrates TypeScript "ts.Type" references. E.g.
|
||||
|
|
@ -20,21 +20,21 @@ import {isTsClassTypeReference} from './reference_resolution/reference_kinds';
|
|||
* - `Partial<MyComp>` will be converted to `UnwrapSignalInputs<Partial<MyComp>>`.
|
||||
in Catalyst test files.
|
||||
*/
|
||||
export function pass9__migrateTypeScriptTypeReferences(
|
||||
result: MigrationResult,
|
||||
knownInputs: KnownInputs,
|
||||
export function pass9__migrateTypeScriptTypeReferences<D extends ClassFieldDescriptor>(
|
||||
host: ReferenceMigrationHost<D>,
|
||||
references: Reference<D>[],
|
||||
importManager: ImportManager,
|
||||
info: ProgramInfo,
|
||||
) {
|
||||
const seenTypeNodes = new WeakSet<ts.TypeReferenceNode>();
|
||||
|
||||
for (const reference of result.references) {
|
||||
for (const reference of references) {
|
||||
// This pass only deals with TS input class type references.
|
||||
if (!isTsClassTypeReference(reference)) {
|
||||
continue;
|
||||
}
|
||||
// Skip references to classes that are not fully migrated.
|
||||
if (knownInputs.getDirectiveInfoForClass(reference.target)?.hasIncompatibleMembers()) {
|
||||
if (!host.shouldMigrateReferencesToClass(reference.target)) {
|
||||
continue;
|
||||
}
|
||||
// Skip duplicate references. E.g. in batching.
|
||||
|
|
@ -62,17 +62,17 @@ export function pass9__migrateTypeScriptTypeReferences(
|
|||
requestedFile: sf,
|
||||
});
|
||||
|
||||
result.replacements.push(
|
||||
host.replacements.push(
|
||||
new Replacement(
|
||||
projectFile(sf, info),
|
||||
new TextUpdate({
|
||||
position: firstArg.getStart(),
|
||||
end: firstArg.getStart(),
|
||||
toInsert: `${result.printer.printNode(ts.EmitHint.Unspecified, unwrapImportExpr, sf)}<`,
|
||||
toInsert: `${host.printer.printNode(ts.EmitHint.Unspecified, unwrapImportExpr, sf)}<`,
|
||||
}),
|
||||
),
|
||||
);
|
||||
result.replacements.push(
|
||||
host.replacements.push(
|
||||
new Replacement(
|
||||
projectFile(sf, info),
|
||||
new TextUpdate({position: firstArg.getEnd(), end: firstArg.getEnd(), toInsert: '>'}),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import ts from 'typescript';
|
||||
import {ProjectFile, Replacement, TextUpdate} from '../../../../../utils/tsurge';
|
||||
import {ProjectFile, Replacement, TextUpdate} from '../../../../../../utils/tsurge';
|
||||
|
||||
/**
|
||||
* Creates replacements to insert the given statement as
|
||||
|
|
@ -13,11 +13,10 @@ import {
|
|||
ProgramInfo,
|
||||
projectFile,
|
||||
ProjectFile,
|
||||
} from '../../../../../utils/tsurge';
|
||||
import {getBindingElementDeclaration} from '../../utils/binding_elements';
|
||||
import {UniqueNamesGenerator} from '../../utils/unique_names';
|
||||
} from '../../../../../../utils/tsurge';
|
||||
import {getBindingElementDeclaration} from '../../../utils/binding_elements';
|
||||
import {UniqueNamesGenerator} from '../../../utils/unique_names';
|
||||
import assert from 'assert';
|
||||
import {MigrationResult} from '../../result';
|
||||
import {createNewBlockToInsertVariable} from './create_block_arrow_function';
|
||||
|
||||
/** An identifier part of a binding element. */
|
||||
|
|
@ -44,7 +43,8 @@ export interface IdentifierOfBindingElement extends ts.Identifier {
|
|||
export function migrateBindingElementInputReference(
|
||||
tsReferencesInBindingElements: Set<IdentifierOfBindingElement>,
|
||||
info: ProgramInfo,
|
||||
result: MigrationResult,
|
||||
replacements: Replacement[],
|
||||
printer: ts.Printer,
|
||||
) {
|
||||
const nameGenerator = new UniqueNamesGenerator(['Input', 'Signal', 'Ref']);
|
||||
|
||||
|
|
@ -90,13 +90,13 @@ export function migrateBindingElementInputReference(
|
|||
continue;
|
||||
}
|
||||
|
||||
result.replacements.push(
|
||||
replacements.push(
|
||||
new Replacement(
|
||||
file,
|
||||
new TextUpdate({
|
||||
position: bindingElement.getStart(),
|
||||
end: bindingElement.getEnd(),
|
||||
toInsert: result.printer.printNode(
|
||||
toInsert: printer.printNode(
|
||||
ts.EmitHint.Unspecified,
|
||||
newBindingToAccessInputField,
|
||||
sourceFile,
|
||||
|
|
@ -7,12 +7,11 @@
|
|||
*/
|
||||
|
||||
import ts from 'typescript';
|
||||
import {analyzeControlFlow} from '../../flow_analysis';
|
||||
import {MigrationResult} from '../../result';
|
||||
import {ProgramInfo, projectFile, Replacement, TextUpdate} from '../../../../../utils/tsurge';
|
||||
import {traverseAccess} from '../../utils/traverse_access';
|
||||
import {UniqueNamesGenerator} from '../../utils/unique_names';
|
||||
import {createNewBlockToInsertVariable} from './create_block_arrow_function';
|
||||
import {analyzeControlFlow} from '../../../flow_analysis';
|
||||
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';
|
||||
|
||||
export interface NarrowableTsReferences {
|
||||
accesses: ts.Identifier[];
|
||||
|
|
@ -21,8 +20,8 @@ export interface NarrowableTsReferences {
|
|||
export function migrateStandardTsReference(
|
||||
tsReferencesWithNarrowing: Map<unknown, NarrowableTsReferences>,
|
||||
checker: ts.TypeChecker,
|
||||
result: MigrationResult,
|
||||
info: ProgramInfo,
|
||||
replacements: Replacement[],
|
||||
) {
|
||||
const nameGenerator = new UniqueNamesGenerator(['Value', 'Val', 'Input']);
|
||||
|
||||
|
|
@ -38,7 +37,7 @@ export function migrateStandardTsReference(
|
|||
// Unwrap the signal directly.
|
||||
if (recommendedNode === 'preserve') {
|
||||
// Append `()` to unwrap the signal.
|
||||
result.replacements.push(
|
||||
replacements.push(
|
||||
new Replacement(
|
||||
projectFile(sf, info),
|
||||
new TextUpdate({
|
||||
|
|
@ -55,7 +54,7 @@ export function migrateStandardTsReference(
|
|||
// with the temporary variable.
|
||||
if (typeof recommendedNode === 'number') {
|
||||
const replaceNode = traverseAccess(originalNode);
|
||||
result.replacements.push(
|
||||
replacements.push(
|
||||
new Replacement(
|
||||
projectFile(sf, info),
|
||||
new TextUpdate({
|
||||
|
|
@ -94,13 +93,13 @@ export function migrateStandardTsReference(
|
|||
// without a block, convert the arrow function to a block and insert the temporary
|
||||
// variable at the beginning.
|
||||
if (ts.isArrowFunction(parent) && !ts.isBlock(parent.body)) {
|
||||
result.replacements.push(
|
||||
replacements.push(
|
||||
...createNewBlockToInsertVariable(parent, filePath, temporaryVariableStr),
|
||||
);
|
||||
} else {
|
||||
const leadingSpace = ts.getLineAndCharacterOfPosition(sf, referenceNodeInBlock.getStart());
|
||||
|
||||
result.replacements.push(
|
||||
replacements.push(
|
||||
new Replacement(
|
||||
filePath,
|
||||
new TextUpdate({
|
||||
|
|
@ -112,7 +111,7 @@ export function migrateStandardTsReference(
|
|||
);
|
||||
}
|
||||
|
||||
result.replacements.push(
|
||||
replacements.push(
|
||||
new Replacement(
|
||||
projectFile(sf, info),
|
||||
new TextUpdate({
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import ts from 'typescript';
|
||||
import {ProgramInfo, projectFile, Replacement, TextUpdate} from '../../../../../utils/tsurge';
|
||||
import {ClassFieldDescriptor} from '../reference_resolution/known_fields';
|
||||
import {isHostBindingReference, Reference} from '../reference_resolution/reference_kinds';
|
||||
import {ReferenceMigrationHost} from './reference_migration_host';
|
||||
|
||||
/**
|
||||
* Phase that migrates Angular host binding references to
|
||||
* unwrap signals.
|
||||
*/
|
||||
export function migrateHostBindings<D extends ClassFieldDescriptor>(
|
||||
host: ReferenceMigrationHost<D>,
|
||||
references: Reference<D>[],
|
||||
info: ProgramInfo,
|
||||
) {
|
||||
const seenReferences = new WeakMap<ts.Node, Set<number>>();
|
||||
|
||||
for (const reference of references) {
|
||||
// This pass only deals with host binding references.
|
||||
if (!isHostBindingReference(reference)) {
|
||||
continue;
|
||||
}
|
||||
// Skip references to incompatible inputs.
|
||||
if (!host.shouldMigrateReferencesToField(reference.target)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bindingField = reference.from.hostPropertyNode;
|
||||
const expressionOffset = bindingField.getStart() + 1; // account for quotes.
|
||||
const readEndPos = expressionOffset + reference.from.read.sourceSpan.end;
|
||||
|
||||
// Skip duplicate references. Can happen if the host object is shared.
|
||||
if (seenReferences.get(bindingField)?.has(readEndPos)) {
|
||||
continue;
|
||||
}
|
||||
if (seenReferences.has(bindingField)) {
|
||||
seenReferences.get(bindingField)!.add(readEndPos);
|
||||
} else {
|
||||
seenReferences.set(bindingField, new Set([readEndPos]));
|
||||
}
|
||||
|
||||
// Expand shorthands like `{bla}` to `{bla: bla()}`.
|
||||
const appendText = reference.from.isObjectShorthandExpression
|
||||
? `: ${reference.from.read.name}()`
|
||||
: `()`;
|
||||
|
||||
host.replacements.push(
|
||||
new Replacement(
|
||||
projectFile(bindingField.getSourceFile(), info),
|
||||
new TextUpdate({position: readEndPos, end: readEndPos, toInsert: appendText}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Replacement, TextUpdate} from '../../../../../utils/tsurge';
|
||||
import {ClassFieldDescriptor} from '../reference_resolution/known_fields';
|
||||
import {isTemplateReference, Reference} from '../reference_resolution/reference_kinds';
|
||||
import {ReferenceMigrationHost} from './reference_migration_host';
|
||||
|
||||
/**
|
||||
* Phase that migrates Angular template references to
|
||||
* unwrap signals.
|
||||
*/
|
||||
export function migrateTemplateReferences<D extends ClassFieldDescriptor>(
|
||||
host: ReferenceMigrationHost<D>,
|
||||
references: Reference<D>[],
|
||||
) {
|
||||
const seenFileReferences = new Set<string>();
|
||||
|
||||
for (const reference of references) {
|
||||
// This pass only deals with HTML template references.
|
||||
if (!isTemplateReference(reference)) {
|
||||
continue;
|
||||
}
|
||||
// Skip references to incompatible inputs.
|
||||
if (!host.shouldMigrateReferencesToField(reference.target)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip duplicate references. E.g. if a template is shared.
|
||||
const fileReferenceId = `${reference.from.templateFile.id}:${reference.from.read.sourceSpan.end}`;
|
||||
if (seenFileReferences.has(fileReferenceId)) {
|
||||
continue;
|
||||
}
|
||||
seenFileReferences.add(fileReferenceId);
|
||||
|
||||
// Expand shorthands like `{bla}` to `{bla: bla()}`.
|
||||
const appendText = reference.from.isObjectShorthandExpression
|
||||
? `: ${reference.from.read.name}()`
|
||||
: `()`;
|
||||
|
||||
host.replacements.push(
|
||||
new Replacement(
|
||||
reference.from.templateFile,
|
||||
new TextUpdate({
|
||||
position: reference.from.read.sourceSpan.end,
|
||||
end: reference.from.read.sourceSpan.end,
|
||||
toInsert: appendText,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import ts from 'typescript';
|
||||
import {
|
||||
migrateBindingElementInputReference,
|
||||
IdentifierOfBindingElement,
|
||||
} from './helpers/object_expansion_refs';
|
||||
import {migrateStandardTsReference, NarrowableTsReferences} from './helpers/standard_reference';
|
||||
import {ProgramInfo} from '../../../../../utils/tsurge';
|
||||
import {ClassFieldDescriptor, ClassFieldUniqueKey} from '../reference_resolution/known_fields';
|
||||
import {isTsReference, Reference} from '../reference_resolution/reference_kinds';
|
||||
import {ReferenceMigrationHost} from './reference_migration_host';
|
||||
|
||||
/**
|
||||
* Migrates TypeScript input references to be signal compatible.
|
||||
*
|
||||
* The phase takes care of control flow analysis and generates temporary variables
|
||||
* where needed to ensure narrowing continues to work. E.g.
|
||||
*
|
||||
* ```
|
||||
* someMethod() {
|
||||
* if (this.input) {
|
||||
* this.input.charAt(0);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* will be transformed into:
|
||||
*
|
||||
* ```
|
||||
* someMethod() {
|
||||
* const input_1 = this.input();
|
||||
* if (input_1) {
|
||||
* input_1.charAt(0);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function migrateTypeScriptReferences<D extends ClassFieldDescriptor>(
|
||||
host: ReferenceMigrationHost<D>,
|
||||
references: Reference<D>[],
|
||||
checker: ts.TypeChecker,
|
||||
info: ProgramInfo,
|
||||
) {
|
||||
const tsReferencesWithNarrowing = new Map<ClassFieldUniqueKey, NarrowableTsReferences>();
|
||||
const tsReferencesInBindingElements = new Set<IdentifierOfBindingElement>();
|
||||
|
||||
const seenIdentifiers = new WeakSet<ts.Identifier>();
|
||||
|
||||
for (const reference of references) {
|
||||
// This pass only deals with TS references.
|
||||
if (!isTsReference(reference)) {
|
||||
continue;
|
||||
}
|
||||
// Skip references to incompatible inputs.
|
||||
if (!host.shouldMigrateReferencesToField(reference.target)) {
|
||||
continue;
|
||||
}
|
||||
// Never attempt to migrate write references.
|
||||
// Those usually invalidate the target input most of the time, but in
|
||||
// best-effort mode they are not.
|
||||
if (reference.from.isWrite) {
|
||||
continue;
|
||||
}
|
||||
// Skip duplicate references. E.g. in batching.
|
||||
if (seenIdentifiers.has(reference.from.node)) {
|
||||
continue;
|
||||
}
|
||||
seenIdentifiers.add(reference.from.node);
|
||||
|
||||
const targetKey = reference.target.key;
|
||||
|
||||
if (reference.from.isPartOfElementBinding) {
|
||||
tsReferencesInBindingElements.add(reference.from.node as IdentifierOfBindingElement);
|
||||
} else {
|
||||
if (!tsReferencesWithNarrowing.has(targetKey)) {
|
||||
tsReferencesWithNarrowing.set(targetKey, {accesses: []});
|
||||
}
|
||||
tsReferencesWithNarrowing.get(targetKey)!.accesses.push(reference.from.node);
|
||||
}
|
||||
}
|
||||
|
||||
migrateBindingElementInputReference(
|
||||
tsReferencesInBindingElements,
|
||||
info,
|
||||
host.replacements,
|
||||
host.printer,
|
||||
);
|
||||
migrateStandardTsReference(tsReferencesWithNarrowing, checker, info, host.replacements);
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import ts from 'typescript';
|
||||
import {KnownInputs} from '../../input_detection/known_inputs';
|
||||
import {MigrationResult} from '../../result';
|
||||
import {ProgramInfo, projectFile, Replacement, TextUpdate} from '../../../../../utils/tsurge';
|
||||
import assert from 'assert';
|
||||
import {ImportManager} from '@angular/compiler-cli/src/ngtsc/translator';
|
||||
import {isTsClassTypeReference} from '../reference_resolution/reference_kinds';
|
||||
|
||||
/**
|
||||
* Migrates TypeScript "ts.Type" references. E.g.
|
||||
|
||||
* - `Partial<MyComp>` will be converted to `UnwrapSignalInputs<Partial<MyComp>>`.
|
||||
in Catalyst test files.
|
||||
*/
|
||||
export function migrateTypeScriptTypeReferences(
|
||||
result: MigrationResult,
|
||||
knownInputs: KnownInputs,
|
||||
importManager: ImportManager,
|
||||
info: ProgramInfo,
|
||||
) {
|
||||
const seenTypeNodes = new WeakSet<ts.TypeReferenceNode>();
|
||||
|
||||
for (const reference of result.references) {
|
||||
// This pass only deals with TS input class type references.
|
||||
if (!isTsClassTypeReference(reference)) {
|
||||
continue;
|
||||
}
|
||||
// Skip references to classes that are not fully migrated.
|
||||
if (knownInputs.getDirectiveInfoForClass(reference.target)?.hasIncompatibleMembers()) {
|
||||
continue;
|
||||
}
|
||||
// Skip duplicate references. E.g. in batching.
|
||||
if (seenTypeNodes.has(reference.from.node)) {
|
||||
continue;
|
||||
}
|
||||
seenTypeNodes.add(reference.from.node);
|
||||
|
||||
if (reference.isPartialReference && reference.isPartOfCatalystFile) {
|
||||
assert(reference.from.node.typeArguments, 'Expected type arguments for partial reference.');
|
||||
assert(reference.from.node.typeArguments.length === 1, 'Expected an argument for reference.');
|
||||
|
||||
const firstArg = reference.from.node.typeArguments[0];
|
||||
const sf = firstArg.getSourceFile();
|
||||
|
||||
const unwrapImportExpr = importManager.addImport({
|
||||
exportModuleSpecifier: 'google3/javascript/angular2/testing/catalyst',
|
||||
exportSymbolName: 'UnwrapSignalInputs',
|
||||
requestedFile: sf,
|
||||
});
|
||||
|
||||
result.replacements.push(
|
||||
new Replacement(
|
||||
projectFile(sf, info),
|
||||
new TextUpdate({
|
||||
position: firstArg.getStart(),
|
||||
end: firstArg.getStart(),
|
||||
toInsert: `${result.printer.printNode(ts.EmitHint.Unspecified, unwrapImportExpr, sf)}<`,
|
||||
}),
|
||||
),
|
||||
);
|
||||
result.replacements.push(
|
||||
new Replacement(
|
||||
projectFile(sf, info),
|
||||
new TextUpdate({position: firstArg.getEnd(), end: firstArg.getEnd(), toInsert: '>'}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import ts from 'typescript';
|
||||
import {Replacement} from '../../../../../utils/tsurge';
|
||||
import {ClassFieldDescriptor} from '../reference_resolution/known_fields';
|
||||
|
||||
export interface ReferenceMigrationHost<D extends ClassFieldDescriptor> {
|
||||
shouldMigrateReferencesToField: (descriptor: D) => boolean;
|
||||
shouldMigrateReferencesToClass: (clazz: ts.ClassDeclaration) => boolean;
|
||||
replacements: Replacement[];
|
||||
printer: ts.Printer;
|
||||
}
|
||||
|
|
@ -117,10 +117,7 @@ export function executeAnalysisPhase(
|
|||
// TODO: Remove this when we support signal narrowing in templates.
|
||||
// https://github.com/angular/angular/pull/55456.
|
||||
if (isTemplateReference(reference)) {
|
||||
if (
|
||||
process.env['MIGRATE_NARROWED_NARROWED_IN_TEMPLATES'] !== '1' &&
|
||||
reference.from.isLikelyPartOfNarrowing
|
||||
) {
|
||||
if (reference.from.isLikelyPartOfNarrowing) {
|
||||
knownInputs.markInputAsIncompatible(reference.target, {
|
||||
reason: InputIncompatibilityReason.PotentiallyNarrowedInTemplateButNoSupportYet,
|
||||
context: null,
|
||||
|
|
|
|||
|
|
@ -9,14 +9,16 @@
|
|||
import {AnalysisProgramInfo} from './analysis_deps';
|
||||
import {KnownInputs} from './input_detection/known_inputs';
|
||||
import {MigrationHost} from './migration_host';
|
||||
import {pass5__migrateTypeScriptReferences} from './passes/5_migrate_ts_references';
|
||||
import {pass6__migrateInputDeclarations} from './passes/6_migrate_input_declarations';
|
||||
import {pass7__migrateTemplateReferences} from './passes/7_migrate_template_references';
|
||||
import {pass8__migrateHostBindings} from './passes/8_migrate_host_bindings';
|
||||
import {pass9__migrateTypeScriptTypeReferences} from './passes/9_migrate_ts_type_references';
|
||||
import {MigrationResult} from './result';
|
||||
import {pass10_applyImportManager} from './passes/10_apply_import_manager';
|
||||
import {ImportManager} from '@angular/compiler-cli/src/ngtsc/translator';
|
||||
import {InputDescriptor} from './utils/input_id';
|
||||
import {ReferenceMigrationHost} from './passes/reference_migration/reference_migration_host';
|
||||
import {pass5__migrateTypeScriptReferences} from './passes/5_migrate_ts_references';
|
||||
import {pass7__migrateTemplateReferences} from './passes/7_migrate_template_references';
|
||||
import {pass8__migrateHostBindings} from './passes/8_migrate_host_bindings';
|
||||
import {pass9__migrateTypeScriptTypeReferences} from './passes/9_migrate_ts_type_references';
|
||||
|
||||
/**
|
||||
* Executes the migration phase.
|
||||
|
|
@ -40,11 +42,26 @@ export function executeMigrationPhase(
|
|||
generateUniqueIdentifier: () => null,
|
||||
});
|
||||
|
||||
const referenceMigrationHost: ReferenceMigrationHost<InputDescriptor> = {
|
||||
printer: result.printer,
|
||||
replacements: result.replacements,
|
||||
shouldMigrateReferencesToField: (inputDescr) =>
|
||||
knownInputs.has(inputDescr) && knownInputs.get(inputDescr)!.isIncompatible() === false,
|
||||
shouldMigrateReferencesToClass: (clazz) =>
|
||||
knownInputs.getDirectiveInfoForClass(clazz) !== undefined &&
|
||||
knownInputs.getDirectiveInfoForClass(clazz)!.hasIncompatibleMembers() === false,
|
||||
};
|
||||
|
||||
// Migrate passes.
|
||||
pass5__migrateTypeScriptReferences(result, typeChecker, knownInputs, info);
|
||||
pass5__migrateTypeScriptReferences(referenceMigrationHost, result.references, typeChecker, info);
|
||||
pass6__migrateInputDeclarations(typeChecker, result, knownInputs, importManager, info);
|
||||
pass7__migrateTemplateReferences(result, knownInputs);
|
||||
pass8__migrateHostBindings(result, knownInputs, info);
|
||||
pass9__migrateTypeScriptTypeReferences(result, knownInputs, importManager, info);
|
||||
pass7__migrateTemplateReferences(referenceMigrationHost, result.references);
|
||||
pass8__migrateHostBindings(referenceMigrationHost, result.references, info);
|
||||
pass9__migrateTypeScriptTypeReferences(
|
||||
referenceMigrationHost,
|
||||
result.references,
|
||||
importManager,
|
||||
info,
|
||||
);
|
||||
pass10_applyImportManager(importManager, result, sourceFiles, info);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue