12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190 |
- 'use strict';
- /**
- * @license Angular v19.2.4
- * (c) 2010-2025 Google LLC. https://angular.io/
- * License: MIT
- */
- 'use strict';
- var schematics = require('@angular-devkit/schematics');
- var project_tsconfig_paths = require('./project_tsconfig_paths-CDVxT6Ov.js');
- var project_paths = require('./project_paths-CXXqWSoY.js');
- require('os');
- var ts = require('typescript');
- var checker = require('./checker-DP-zos5Q.js');
- var program = require('./program-BmLi-Vxz.js');
- require('path');
- var apply_import_manager = require('./apply_import_manager-BynuozbO.js');
- var migrate_ts_type_references = require('./migrate_ts_type_references-Ri-K4P_1.js');
- var assert = require('assert');
- var index = require('./index-CPpyW--c.js');
- require('@angular-devkit/core');
- require('node:path/posix');
- require('fs');
- require('module');
- require('url');
- require('./leading_space-D9nQ8UQC.js');
- /**
- * Phase that migrates Angular host binding references to
- * unwrap signals.
- */
- function migrateHostBindings(host, references, info) {
- const seenReferences = new WeakMap();
- for (const reference of references) {
- // This pass only deals with host binding references.
- if (!index.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 project_paths.Replacement(project_paths.projectFile(bindingField.getSourceFile(), info), new project_paths.TextUpdate({ position: readEndPos, end: readEndPos, toInsert: appendText })));
- }
- }
- /**
- * Phase that migrates Angular template references to
- * unwrap signals.
- */
- function migrateTemplateReferences(host, references) {
- const seenFileReferences = new Set();
- for (const reference of references) {
- // This pass only deals with HTML template references.
- if (!index.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 project_paths.Replacement(reference.from.templateFile, new project_paths.TextUpdate({
- position: reference.from.read.sourceSpan.end,
- end: reference.from.read.sourceSpan.end,
- toInsert: appendText,
- })));
- }
- }
- /**
- * Extracts the type `T` of expressions referencing `QueryList<T>`.
- */
- function extractQueryListType(node) {
- // Initializer variant of `new QueryList<T>()`.
- if (ts.isNewExpression(node) &&
- ts.isIdentifier(node.expression) &&
- node.expression.text === 'QueryList') {
- return node.typeArguments?.[0];
- }
- // Type variant of `: QueryList<T>`.
- if (ts.isTypeReferenceNode(node) &&
- ts.isIdentifier(node.typeName) &&
- node.typeName.text === 'QueryList') {
- return node.typeArguments?.[0];
- }
- return undefined;
- }
- /**
- * A few notes on changes:
- *
- * @ViewChild()
- * --> static is gone!
- * --> read stays
- *
- * @ViewChildren()
- * --> emitDistinctChangesOnly is gone!
- * --> read stays
- *
- * @ContentChild()
- * --> descendants stays
- * --> read stays
- * --> static is gone!
- *
- * @ContentChildren()
- * --> descendants stays
- * --> read stays
- * --> emitDistinctChangesOnly is gone!
- */
- function computeReplacementsToMigrateQuery(node, metadata, importManager, info, printer, options, checker$1) {
- const sf = node.getSourceFile();
- let newQueryFn = importManager.addImport({
- requestedFile: sf,
- exportModuleSpecifier: '@angular/core',
- exportSymbolName: metadata.kind,
- });
- // The default value for descendants is `true`, except for `ContentChildren`.
- const defaultDescendants = metadata.kind !== 'contentChildren';
- const optionProperties = [];
- const args = [
- metadata.args[0], // Locator.
- ];
- let type = node.type;
- // For multi queries, attempt to unwrap `QueryList` types, or infer the
- // type from the initializer, if possible.
- if (!metadata.queryInfo.first) {
- if (type === undefined && node.initializer !== undefined) {
- type = extractQueryListType(node.initializer);
- }
- else if (type !== undefined) {
- type = extractQueryListType(type);
- }
- }
- if (metadata.queryInfo.read !== null) {
- assert(metadata.queryInfo.read instanceof checker.WrappedNodeExpr);
- optionProperties.push(ts.factory.createPropertyAssignment('read', metadata.queryInfo.read.node));
- }
- if (metadata.queryInfo.descendants !== defaultDescendants) {
- optionProperties.push(ts.factory.createPropertyAssignment('descendants', metadata.queryInfo.descendants ? ts.factory.createTrue() : ts.factory.createFalse()));
- }
- if (optionProperties.length > 0) {
- args.push(ts.factory.createObjectLiteralExpression(optionProperties));
- }
- const strictNullChecksEnabled = options.strict === true || options.strictNullChecks === true;
- const strictPropertyInitialization = options.strict === true || options.strictPropertyInitialization === true;
- let isRequired = node.exclamationToken !== undefined;
- // If we come across an application with strict null checks enabled, but strict
- // property initialization is disabled, there are two options:
- // - Either the query is already typed to include `undefined` explicitly,
- // in which case an option query makes sense.
- // - OR, the query is not typed to include `undefined`. In which case, the query
- // should be marked as required to not break the app. The user-code throughout
- // the application (given strict null checks) already assumes non-nullable!
- if (strictNullChecksEnabled &&
- !strictPropertyInitialization &&
- node.initializer === undefined &&
- node.questionToken === undefined &&
- type !== undefined &&
- !checker$1.isTypeAssignableTo(checker$1.getUndefinedType(), checker$1.getTypeFromTypeNode(type))) {
- isRequired = true;
- }
- if (isRequired && metadata.queryInfo.first) {
- // If the query is required already via some indicators, and this is a "single"
- // query, use the available `.required` method.
- newQueryFn = ts.factory.createPropertyAccessExpression(newQueryFn, 'required');
- }
- // If this query is still nullable (i.e. not required), attempt to remove
- // explicit `undefined` types if possible.
- if (!isRequired && type !== undefined && ts.isUnionTypeNode(type)) {
- type = migrate_ts_type_references.removeFromUnionIfPossible(type, (v) => v.kind !== ts.SyntaxKind.UndefinedKeyword);
- }
- let locatorType = Array.isArray(metadata.queryInfo.predicate)
- ? null
- : metadata.queryInfo.predicate.expression;
- let resolvedReadType = metadata.queryInfo.read ?? locatorType;
- // If the original property type and the read type are matching, we can rely
- // on the TS inference, instead of repeating types, like in `viewChild<Button>(Button)`.
- if (type !== undefined &&
- resolvedReadType instanceof checker.WrappedNodeExpr &&
- ts.isIdentifier(resolvedReadType.node) &&
- ts.isTypeReferenceNode(type) &&
- ts.isIdentifier(type.typeName) &&
- type.typeName.text === resolvedReadType.node.text) {
- locatorType = null;
- }
- const call = ts.factory.createCallExpression(newQueryFn,
- // If there is no resolved `ReadT` (e.g. string predicate), we use the
- // original type explicitly as generic. Otherwise, query API is smart
- // enough to always infer.
- resolvedReadType === null && type !== undefined ? [type] : undefined, args);
- const updated = ts.factory.createPropertyDeclaration([ts.factory.createModifier(ts.SyntaxKind.ReadonlyKeyword)], node.name, undefined, undefined, call);
- return [
- new project_paths.Replacement(project_paths.projectFile(node.getSourceFile(), info), new project_paths.TextUpdate({
- position: node.getStart(),
- end: node.getEnd(),
- toInsert: printer.printNode(ts.EmitHint.Unspecified, updated, sf),
- })),
- ];
- }
- /**
- * Attempts to get a class field descriptor if the given symbol
- * points to a class field.
- */
- function getClassFieldDescriptorForSymbol(symbol, info) {
- if (symbol?.valueDeclaration === undefined ||
- !ts.isPropertyDeclaration(symbol.valueDeclaration)) {
- return null;
- }
- const key = getUniqueIDForClassProperty(symbol.valueDeclaration, info);
- if (key === null) {
- return null;
- }
- return {
- key,
- node: symbol.valueDeclaration,
- };
- }
- /**
- * Gets a unique ID for the given class property.
- *
- * This is useful for matching class fields across compilation units.
- * E.g. a reference may point to the field via `.d.ts`, while the other
- * may reference it via actual `.ts` sources. IDs for the same fields
- * would then match identity.
- */
- function getUniqueIDForClassProperty(property, info) {
- if (!ts.isClassDeclaration(property.parent) || property.parent.name === undefined) {
- return null;
- }
- if (property.name === undefined) {
- return null;
- }
- const id = project_paths.projectFile(property.getSourceFile(), info).id.replace(/\.d\.ts$/, '.ts');
- // Note: If a class is nested, there could be an ID clash.
- // This is highly unlikely though, and this is not a problem because
- // in such cases, there is even less chance there are any references to
- // a non-exported classes; in which case, cross-compilation unit references
- // likely can't exist anyway.
- return `${id}-${property.parent.name.text}-${property.name.getText()}`;
- }
- /**
- * Determines if the given node refers to a decorator-based query, and
- * returns its resolved metadata if possible.
- */
- function extractSourceQueryDefinition(node, reflector, evaluator, info) {
- if ((!ts.isPropertyDeclaration(node) && !ts.isAccessor(node)) ||
- !ts.isClassDeclaration(node.parent) ||
- node.parent.name === undefined ||
- !ts.isIdentifier(node.name)) {
- return null;
- }
- const decorators = reflector.getDecoratorsOfDeclaration(node) ?? [];
- const ngDecorators = checker.getAngularDecorators(decorators, checker.queryDecoratorNames, /* isCore */ false);
- if (ngDecorators.length === 0) {
- return null;
- }
- const decorator = ngDecorators[0];
- const id = getUniqueIDForClassProperty(node, info);
- if (id === null) {
- return null;
- }
- let kind;
- if (decorator.name === 'ViewChild') {
- kind = 'viewChild';
- }
- else if (decorator.name === 'ViewChildren') {
- kind = 'viewChildren';
- }
- else if (decorator.name === 'ContentChild') {
- kind = 'contentChild';
- }
- else if (decorator.name === 'ContentChildren') {
- kind = 'contentChildren';
- }
- else {
- throw new Error('Unexpected query decorator detected.');
- }
- let queryInfo = null;
- try {
- queryInfo = checker.extractDecoratorQueryMetadata(node, decorator.name, decorator.args ?? [], node.name.text, reflector, evaluator);
- }
- catch (e) {
- if (!(e instanceof checker.FatalDiagnosticError)) {
- throw e;
- }
- console.error(`Skipping query: ${e.node.getSourceFile().fileName}: ${e.toString()}`);
- return null;
- }
- return {
- id,
- kind,
- args: decorator.args ?? [],
- queryInfo,
- node: node,
- fieldDecorators: decorators,
- };
- }
- function markFieldIncompatibleInMetadata(data, id, reason) {
- const existing = data[id];
- if (existing === undefined) {
- data[id] = {
- fieldReason: reason,
- classReason: null,
- };
- }
- else if (existing.fieldReason === null) {
- existing.fieldReason = reason;
- }
- else {
- existing.fieldReason = migrate_ts_type_references.pickFieldIncompatibility({ reason, context: null }, { reason: existing.fieldReason, context: null }).reason;
- }
- }
- function filterBestEffortIncompatibilities(knownQueries) {
- for (const query of Object.values(knownQueries.globalMetadata.problematicQueries)) {
- if (query.fieldReason !== null &&
- !migrate_ts_type_references.nonIgnorableFieldIncompatibilities.includes(query.fieldReason)) {
- query.fieldReason = null;
- }
- }
- }
- class KnownQueries {
- info;
- config;
- globalMetadata;
- classToQueryFields = new Map();
- knownQueryIDs = new Map();
- constructor(info, config, globalMetadata) {
- this.info = info;
- this.config = config;
- this.globalMetadata = globalMetadata;
- }
- isFieldIncompatible(descriptor) {
- return this.getIncompatibilityForField(descriptor) !== null;
- }
- markFieldIncompatible(field, incompatibility) {
- markFieldIncompatibleInMetadata(this.globalMetadata.problematicQueries, field.key, incompatibility.reason);
- }
- markClassIncompatible(node, reason) {
- this.classToQueryFields.get(node)?.forEach((f) => {
- this.globalMetadata.problematicQueries[f.key] ??= { classReason: null, fieldReason: null };
- this.globalMetadata.problematicQueries[f.key].classReason = reason;
- });
- }
- registerQueryField(queryField, id) {
- if (!this.classToQueryFields.has(queryField.parent)) {
- this.classToQueryFields.set(queryField.parent, []);
- }
- this.classToQueryFields.get(queryField.parent).push({
- key: id,
- node: queryField,
- });
- this.knownQueryIDs.set(id, { key: id, node: queryField });
- const descriptor = { key: id, node: queryField };
- const file = project_paths.projectFile(queryField.getSourceFile(), this.info);
- if (this.config.shouldMigrateQuery !== undefined &&
- !this.config.shouldMigrateQuery(descriptor, file)) {
- this.markFieldIncompatible(descriptor, {
- context: null,
- reason: migrate_ts_type_references.FieldIncompatibilityReason.SkippedViaConfigFilter,
- });
- }
- }
- attemptRetrieveDescriptorFromSymbol(symbol) {
- const descriptor = getClassFieldDescriptorForSymbol(symbol, this.info);
- if (descriptor !== null && this.knownQueryIDs.has(descriptor.key)) {
- return descriptor;
- }
- return null;
- }
- shouldTrackClassReference(clazz) {
- return this.classToQueryFields.has(clazz);
- }
- getQueryFieldsOfClass(clazz) {
- return this.classToQueryFields.get(clazz);
- }
- getAllClassesWithQueries() {
- return Array.from(this.classToQueryFields.keys()).filter((c) => ts.isClassDeclaration(c));
- }
- captureKnownFieldInheritanceRelationship(derived, parent) {
- // Note: The edge problematic pattern recognition is not as good as the one
- // we have in the signal input migration. That is because we couldn't trivially
- // build up an inheritance graph during analyze phase where we DON'T know what
- // fields refer to queries. Usually we'd use the graph to smartly propagate
- // incompatibilities using topological sort. This doesn't work here and is
- // unnecessarily complex, so we try our best at detecting direct edge
- // incompatibilities (which are quite order dependent).
- if (this.isFieldIncompatible(parent) && !this.isFieldIncompatible(derived)) {
- this.markFieldIncompatible(derived, {
- context: null,
- reason: migrate_ts_type_references.FieldIncompatibilityReason.ParentIsIncompatible,
- });
- return;
- }
- if (this.isFieldIncompatible(derived) && !this.isFieldIncompatible(parent)) {
- this.markFieldIncompatible(parent, {
- context: null,
- reason: migrate_ts_type_references.FieldIncompatibilityReason.DerivedIsIncompatible,
- });
- }
- }
- captureUnknownDerivedField(field) {
- this.markFieldIncompatible(field, {
- context: null,
- reason: migrate_ts_type_references.FieldIncompatibilityReason.OverriddenByDerivedClass,
- });
- }
- captureUnknownParentField(field) {
- this.markFieldIncompatible(field, {
- context: null,
- reason: migrate_ts_type_references.FieldIncompatibilityReason.TypeConflictWithBaseClass,
- });
- }
- getIncompatibilityForField(descriptor) {
- const problematicInfo = this.globalMetadata.problematicQueries[descriptor.key];
- if (problematicInfo === undefined) {
- return null;
- }
- if (problematicInfo.fieldReason !== null) {
- return { context: null, reason: problematicInfo.fieldReason };
- }
- if (problematicInfo.classReason !== null) {
- return problematicInfo.classReason;
- }
- return null;
- }
- getIncompatibilityTextForField(field) {
- const incompatibilityInfo = this.globalMetadata.problematicQueries[field.key];
- if (incompatibilityInfo.fieldReason !== null) {
- return migrate_ts_type_references.getMessageForFieldIncompatibility(incompatibilityInfo.fieldReason, {
- single: 'query',
- plural: 'queries',
- });
- }
- if (incompatibilityInfo.classReason !== null) {
- return migrate_ts_type_references.getMessageForClassIncompatibility(incompatibilityInfo.classReason, {
- single: 'query',
- plural: 'queries',
- });
- }
- return null;
- }
- }
- /** Converts an initializer query API name to its decorator-equivalent. */
- function queryFunctionNameToDecorator(name) {
- if (name === 'viewChild') {
- return 'ViewChild';
- }
- else if (name === 'viewChildren') {
- return 'ViewChildren';
- }
- else if (name === 'contentChild') {
- return 'ContentChild';
- }
- else if (name === 'contentChildren') {
- return 'ContentChildren';
- }
- throw new Error(`Unexpected query function name: ${name}`);
- }
- /**
- * Gets whether the given field is accessed via the
- * given reference.
- *
- * E.g. whether `<my-read>.toArray` is detected.
- */
- function checkTsReferenceAccessesField(ref, fieldName) {
- const accessNode = index.traverseAccess(ref.from.node);
- // Check if the reference is part of a property access.
- if (!ts.isPropertyAccessExpression(accessNode.parent) ||
- !ts.isIdentifier(accessNode.parent.name)) {
- return null;
- }
- // Check if the reference is refers to the given field name.
- if (accessNode.parent.name.text !== fieldName) {
- return null;
- }
- return accessNode.parent;
- }
- /**
- * Gets whether the given read is used to access
- * the specified field.
- *
- * E.g. whether `<my-read>.toArray` is detected.
- */
- function checkNonTsReferenceAccessesField(ref, fieldName) {
- const readFromPath = ref.from.readAstPath.at(-1);
- const parentRead = ref.from.readAstPath.at(-2);
- if (ref.from.read !== readFromPath) {
- return null;
- }
- if (!(parentRead instanceof checker.PropertyRead) || parentRead.name !== fieldName) {
- return null;
- }
- return parentRead;
- }
- /**
- * Gets whether the given reference is accessed to call the
- * specified function on it.
- *
- * E.g. whether `<my-read>.toArray()` is detected.
- */
- function checkTsReferenceCallsField(ref, fieldName) {
- const propertyAccess = checkTsReferenceAccessesField(ref, fieldName);
- if (propertyAccess === null) {
- return null;
- }
- if (ts.isCallExpression(propertyAccess.parent) &&
- propertyAccess.parent.expression === propertyAccess) {
- return propertyAccess.parent;
- }
- return null;
- }
- /**
- * Gets whether the given reference is accessed to call the
- * specified function on it.
- *
- * E.g. whether `<my-read>.toArray()` is detected.
- */
- function checkNonTsReferenceCallsField(ref, fieldName) {
- const propertyAccess = checkNonTsReferenceAccessesField(ref, fieldName);
- if (propertyAccess === null) {
- return null;
- }
- const accessIdx = ref.from.readAstPath.indexOf(propertyAccess);
- if (accessIdx === -1) {
- return null;
- }
- const potentialCall = ref.from.readAstPath[accessIdx - 1];
- if (potentialCall === undefined || !(potentialCall instanceof checker.Call)) {
- return null;
- }
- return potentialCall;
- }
- function removeQueryListToArrayCall(ref, info, globalMetadata, knownQueries, replacements) {
- if (!index.isHostBindingReference(ref) && !index.isTemplateReference(ref) && !index.isTsReference(ref)) {
- return;
- }
- if (knownQueries.isFieldIncompatible(ref.target)) {
- return;
- }
- if (!globalMetadata.knownQueryFields[ref.target.key]?.isMulti) {
- return;
- }
- // TS references.
- if (index.isTsReference(ref)) {
- const toArrayCallExpr = checkTsReferenceCallsField(ref, 'toArray');
- if (toArrayCallExpr === null) {
- return;
- }
- const toArrayExpr = toArrayCallExpr.expression;
- replacements.push(new project_paths.Replacement(project_paths.projectFile(toArrayExpr.getSourceFile(), info), new project_paths.TextUpdate({
- // Delete from expression end to call end. E.g. `.toArray(<..>)`.
- position: toArrayExpr.expression.getEnd(),
- end: toArrayCallExpr.getEnd(),
- toInsert: '',
- })));
- return;
- }
- // Template and host binding references.
- const callExpr = checkNonTsReferenceCallsField(ref, 'toArray');
- if (callExpr === null) {
- return;
- }
- const file = index.isHostBindingReference(ref) ? ref.from.file : ref.from.templateFile;
- const offset = index.isHostBindingReference(ref) ? ref.from.hostPropertyNode.getStart() + 1 : 0;
- replacements.push(new project_paths.Replacement(file, new project_paths.TextUpdate({
- // Delete from expression end to call end. E.g. `.toArray(<..>)`.
- position: offset + callExpr.receiver.receiver.sourceSpan.end,
- end: offset + callExpr.sourceSpan.end,
- toInsert: '',
- })));
- }
- function replaceQueryListGetCall(ref, info, globalMetadata, knownQueries, replacements) {
- if (!index.isHostBindingReference(ref) && !index.isTemplateReference(ref) && !index.isTsReference(ref)) {
- return;
- }
- if (knownQueries.isFieldIncompatible(ref.target)) {
- return;
- }
- if (!globalMetadata.knownQueryFields[ref.target.key]?.isMulti) {
- return;
- }
- if (index.isTsReference(ref)) {
- const getCallExpr = checkTsReferenceCallsField(ref, 'get');
- if (getCallExpr === null) {
- return;
- }
- const getExpr = getCallExpr.expression;
- replacements.push(new project_paths.Replacement(project_paths.projectFile(getExpr.getSourceFile(), info), new project_paths.TextUpdate({
- position: getExpr.name.getStart(),
- end: getExpr.name.getEnd(),
- toInsert: 'at',
- })));
- return;
- }
- // Template and host binding references.
- const callExpr = checkNonTsReferenceCallsField(ref, 'get');
- if (callExpr === null) {
- return;
- }
- const file = index.isHostBindingReference(ref) ? ref.from.file : ref.from.templateFile;
- const offset = index.isHostBindingReference(ref) ? ref.from.hostPropertyNode.getStart() + 1 : 0;
- replacements.push(new project_paths.Replacement(file, new project_paths.TextUpdate({
- position: offset + callExpr.receiver.nameSpan.start,
- end: offset + callExpr.receiver.nameSpan.end,
- toInsert: 'at',
- })));
- }
- const problematicQueryListMethods = [
- 'dirty',
- 'changes',
- 'setDirty',
- 'reset',
- 'notifyOnChanges',
- 'destroy',
- ];
- function checkForIncompatibleQueryListAccesses(ref, result) {
- if (index.isTsReference(ref)) {
- for (const problematicFn of problematicQueryListMethods) {
- const access = checkTsReferenceAccessesField(ref, problematicFn);
- if (access !== null) {
- result.potentialProblematicReferenceForMultiQueries[ref.target.key] = true;
- return;
- }
- }
- }
- if (index.isHostBindingReference(ref) || index.isTemplateReference(ref)) {
- for (const problematicFn of problematicQueryListMethods) {
- const access = checkNonTsReferenceAccessesField(ref, problematicFn);
- if (access !== null) {
- result.potentialProblematicReferenceForMultiQueries[ref.target.key] = true;
- return;
- }
- }
- }
- }
- const mapping = new Map([
- ['first', 'at(0)!'],
- ['last', 'at(-1)!'],
- ]);
- function replaceQueryListFirstAndLastReferences(ref, info, globalMetadata, knownQueries, replacements) {
- if (!index.isHostBindingReference(ref) && !index.isTemplateReference(ref) && !index.isTsReference(ref)) {
- return;
- }
- if (knownQueries.isFieldIncompatible(ref.target)) {
- return;
- }
- if (!globalMetadata.knownQueryFields[ref.target.key]?.isMulti) {
- return;
- }
- if (index.isTsReference(ref)) {
- const expr = checkTsReferenceAccessesField(ref, 'first') ?? checkTsReferenceAccessesField(ref, 'last');
- if (expr === null) {
- return;
- }
- replacements.push(new project_paths.Replacement(project_paths.projectFile(expr.getSourceFile(), info), new project_paths.TextUpdate({
- position: expr.name.getStart(),
- end: expr.name.getEnd(),
- toInsert: mapping.get(expr.name.text),
- })));
- return;
- }
- // Template and host binding references.
- const expr = checkNonTsReferenceAccessesField(ref, 'first') ?? checkNonTsReferenceAccessesField(ref, 'last');
- if (expr === null) {
- return;
- }
- const file = index.isHostBindingReference(ref) ? ref.from.file : ref.from.templateFile;
- const offset = index.isHostBindingReference(ref) ? ref.from.hostPropertyNode.getStart() + 1 : 0;
- replacements.push(new project_paths.Replacement(file, new project_paths.TextUpdate({
- position: offset + expr.nameSpan.start,
- end: offset + expr.nameSpan.end,
- toInsert: mapping.get(expr.name),
- })));
- }
- class SignalQueriesMigration extends project_paths.TsurgeComplexMigration {
- config;
- constructor(config = {}) {
- super();
- this.config = config;
- }
- async analyze(info) {
- // Pre-Analyze the program and get access to the template type checker.
- const { templateTypeChecker } = info.ngCompiler?.['ensureAnalyzed']() ?? {
- templateTypeChecker: null,
- };
- const resourceLoader = info.ngCompiler?.['resourceManager'] ?? null;
- // Generate all type check blocks, if we have Angular template information.
- if (templateTypeChecker !== null) {
- templateTypeChecker.generateAllTypeCheckBlocks();
- }
- const { sourceFiles, program: program$1 } = info;
- const checker$1 = program$1.getTypeChecker();
- const reflector = new checker.TypeScriptReflectionHost(checker$1);
- const evaluator = new program.PartialEvaluator(reflector, checker$1, null);
- const res = {
- knownQueryFields: {},
- potentialProblematicQueries: {},
- potentialProblematicReferenceForMultiQueries: {},
- reusableAnalysisReferences: null,
- };
- const groupedAstVisitor = new migrate_ts_type_references.GroupedTsAstVisitor(sourceFiles);
- const referenceResult = { references: [] };
- const classesWithFilteredQueries = new Set();
- const filteredQueriesForCompilationUnit = new Map();
- const findQueryDefinitionsVisitor = (node) => {
- const extractedQuery = extractSourceQueryDefinition(node, reflector, evaluator, info);
- if (extractedQuery !== null) {
- const queryNode = extractedQuery.node;
- const descriptor = {
- key: extractedQuery.id,
- node: queryNode,
- };
- const containingFile = project_paths.projectFile(queryNode.getSourceFile(), info);
- // If we have a config filter function, use it here for later
- // perf-boosted reference lookups. Useful in non-batch mode.
- if (this.config.shouldMigrateQuery === undefined ||
- this.config.shouldMigrateQuery(descriptor, containingFile)) {
- classesWithFilteredQueries.add(queryNode.parent);
- filteredQueriesForCompilationUnit.set(extractedQuery.id, {
- fieldName: extractedQuery.queryInfo.propertyName,
- });
- }
- res.knownQueryFields[extractedQuery.id] = {
- fieldName: extractedQuery.queryInfo.propertyName,
- isMulti: extractedQuery.queryInfo.first === false,
- };
- if (ts.isAccessor(queryNode)) {
- markFieldIncompatibleInMetadata(res.potentialProblematicQueries, extractedQuery.id, migrate_ts_type_references.FieldIncompatibilityReason.Accessor);
- }
- // Detect queries with union types that are uncommon to be
- // automatically migrate-able. E.g. `refs: ElementRef|null`,
- // or `ElementRef|SomeOtherType`.
- if (queryNode.type !== undefined &&
- ts.isUnionTypeNode(queryNode.type) &&
- // Either too large union, or doesn't match `T|undefined`.
- (queryNode.type.types.length > 2 ||
- !queryNode.type.types.some((t) => t.kind === ts.SyntaxKind.UndefinedKeyword))) {
- markFieldIncompatibleInMetadata(res.potentialProblematicQueries, extractedQuery.id, migrate_ts_type_references.FieldIncompatibilityReason.SignalQueries__IncompatibleMultiUnionType);
- }
- // Migrating fields with `@HostBinding` is incompatible as
- // the host binding decorator does not invoke the signal.
- const hostBindingDecorators = checker.getAngularDecorators(extractedQuery.fieldDecorators, ['HostBinding'],
- /* isCore */ false);
- if (hostBindingDecorators.length > 0) {
- markFieldIncompatibleInMetadata(res.potentialProblematicQueries, extractedQuery.id, migrate_ts_type_references.FieldIncompatibilityReason.SignalIncompatibleWithHostBinding);
- }
- }
- };
- this.config.reportProgressFn?.(20, 'Scanning for queries..');
- groupedAstVisitor.register(findQueryDefinitionsVisitor);
- groupedAstVisitor.execute();
- const allFieldsOrKnownQueries = {
- // Note: We don't support cross-target migration of `Partial<T>` usages.
- // This is an acceptable limitation for performance reasons.
- shouldTrackClassReference: (node) => classesWithFilteredQueries.has(node),
- attemptRetrieveDescriptorFromSymbol: (s) => {
- const descriptor = getClassFieldDescriptorForSymbol(s, info);
- // If we are executing in upgraded analysis phase mode, we know all
- // of the queries since there aren't any other compilation units.
- // Ignore references to non-query class fields.
- if (this.config.assumeNonBatch &&
- (descriptor === null || !filteredQueriesForCompilationUnit.has(descriptor.key))) {
- return null;
- }
- // In batch mode, we eagerly, rather expensively, track all references.
- // We don't know yet if something refers to a different query or not, so we
- // eagerly detect such and later filter those problematic references that
- // turned out to refer to queries (once we have the global metadata).
- return descriptor;
- },
- };
- groupedAstVisitor.register(index.createFindAllSourceFileReferencesVisitor(info, checker$1, reflector, resourceLoader, evaluator, templateTypeChecker, allFieldsOrKnownQueries,
- // In non-batch mode, we know what inputs exist and can optimize the reference
- // resolution significantly (for e.g. VSCode integration)— as we know what
- // field names may be used to reference potential queries.
- this.config.assumeNonBatch
- ? new Set(Array.from(filteredQueriesForCompilationUnit.values()).map((f) => f.fieldName))
- : null, referenceResult).visitor);
- const inheritanceGraph = new migrate_ts_type_references.InheritanceGraph(checker$1).expensivePopulate(info.sourceFiles);
- migrate_ts_type_references.checkIncompatiblePatterns(inheritanceGraph, checker$1, groupedAstVisitor, {
- ...allFieldsOrKnownQueries,
- isFieldIncompatible: (f) => res.potentialProblematicQueries[f.key]?.fieldReason !== null ||
- res.potentialProblematicQueries[f.key]?.classReason !== null,
- markClassIncompatible: (clazz, reason) => {
- for (const field of clazz.members) {
- const key = getUniqueIDForClassProperty(field, info);
- if (key !== null) {
- res.potentialProblematicQueries[key] ??= { classReason: null, fieldReason: null };
- res.potentialProblematicQueries[key].classReason = reason;
- }
- }
- },
- markFieldIncompatible: (f, incompatibility) => markFieldIncompatibleInMetadata(res.potentialProblematicQueries, f.key, incompatibility.reason),
- }, () => Array.from(classesWithFilteredQueries));
- this.config.reportProgressFn?.(60, 'Scanning for references and problematic patterns..');
- groupedAstVisitor.execute();
- // Determine incompatible queries based on problematic references
- // we saw in TS code, templates or host bindings.
- for (const ref of referenceResult.references) {
- if (index.isTsReference(ref) && ref.from.isWrite) {
- markFieldIncompatibleInMetadata(res.potentialProblematicQueries, ref.target.key, migrate_ts_type_references.FieldIncompatibilityReason.WriteAssignment);
- }
- if ((index.isTemplateReference(ref) || index.isHostBindingReference(ref)) && ref.from.isWrite) {
- markFieldIncompatibleInMetadata(res.potentialProblematicQueries, ref.target.key, migrate_ts_type_references.FieldIncompatibilityReason.WriteAssignment);
- }
- // TODO: Remove this when we support signal narrowing in templates.
- // https://github.com/angular/angular/pull/55456.
- if (index.isTemplateReference(ref) && ref.from.isLikelyPartOfNarrowing) {
- markFieldIncompatibleInMetadata(res.potentialProblematicQueries, ref.target.key, migrate_ts_type_references.FieldIncompatibilityReason.PotentiallyNarrowedInTemplateButNoSupportYet);
- }
- // Check for other incompatible query list accesses.
- checkForIncompatibleQueryListAccesses(ref, res);
- }
- if (this.config.assumeNonBatch) {
- res.reusableAnalysisReferences = referenceResult.references;
- }
- return project_paths.confirmAsSerializable(res);
- }
- async combine(unitA, unitB) {
- const combined = {
- knownQueryFields: {},
- potentialProblematicQueries: {},
- potentialProblematicReferenceForMultiQueries: {},
- reusableAnalysisReferences: null,
- };
- for (const unit of [unitA, unitB]) {
- for (const [id, value] of Object.entries(unit.knownQueryFields)) {
- combined.knownQueryFields[id] = value;
- }
- for (const [id, info] of Object.entries(unit.potentialProblematicQueries)) {
- if (info.fieldReason !== null) {
- markFieldIncompatibleInMetadata(combined.potentialProblematicQueries, id, info.fieldReason);
- }
- if (info.classReason !== null) {
- combined.potentialProblematicQueries[id] ??= {
- classReason: null,
- fieldReason: null,
- };
- combined.potentialProblematicQueries[id].classReason =
- info.classReason;
- }
- }
- for (const id of Object.keys(unit.potentialProblematicReferenceForMultiQueries)) {
- combined.potentialProblematicReferenceForMultiQueries[id] = true;
- }
- if (unit.reusableAnalysisReferences !== null) {
- combined.reusableAnalysisReferences = unit.reusableAnalysisReferences;
- }
- }
- for (const unit of [unitA, unitB]) {
- for (const id of Object.keys(unit.potentialProblematicReferenceForMultiQueries)) {
- if (combined.knownQueryFields[id]?.isMulti) {
- markFieldIncompatibleInMetadata(combined.potentialProblematicQueries, id, migrate_ts_type_references.FieldIncompatibilityReason.SignalQueries__QueryListProblematicFieldAccessed);
- }
- }
- }
- return project_paths.confirmAsSerializable(combined);
- }
- async globalMeta(combinedData) {
- const globalUnitData = {
- knownQueryFields: combinedData.knownQueryFields,
- problematicQueries: combinedData.potentialProblematicQueries,
- reusableAnalysisReferences: combinedData.reusableAnalysisReferences,
- };
- for (const id of Object.keys(combinedData.potentialProblematicReferenceForMultiQueries)) {
- if (combinedData.knownQueryFields[id]?.isMulti) {
- markFieldIncompatibleInMetadata(globalUnitData.problematicQueries, id, migrate_ts_type_references.FieldIncompatibilityReason.SignalQueries__QueryListProblematicFieldAccessed);
- }
- }
- return project_paths.confirmAsSerializable(globalUnitData);
- }
- async migrate(globalMetadata, info) {
- // Pre-Analyze the program and get access to the template type checker.
- const { templateTypeChecker, metaReader } = info.ngCompiler?.['ensureAnalyzed']() ?? {
- templateTypeChecker: null,
- metaReader: null,
- };
- const resourceLoader = info.ngCompiler?.['resourceManager'] ?? null;
- const { program: program$1, sourceFiles } = info;
- const checker$1 = program$1.getTypeChecker();
- const reflector = new checker.TypeScriptReflectionHost(checker$1);
- const evaluator = new program.PartialEvaluator(reflector, checker$1, null);
- const replacements = [];
- const importManager = new checker.ImportManager();
- const printer = ts.createPrinter();
- const filesWithSourceQueries = new Map();
- const filesWithIncompleteMigration = new Map();
- const filesWithQueryListOutsideOfDeclarations = new WeakSet();
- const knownQueries = new KnownQueries(info, this.config, globalMetadata);
- const referenceResult = { references: [] };
- const sourceQueries = [];
- // Detect all queries in this unit.
- const queryWholeProgramVisitor = (node) => {
- // Detect all SOURCE queries and migrate them, if possible.
- const extractedQuery = extractSourceQueryDefinition(node, reflector, evaluator, info);
- if (extractedQuery !== null) {
- knownQueries.registerQueryField(extractedQuery.node, extractedQuery.id);
- sourceQueries.push(extractedQuery);
- return;
- }
- // Detect OTHER queries, inside `.d.ts`. Needed for reference resolution below.
- if (ts.isPropertyDeclaration(node) ||
- (ts.isAccessor(node) && ts.isClassDeclaration(node.parent))) {
- const classFieldID = getUniqueIDForClassProperty(node, info);
- if (classFieldID !== null && globalMetadata.knownQueryFields[classFieldID] !== undefined) {
- knownQueries.registerQueryField(node, classFieldID);
- return;
- }
- }
- // Detect potential usages of `QueryList` outside of queries or imports.
- // Those prevent us from removing the import later.
- if (ts.isIdentifier(node) &&
- node.text === 'QueryList' &&
- ts.findAncestor(node, ts.isImportDeclaration) === undefined) {
- filesWithQueryListOutsideOfDeclarations.add(node.getSourceFile());
- }
- ts.forEachChild(node, queryWholeProgramVisitor);
- };
- for (const sf of info.fullProgramSourceFiles) {
- ts.forEachChild(sf, queryWholeProgramVisitor);
- }
- // Set of all queries in the program. Useful for speeding up reference
- // lookups below.
- const fieldNamesToConsiderForReferenceLookup = new Set(Object.values(globalMetadata.knownQueryFields).map((f) => f.fieldName));
- // Find all references.
- const groupedAstVisitor = new migrate_ts_type_references.GroupedTsAstVisitor(sourceFiles);
- // Re-use previous reference result if available, instead of
- // looking for references which is quite expensive.
- if (globalMetadata.reusableAnalysisReferences !== null) {
- referenceResult.references = globalMetadata.reusableAnalysisReferences;
- }
- else {
- groupedAstVisitor.register(index.createFindAllSourceFileReferencesVisitor(info, checker$1, reflector, resourceLoader, evaluator, templateTypeChecker, knownQueries, fieldNamesToConsiderForReferenceLookup, referenceResult).visitor);
- }
- // Check inheritance.
- // NOTE: Inheritance is only checked in the migrate stage as we cannot reliably
- // check during analyze— where we don't know what fields from foreign `.d.ts`
- // files refer to queries or not.
- const inheritanceGraph = new migrate_ts_type_references.InheritanceGraph(checker$1).expensivePopulate(info.sourceFiles);
- migrate_ts_type_references.checkInheritanceOfKnownFields(inheritanceGraph, metaReader, knownQueries, {
- getFieldsForClass: (n) => knownQueries.getQueryFieldsOfClass(n) ?? [],
- isClassWithKnownFields: (clazz) => knownQueries.getQueryFieldsOfClass(clazz) !== undefined,
- });
- this.config.reportProgressFn?.(80, 'Checking inheritance..');
- groupedAstVisitor.execute();
- if (this.config.bestEffortMode) {
- filterBestEffortIncompatibilities(knownQueries);
- }
- this.config.reportProgressFn?.(90, 'Migrating queries..');
- // Migrate declarations.
- for (const extractedQuery of sourceQueries) {
- const node = extractedQuery.node;
- const sf = node.getSourceFile();
- const descriptor = { key: extractedQuery.id, node: extractedQuery.node };
- const incompatibility = knownQueries.getIncompatibilityForField(descriptor);
- updateFileState(filesWithSourceQueries, sf, extractedQuery.kind);
- if (incompatibility !== null) {
- // Add a TODO for the incompatible query, if desired.
- if (this.config.insertTodosForSkippedFields) {
- replacements.push(...migrate_ts_type_references.insertTodoForIncompatibility(node, info, incompatibility, {
- single: 'query',
- plural: 'queries',
- }));
- }
- updateFileState(filesWithIncompleteMigration, sf, extractedQuery.kind);
- continue;
- }
- replacements.push(...computeReplacementsToMigrateQuery(node, extractedQuery, importManager, info, printer, info.userOptions, checker$1));
- }
- // Migrate references.
- const referenceMigrationHost = {
- printer,
- replacements,
- shouldMigrateReferencesToField: (field) => !knownQueries.isFieldIncompatible(field),
- shouldMigrateReferencesToClass: (clazz) => !!knownQueries
- .getQueryFieldsOfClass(clazz)
- ?.some((q) => !knownQueries.isFieldIncompatible(q)),
- };
- migrate_ts_type_references.migrateTypeScriptReferences(referenceMigrationHost, referenceResult.references, checker$1, info);
- migrateTemplateReferences(referenceMigrationHost, referenceResult.references);
- migrateHostBindings(referenceMigrationHost, referenceResult.references, info);
- migrate_ts_type_references.migrateTypeScriptTypeReferences(referenceMigrationHost, referenceResult.references, importManager, info);
- // Fix problematic calls, like `QueryList#toArray`, or `QueryList#get`.
- for (const ref of referenceResult.references) {
- removeQueryListToArrayCall(ref, info, globalMetadata, knownQueries, replacements);
- replaceQueryListGetCall(ref, info, globalMetadata, knownQueries, replacements);
- replaceQueryListFirstAndLastReferences(ref, info, globalMetadata, knownQueries, replacements);
- }
- // Remove imports if possible.
- for (const [file, types] of filesWithSourceQueries) {
- let seenIncompatibleMultiQuery = false;
- for (const type of types) {
- const incompatibleQueryTypesForFile = filesWithIncompleteMigration.get(file);
- // Query type is fully migrated. No incompatible queries in file.
- if (!incompatibleQueryTypesForFile?.has(type)) {
- importManager.removeImport(file, queryFunctionNameToDecorator(type), '@angular/core');
- }
- else if (type === 'viewChildren' || type === 'contentChildren') {
- seenIncompatibleMultiQuery = true;
- }
- }
- if (!seenIncompatibleMultiQuery && !filesWithQueryListOutsideOfDeclarations.has(file)) {
- importManager.removeImport(file, 'QueryList', '@angular/core');
- }
- }
- apply_import_manager.applyImportManagerChanges(importManager, replacements, sourceFiles, info);
- return { replacements, knownQueries };
- }
- async stats(globalMetadata) {
- let queriesCount = 0;
- let multiQueries = 0;
- let incompatibleQueries = 0;
- const fieldIncompatibleCounts = {};
- const classIncompatibleCounts = {};
- for (const query of Object.values(globalMetadata.knownQueryFields)) {
- queriesCount++;
- if (query.isMulti) {
- multiQueries++;
- }
- }
- for (const [id, info] of Object.entries(globalMetadata.problematicQueries)) {
- if (globalMetadata.knownQueryFields[id] === undefined) {
- continue;
- }
- // Do not count queries that were forcibly ignored via best effort mode.
- if (this.config.bestEffortMode &&
- (info.fieldReason === null ||
- !migrate_ts_type_references.nonIgnorableFieldIncompatibilities.includes(info.fieldReason))) {
- continue;
- }
- incompatibleQueries++;
- if (info.classReason !== null) {
- const reasonName = migrate_ts_type_references.ClassIncompatibilityReason[info.classReason];
- const key = `incompat-class-${reasonName}`;
- classIncompatibleCounts[key] ??= 0;
- classIncompatibleCounts[key]++;
- }
- if (info.fieldReason !== null) {
- const reasonName = migrate_ts_type_references.FieldIncompatibilityReason[info.fieldReason];
- const key = `incompat-field-${reasonName}`;
- fieldIncompatibleCounts[key] ??= 0;
- fieldIncompatibleCounts[key]++;
- }
- }
- return {
- counters: {
- queriesCount,
- multiQueries,
- incompatibleQueries,
- ...fieldIncompatibleCounts,
- ...classIncompatibleCounts,
- },
- };
- }
- }
- /**
- * Updates the given map to capture the given query type.
- * The map may track migrated queries in a file, or query types
- * that couldn't be migrated.
- */
- function updateFileState(stateMap, node, queryType) {
- const file = node.getSourceFile();
- if (!stateMap.has(file)) {
- stateMap.set(file, new Set());
- }
- stateMap.get(file).add(queryType);
- }
- function migrate(options) {
- return async (tree, context) => {
- const { buildPaths, testPaths } = await project_tsconfig_paths.getProjectTsConfigPaths(tree);
- if (!buildPaths.length && !testPaths.length) {
- throw new schematics.SchematicsException('Could not find any tsconfig file. Cannot run signal queries migration.');
- }
- const fs = new project_paths.DevkitMigrationFilesystem(tree);
- checker.setFileSystem(fs);
- const migration = new SignalQueriesMigration({
- bestEffortMode: options.bestEffortMode,
- insertTodosForSkippedFields: options.insertTodos,
- shouldMigrateQuery: (_query, file) => {
- return (file.rootRelativePath.startsWith(fs.normalize(options.path)) &&
- !/(^|\/)node_modules\//.test(file.rootRelativePath));
- },
- });
- const analysisPath = fs.resolve(options.analysisDir);
- const unitResults = [];
- const programInfos = [...buildPaths, ...testPaths].map((tsconfigPath) => {
- context.logger.info(`Preparing analysis for: ${tsconfigPath}..`);
- const baseInfo = migration.createProgram(tsconfigPath, fs);
- const info = migration.prepareProgram(baseInfo);
- // Support restricting the analysis to subfolders for larger projects.
- if (analysisPath !== '/') {
- info.sourceFiles = info.sourceFiles.filter((sf) => sf.fileName.startsWith(analysisPath));
- info.fullProgramSourceFiles = info.fullProgramSourceFiles.filter((sf) => sf.fileName.startsWith(analysisPath));
- }
- return { info, tsconfigPath };
- });
- // Analyze phase. Treat all projects as compilation units as
- // this allows us to support references between those.
- for (const { info, tsconfigPath } of programInfos) {
- context.logger.info(`Scanning for queries: ${tsconfigPath}..`);
- unitResults.push(await migration.analyze(info));
- }
- context.logger.info(``);
- context.logger.info(`Processing analysis data between targets..`);
- context.logger.info(``);
- const combined = await project_paths.synchronouslyCombineUnitData(migration, unitResults);
- if (combined === null) {
- context.logger.error('Migration failed unexpectedly with no analysis data');
- return;
- }
- const globalMeta = await migration.globalMeta(combined);
- const replacementsPerFile = new Map();
- for (const { info, tsconfigPath } of programInfos) {
- context.logger.info(`Migrating: ${tsconfigPath}..`);
- const { replacements } = await migration.migrate(globalMeta, info);
- const changesPerFile = project_paths.groupReplacementsByFile(replacements);
- for (const [file, changes] of changesPerFile) {
- if (!replacementsPerFile.has(file)) {
- replacementsPerFile.set(file, changes);
- }
- }
- }
- context.logger.info(`Applying changes..`);
- for (const [file, changes] of replacementsPerFile) {
- const recorder = tree.beginUpdate(file);
- for (const c of changes) {
- recorder
- .remove(c.data.position, c.data.end - c.data.position)
- .insertLeft(c.data.position, c.data.toInsert);
- }
- tree.commitUpdate(recorder);
- }
- context.logger.info('');
- context.logger.info(`Successfully migrated to signal queries 🎉`);
- const { counters: { queriesCount, incompatibleQueries, multiQueries }, } = await migration.stats(globalMeta);
- const migratedQueries = queriesCount - incompatibleQueries;
- context.logger.info('');
- context.logger.info(`Successfully migrated to signal queries 🎉`);
- context.logger.info(` -> Migrated ${migratedQueries}/${queriesCount} queries.`);
- if (incompatibleQueries > 0 && !options.insertTodos) {
- context.logger.warn(`To see why ${incompatibleQueries} queries couldn't be migrated`);
- context.logger.warn(`consider re-running with "--insert-todos" or "--best-effort-mode".`);
- }
- if (options.bestEffortMode) {
- context.logger.warn(`You ran with best effort mode. Manually verify all code ` +
- `works as intended, and fix where necessary.`);
- }
- };
- }
- exports.migrate = migrate;
|