@ethlete/query 6.0.0-next.61 → 6.0.0-next.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/fesm2022/ethlete-query-devtools-contract.mjs +1 -1
- package/fesm2022/ethlete-query-ngrx-toolkit.mjs +30 -7
- package/fesm2022/ethlete-query-ngrx-toolkit.mjs.map +1 -1
- package/fesm2022/ethlete-query.mjs +183 -43
- package/fesm2022/ethlete-query.mjs.map +1 -1
- package/generators/migrate-from-ngrx-toolkit/args-assignability.js +55 -8
- package/generators/migrate-from-ngrx-toolkit/consumers.js +133 -37
- package/generators/migrate-from-ngrx-toolkit/creators.js +8 -4
- package/generators/migrate-from-ngrx-toolkit/feature.js +31 -4
- package/generators/migrate-from-ngrx-toolkit/migration.js +3 -1
- package/generators/migrate-from-ngrx-toolkit/module-resolution.js +23 -0
- package/generators/migrate-from-ngrx-toolkit/store-removal.js +64 -11
- package/generators/migrate-to-query-v3/auth-consumers-migration.js +64 -0
- package/generators/migrate-to-query-v3/auth-provider-scaffold.js +93 -1
- package/generators/migrate-to-query-v3/cleanup-migration.js +135 -15
- package/generators/migrate-to-query-v3/legacy-prepare-migration.js +275 -27
- package/generators/migrate-to-query-v3/legacy-query-creator-migration.js +171 -25
- package/generators/migrate-to-query-v3/migration.js +9 -3
- package/generators/migrate-to-query-v3/module-resolution.js +131 -0
- package/generators/migrate-to-query-v3/query-client-migration.js +82 -14
- package/generators/migrate-to-query-v3/rename-symbols.js +16 -11
- package/generators/move-devtools-contract-imports/moved-names.js +1 -0
- package/migrations.json +10 -0
- package/package.json +1 -1
- package/types/ethlete-query-devtools-contract.d.ts +2 -2
- package/types/ethlete-query-ngrx-toolkit.d.ts +13 -0
- package/types/ethlete-query.d.ts +40 -6
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import {
|
|
2
|
+
import { findUsedNames, pruneUnusedNamedImports } from './rename-symbols.js';
|
|
3
|
+
import { createSourceFile, ensureAngularCoreImports, ensureImportFromQuery, getIndentation, getLineNumber, getLineNumberFromPosition, } from './shared.js';
|
|
4
|
+
/** The creator methods that resolve an injector, and so throw `ET950` when called without one. */
|
|
5
|
+
const INJECTOR_METHODS = new Set(['prepare', 'createSubject', 'createSignal', 'behaviorSubject']);
|
|
3
6
|
/** `default` can never be a class name, so it cannot collide with a named class of the same file. */
|
|
4
7
|
const ANONYMOUS_DEFAULT_CLASS_KEY = 'default';
|
|
5
8
|
const getClassKey = (node) => node.name?.text ?? ANONYMOUS_DEFAULT_CLASS_KEY;
|
|
@@ -22,6 +25,7 @@ export const migrateLegacyPrepareCalls = (tree, report, scope) => {
|
|
|
22
25
|
return;
|
|
23
26
|
}
|
|
24
27
|
allUsages.push(...findLegacyCreatorUsages(content, filePath));
|
|
28
|
+
reportUnresolvedReceivers(content, filePath, report);
|
|
25
29
|
});
|
|
26
30
|
if (allUsages.length === 0) {
|
|
27
31
|
console.log('\n✅ No legacy query creator usages found');
|
|
@@ -60,12 +64,13 @@ const findLegacyCreatorUsages = (content, filePath) => {
|
|
|
60
64
|
const visit = (node) => {
|
|
61
65
|
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
|
|
62
66
|
const propertyAccess = node.expression;
|
|
63
|
-
if (ts.isIdentifier(propertyAccess.name) && propertyAccess.name.text
|
|
67
|
+
if (ts.isIdentifier(propertyAccess.name) && INJECTOR_METHODS.has(propertyAccess.name.text)) {
|
|
64
68
|
const creatorName = ts.isIdentifier(propertyAccess.expression) ? propertyAccess.expression.text : undefined;
|
|
65
69
|
if (creatorName && legacyCreatorNames.has(creatorName)) {
|
|
66
70
|
usages.push({
|
|
67
71
|
filePath,
|
|
68
72
|
creatorName,
|
|
73
|
+
method: propertyAccess.name.text,
|
|
69
74
|
line: getLineNumber(node, sourceFile),
|
|
70
75
|
position: {
|
|
71
76
|
start: node.getStart(sourceFile),
|
|
@@ -82,6 +87,35 @@ const findLegacyCreatorUsages = (content, filePath) => {
|
|
|
82
87
|
visit(sourceFile);
|
|
83
88
|
return usages;
|
|
84
89
|
};
|
|
90
|
+
const LEGACY_PROPERTY_NAME = /^legacy[A-Z]/;
|
|
91
|
+
const reportUnresolvedReceivers = (content, filePath, report) => {
|
|
92
|
+
if (!/\blegacy[A-Z]\w*\s*\.(prepare|createSubject|createSignal|behaviorSubject)\(/.test(content)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const sourceFile = createSourceFile(content, filePath);
|
|
96
|
+
const visit = (node) => {
|
|
97
|
+
if (ts.isCallExpression(node) &&
|
|
98
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
99
|
+
ts.isIdentifier(node.expression.name) &&
|
|
100
|
+
INJECTOR_METHODS.has(node.expression.name.text) &&
|
|
101
|
+
ts.isPropertyAccessExpression(node.expression.expression) &&
|
|
102
|
+
LEGACY_PROPERTY_NAME.test(node.expression.expression.name.text) &&
|
|
103
|
+
!CONTEXTS_WITH_INJECTOR.includes(determineUsageContext(node))) {
|
|
104
|
+
const line = getLineNumber(node, sourceFile);
|
|
105
|
+
const receiver = node.expression.expression.getText(sourceFile);
|
|
106
|
+
report.addManualReview({
|
|
107
|
+
title: `Check the injector of ${receiver}.${node.expression.name.text}()`,
|
|
108
|
+
summary: `${receiver}.${node.expression.name.text}() is called on a creator the migration cannot resolve, because the receiver is not a plain legacy* identifier. The call may run outside an injection context and then throws ET950.`,
|
|
109
|
+
action: 'Pass `{ injector }` to the call, or add provideLegacyPrepareFallback() to the app providers. A call in a constructor, a field initializer or an inject() function needs nothing.',
|
|
110
|
+
locations: [{ filePath, line }],
|
|
111
|
+
source: 'legacy-prepare-migration',
|
|
112
|
+
dedupeKey: `unresolved-receiver:${filePath}:${line}`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
ts.forEachChild(node, visit);
|
|
116
|
+
};
|
|
117
|
+
visit(sourceFile);
|
|
118
|
+
};
|
|
85
119
|
const findLegacyCreatorNames = (sourceFile) => {
|
|
86
120
|
const names = new Set();
|
|
87
121
|
const visit = (node) => {
|
|
@@ -330,6 +364,12 @@ const createInjectorMembers = (tree, classInjectors) => {
|
|
|
330
364
|
console.log(`\n✅ Added injector members to ${Array.from(filesToUpdate.values()).flat().length} classes`);
|
|
331
365
|
}
|
|
332
366
|
};
|
|
367
|
+
const getMemberStartWithComments = (content, member) => {
|
|
368
|
+
const ownLineComment = ts
|
|
369
|
+
.getLeadingCommentRanges(content, member.getFullStart())
|
|
370
|
+
?.find((range) => /\n[ \t]*$/.test(content.slice(0, range.pos)));
|
|
371
|
+
return ownLineComment?.pos ?? member.getStart();
|
|
372
|
+
};
|
|
333
373
|
const addInjectorMemberToClass = (content, classInfo) => {
|
|
334
374
|
const sourceFile = createSourceFile(content);
|
|
335
375
|
let targetClass;
|
|
@@ -343,7 +383,9 @@ const addInjectorMemberToClass = (content, classInfo) => {
|
|
|
343
383
|
if (!targetClass) {
|
|
344
384
|
return content;
|
|
345
385
|
}
|
|
346
|
-
const classBodyStart = targetClass.members.length > 0
|
|
386
|
+
const classBodyStart = targetClass.members.length > 0
|
|
387
|
+
? getMemberStartWithComments(content, targetClass.members[0])
|
|
388
|
+
: targetClass.getEnd() - 1;
|
|
347
389
|
const indentation = getIndentation(content, classBodyStart);
|
|
348
390
|
const injectorMember = `private ${classInfo.injectorMemberName} = inject(Injector);\n\n${indentation}`;
|
|
349
391
|
return content.slice(0, classBodyStart) + injectorMember + content.slice(classBodyStart);
|
|
@@ -362,7 +404,7 @@ const transformLegacyCreatorPrepareCalls = (tree, classInjectors, pollingInfo, s
|
|
|
362
404
|
return;
|
|
363
405
|
}
|
|
364
406
|
const content = tree.read(filePath, 'utf-8');
|
|
365
|
-
if (!content ||
|
|
407
|
+
if (!content || !/\.(prepare|createSubject|createSignal|behaviorSubject)\(/.test(content)) {
|
|
366
408
|
return;
|
|
367
409
|
}
|
|
368
410
|
const usages = findLegacyCreatorUsages(content, filePath);
|
|
@@ -371,12 +413,11 @@ const transformLegacyCreatorPrepareCalls = (tree, classInjectors, pollingInfo, s
|
|
|
371
413
|
}
|
|
372
414
|
});
|
|
373
415
|
filesByPath.forEach((classes, filePath) => {
|
|
374
|
-
|
|
416
|
+
const content = tree.read(filePath, 'utf-8');
|
|
375
417
|
if (!content) {
|
|
376
418
|
return;
|
|
377
419
|
}
|
|
378
|
-
|
|
379
|
-
const nextContent = transformPrepareCallsInFile(content, classes, pollingInfo);
|
|
420
|
+
const nextContent = pruneUnusedNamedImports(transformPrepareCallsInFile(ensureAngularCoreImports(content, ['inject', 'Injector']), classes, pollingInfo), new Set([...findUsedNames(content), 'inject', 'Injector']));
|
|
380
421
|
if (nextContent !== content) {
|
|
381
422
|
tree.write(filePath, nextContent);
|
|
382
423
|
updatedFiles.push(filePath);
|
|
@@ -391,6 +432,7 @@ const transformPrepareCallsInFile = (content, classes, pollingInfo) => {
|
|
|
391
432
|
const replacements = [];
|
|
392
433
|
const legacyCreatorNames = findLegacyCreatorNames(sourceFile);
|
|
393
434
|
const classNameToInjector = new Map();
|
|
435
|
+
const rewrittenComputeds = new Set();
|
|
394
436
|
const functionsNeedingInjector = new Map();
|
|
395
437
|
if (legacyCreatorNames.size === 0) {
|
|
396
438
|
return content;
|
|
@@ -401,13 +443,31 @@ const transformPrepareCallsInFile = (content, classes, pollingInfo) => {
|
|
|
401
443
|
const visit = (node) => {
|
|
402
444
|
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
|
|
403
445
|
const propertyAccess = node.expression;
|
|
404
|
-
if (ts.isIdentifier(propertyAccess.name) && propertyAccess.name.text
|
|
446
|
+
if (ts.isIdentifier(propertyAccess.name) && INJECTOR_METHODS.has(propertyAccess.name.text)) {
|
|
405
447
|
const creatorName = ts.isIdentifier(propertyAccess.expression) ? propertyAccess.expression.text : undefined;
|
|
406
448
|
if (!creatorName || !legacyCreatorNames.has(creatorName)) {
|
|
407
449
|
ts.forEachChild(node, visit);
|
|
408
450
|
return;
|
|
409
451
|
}
|
|
410
452
|
const context = determineUsageContext(node);
|
|
453
|
+
const computedHost = findReturningPlainComputed(node);
|
|
454
|
+
const rewriteComputed = (injectorExpression) => {
|
|
455
|
+
if (!computedHost || rewrittenComputeds.has(computedHost))
|
|
456
|
+
return;
|
|
457
|
+
rewrittenComputeds.add(computedHost);
|
|
458
|
+
replacements.push({
|
|
459
|
+
start: computedHost.expression.getStart(sourceFile),
|
|
460
|
+
end: computedHost.expression.getEnd(),
|
|
461
|
+
replacement: 'queryComputed',
|
|
462
|
+
});
|
|
463
|
+
if (computedHost.arguments.length === 1) {
|
|
464
|
+
replacements.push({
|
|
465
|
+
start: computedHost.arguments[0].getEnd(),
|
|
466
|
+
end: computedHost.arguments[0].getEnd(),
|
|
467
|
+
replacement: `, { injector: ${injectorExpression} }`,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
};
|
|
411
471
|
if (CONTEXTS_WITH_INJECTOR.includes(context)) {
|
|
412
472
|
ts.forEachChild(node, visit);
|
|
413
473
|
return;
|
|
@@ -420,7 +480,8 @@ const transformPrepareCallsInFile = (content, classes, pollingInfo) => {
|
|
|
420
480
|
ts.forEachChild(node, visit);
|
|
421
481
|
return;
|
|
422
482
|
}
|
|
423
|
-
|
|
483
|
+
rewriteComputed(`this.${injectorMember}`);
|
|
484
|
+
const transformedCall = transformCall(node, `this.${injectorMember}`, sourceFile, pollingInfo);
|
|
424
485
|
if (transformedCall) {
|
|
425
486
|
replacements.push({
|
|
426
487
|
start: node.getStart(sourceFile),
|
|
@@ -432,15 +493,22 @@ const transformPrepareCallsInFile = (content, classes, pollingInfo) => {
|
|
|
432
493
|
else if (containingFunction) {
|
|
433
494
|
const functionWithInject = findOutermostFunctionWithInject(containingFunction);
|
|
434
495
|
if (!functionWithInject) {
|
|
496
|
+
if (propertyAccess.name.text === 'prepare' && isInsideAsyncValidator(node)) {
|
|
497
|
+
replacements.push({
|
|
498
|
+
start: node.getStart(sourceFile),
|
|
499
|
+
end: node.getEnd(),
|
|
500
|
+
replacement: transformSinglePrepareCall(node, undefined, sourceFile, pollingInfo),
|
|
501
|
+
});
|
|
502
|
+
}
|
|
435
503
|
ts.forEachChild(node, visit);
|
|
436
504
|
return;
|
|
437
505
|
}
|
|
438
506
|
const functionPosition = functionWithInject.getStart(sourceFile);
|
|
439
507
|
if (!functionsNeedingInjector.has(functionPosition)) {
|
|
440
|
-
const
|
|
441
|
-
if (
|
|
508
|
+
const existingInjector = findExistingInjector(functionWithInject);
|
|
509
|
+
if (existingInjector) {
|
|
442
510
|
functionsNeedingInjector.set(functionPosition, {
|
|
443
|
-
injectorName:
|
|
511
|
+
injectorName: existingInjector,
|
|
444
512
|
bodyStart: -1,
|
|
445
513
|
needsDeclaration: false,
|
|
446
514
|
});
|
|
@@ -457,7 +525,8 @@ const transformPrepareCallsInFile = (content, classes, pollingInfo) => {
|
|
|
457
525
|
}
|
|
458
526
|
}
|
|
459
527
|
const injectorName = functionsNeedingInjector.get(functionPosition)?.injectorName ?? 'injector';
|
|
460
|
-
|
|
528
|
+
rewriteComputed(injectorName);
|
|
529
|
+
const transformedCall = transformCall(node, injectorName, sourceFile, pollingInfo);
|
|
461
530
|
if (transformedCall) {
|
|
462
531
|
replacements.push({
|
|
463
532
|
start: node.getStart(sourceFile),
|
|
@@ -497,9 +566,9 @@ const transformPrepareCallsInFile = (content, classes, pollingInfo) => {
|
|
|
497
566
|
});
|
|
498
567
|
result = result.slice(0, start + adjustment) + replacement + result.slice(end + adjustment);
|
|
499
568
|
});
|
|
500
|
-
return result;
|
|
569
|
+
return rewrittenComputeds.size > 0 ? ensureImportFromQuery(result, ['queryComputed']) : result;
|
|
501
570
|
};
|
|
502
|
-
const
|
|
571
|
+
const findExistingInjector = (func) => {
|
|
503
572
|
for (const parameter of func.parameters) {
|
|
504
573
|
if (!ts.isIdentifier(parameter.name)) {
|
|
505
574
|
continue;
|
|
@@ -511,6 +580,23 @@ const findInjectorParameter = (func) => {
|
|
|
511
580
|
return parameter.name.text;
|
|
512
581
|
}
|
|
513
582
|
}
|
|
583
|
+
if (!func.body || !ts.isBlock(func.body))
|
|
584
|
+
return undefined;
|
|
585
|
+
for (const statement of func.body.statements) {
|
|
586
|
+
if (!ts.isVariableStatement(statement))
|
|
587
|
+
continue;
|
|
588
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
589
|
+
const initializer = declaration.initializer;
|
|
590
|
+
if (ts.isIdentifier(declaration.name) &&
|
|
591
|
+
initializer &&
|
|
592
|
+
ts.isCallExpression(initializer) &&
|
|
593
|
+
ts.isIdentifier(initializer.expression) &&
|
|
594
|
+
initializer.expression.text === 'inject' &&
|
|
595
|
+
initializer.arguments[0]?.getText() === 'Injector') {
|
|
596
|
+
return declaration.name.text;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
514
600
|
return undefined;
|
|
515
601
|
};
|
|
516
602
|
const getFunctionBodyStart = (func, sourceFile) => {
|
|
@@ -560,6 +646,26 @@ const findContainingClass = (node) => {
|
|
|
560
646
|
}
|
|
561
647
|
return undefined;
|
|
562
648
|
};
|
|
649
|
+
const transformCall = (callNode, injectorReference, sourceFile, pollingInfo) => callNode.expression.name.text === 'prepare'
|
|
650
|
+
? transformSinglePrepareCall(callNode, injectorReference, sourceFile, pollingInfo)
|
|
651
|
+
: transformContainerCall(callNode, injectorReference, sourceFile);
|
|
652
|
+
const transformContainerCall = (callNode, injectorReference, sourceFile) => {
|
|
653
|
+
const propertyAccess = callNode.expression;
|
|
654
|
+
const initialValue = callNode.arguments[0]?.getText(sourceFile) ?? 'null';
|
|
655
|
+
const config = callNode.arguments[1];
|
|
656
|
+
let nextConfig = `{ injector: ${injectorReference} }`;
|
|
657
|
+
if (config && ts.isObjectLiteralExpression(config)) {
|
|
658
|
+
const hasInjector = config.properties.some((property) => !!property.name && ts.isIdentifier(property.name) && property.name.text === 'injector');
|
|
659
|
+
if (hasInjector) {
|
|
660
|
+
return undefined;
|
|
661
|
+
}
|
|
662
|
+
nextConfig = `{ ${[...config.properties.map((property) => property.getText(sourceFile)), `injector: ${injectorReference}`].join(', ')} }`;
|
|
663
|
+
}
|
|
664
|
+
else if (config) {
|
|
665
|
+
nextConfig = `{ ...${config.getText(sourceFile)}, injector: ${injectorReference} }`;
|
|
666
|
+
}
|
|
667
|
+
return `${propertyAccess.expression.getText(sourceFile)}.${propertyAccess.name.text}(${initialValue}, ${nextConfig})`;
|
|
668
|
+
};
|
|
563
669
|
const transformSinglePrepareCall = (callNode, injectorReference, sourceFile, pollingInfo) => {
|
|
564
670
|
const propertyAccess = callNode.expression;
|
|
565
671
|
const creatorName = propertyAccess.expression.text;
|
|
@@ -594,9 +700,10 @@ const transformSinglePrepareCall = (callNode, injectorReference, sourceFile, pol
|
|
|
594
700
|
spreadExpression = existingArgument.getText(sourceFile);
|
|
595
701
|
}
|
|
596
702
|
}
|
|
597
|
-
|
|
703
|
+
if (injectorReference)
|
|
704
|
+
nextArgumentProperties.push(`injector: ${injectorReference}`);
|
|
598
705
|
const queryVariableName = findQueryVariableNameForPrepareCall(callNode, sourceFile);
|
|
599
|
-
const addDestroyOnResponse = shouldAddDestroyOnResponse(queryVariableName, pollingInfo, isDiscardedPrepareCall(callNode));
|
|
706
|
+
const addDestroyOnResponse = shouldAddDestroyOnResponse(queryVariableName, pollingInfo, isDiscardedPrepareCall(callNode), isOneShotLocalVariable(callNode) || isInsideAsyncValidator(callNode));
|
|
600
707
|
if (existingConfig && ts.isObjectLiteralExpression(existingConfig.initializer)) {
|
|
601
708
|
const configProperties = [];
|
|
602
709
|
existingConfig.initializer.properties.forEach((property) => {
|
|
@@ -685,9 +792,9 @@ const reportManualReviewLocations = (tree, allUsages, report) => {
|
|
|
685
792
|
visit(sourceFile);
|
|
686
793
|
if (foundFunctionWithoutInject) {
|
|
687
794
|
report.addManualReview({
|
|
688
|
-
title: `Review standalone
|
|
689
|
-
summary: `${usage.creatorName}.
|
|
690
|
-
action: '
|
|
795
|
+
title: `Review standalone ${usage.method}() usage for ${usage.creatorName}`,
|
|
796
|
+
summary: `${usage.creatorName}.${usage.method}() is used in a standalone function without an obvious injection context.`,
|
|
797
|
+
action: 'If the factory that returns this function runs in an injection context, add `const injector = inject(Injector);` at the top of the factory and pass `{ injector }` to the call. Otherwise pass an Injector from the caller, move the call into queryComputed(), or add provideLegacyPrepareFallback() to the app providers.',
|
|
691
798
|
locations: [{ filePath: usage.filePath, line: usage.line }],
|
|
692
799
|
source: 'legacy-prepare-migration',
|
|
693
800
|
dedupeKey: `manual-review:${usage.filePath}:${usage.line}:${usage.creatorName}`,
|
|
@@ -697,7 +804,7 @@ const reportManualReviewLocations = (tree, allUsages, report) => {
|
|
|
697
804
|
if (usage.context === 'unknown') {
|
|
698
805
|
report.addManualReview({
|
|
699
806
|
title: `Verify execution context for ${usage.creatorName}`,
|
|
700
|
-
summary: `The migration could not classify the execution context of ${usage.creatorName}.
|
|
807
|
+
summary: `The migration could not classify the execution context of ${usage.creatorName}.${usage.method}().`,
|
|
701
808
|
action: 'Inspect the surrounding code and confirm that the generated injector handling and destroyOnResponse behavior are correct.',
|
|
702
809
|
locations: [{ filePath: usage.filePath, line: usage.line }],
|
|
703
810
|
source: 'legacy-prepare-migration',
|
|
@@ -851,12 +958,153 @@ const isDiscardedPrepareCall = (callNode) => {
|
|
|
851
958
|
}
|
|
852
959
|
return false;
|
|
853
960
|
};
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
961
|
+
const unwrapChainEnd = (callNode) => {
|
|
962
|
+
let current = callNode;
|
|
963
|
+
while (current.parent) {
|
|
964
|
+
const parent = current.parent;
|
|
965
|
+
if (ts.isParenthesizedExpression(parent) || ts.isNonNullExpression(parent)) {
|
|
966
|
+
current = parent;
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
const call = parent.parent;
|
|
970
|
+
if (ts.isPropertyAccessExpression(parent) &&
|
|
971
|
+
parent.expression === current &&
|
|
972
|
+
parent.name.text === 'execute' &&
|
|
973
|
+
call &&
|
|
974
|
+
ts.isCallExpression(call) &&
|
|
975
|
+
call.expression === parent) {
|
|
976
|
+
current = call;
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
break;
|
|
980
|
+
}
|
|
981
|
+
return current;
|
|
982
|
+
};
|
|
983
|
+
/**
|
|
984
|
+
* The Angular `computed(() => legacy.prepare(...).execute())` whose callback returns the prepared query.
|
|
985
|
+
* Such a computed builds one unmanaged query per input change, so it becomes `queryComputed`, whose
|
|
986
|
+
* container destroys the superseded one.
|
|
987
|
+
*/
|
|
988
|
+
const findReturningPlainComputed = (callNode) => {
|
|
989
|
+
const chainEnd = unwrapChainEnd(callNode);
|
|
990
|
+
const callback = chainEnd.parent;
|
|
991
|
+
if (!callback || !ts.isArrowFunction(callback) || callback.body !== chainEnd) {
|
|
992
|
+
const returnStatement = chainEnd.parent;
|
|
993
|
+
if (!returnStatement || !ts.isReturnStatement(returnStatement))
|
|
994
|
+
return undefined;
|
|
995
|
+
let fn = returnStatement.parent;
|
|
996
|
+
while (fn && !isCallbackFunction(fn))
|
|
997
|
+
fn = fn.parent;
|
|
998
|
+
return fn && isCallbackFunction(fn) ? getPlainComputedHost(fn) : undefined;
|
|
999
|
+
}
|
|
1000
|
+
return getPlainComputedHost(callback);
|
|
1001
|
+
};
|
|
1002
|
+
const getPlainComputedHost = (node) => {
|
|
1003
|
+
const host = getCallbackHost(node);
|
|
1004
|
+
if (!host || !ts.isIdentifier(host.expression) || host.expression.text !== 'computed' || host.arguments[0] !== node) {
|
|
1005
|
+
return undefined;
|
|
1006
|
+
}
|
|
1007
|
+
return host;
|
|
1008
|
+
};
|
|
1009
|
+
const ASYNC_VALIDATOR_PROPERTIES = new Set(['asyncValidator', 'asyncValidators']);
|
|
1010
|
+
const ASYNC_VALIDATOR_SETTERS = new Set(['setAsyncValidators', 'addAsyncValidators']);
|
|
1011
|
+
/**
|
|
1012
|
+
* Whether a function around the call is an Angular async validator: a value of an `asyncValidator(s)` property or
|
|
1013
|
+
* setter argument, an `AsyncValidatorFn` or a factory of one, a function returning `Observable`/`Promise` of
|
|
1014
|
+
* `ValidationErrors`, or `validate` of an `AsyncValidator`.
|
|
1015
|
+
*/
|
|
1016
|
+
const isInsideAsyncValidator = (callNode) => {
|
|
1017
|
+
for (let node = callNode.parent; node; node = node.parent) {
|
|
1018
|
+
if (ts.isMethodDeclaration(node))
|
|
1019
|
+
return isAsyncValidatorMethod(node);
|
|
1020
|
+
if (isCallbackFunction(node) && isAsyncValidatorFunction(node))
|
|
1021
|
+
return true;
|
|
1022
|
+
}
|
|
1023
|
+
return false;
|
|
1024
|
+
};
|
|
1025
|
+
const isAsyncValidatorMethod = (method) => {
|
|
1026
|
+
const owner = method.parent;
|
|
1027
|
+
return (ts.isIdentifier(method.name) &&
|
|
1028
|
+
method.name.text === 'validate' &&
|
|
1029
|
+
ts.isClassDeclaration(owner) &&
|
|
1030
|
+
!!owner.heritageClauses?.some((clause) => clause.types.some((type) => type.expression.getText() === 'AsyncValidator')));
|
|
1031
|
+
};
|
|
1032
|
+
const ASYNC_VALIDATOR_SIGNATURE = /\bAsyncValidatorFn\b|\b(?:Observable|Promise)<\s*ValidationErrors\b/;
|
|
1033
|
+
const isAsyncValidatorFunction = (fn) => {
|
|
1034
|
+
if (fn.type && ASYNC_VALIDATOR_SIGNATURE.test(fn.type.getText()))
|
|
1035
|
+
return true;
|
|
1036
|
+
let value = fn;
|
|
1037
|
+
if (ts.isArrayLiteralExpression(value.parent))
|
|
1038
|
+
value = value.parent;
|
|
1039
|
+
const parent = value.parent;
|
|
1040
|
+
if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name)) {
|
|
1041
|
+
return ASYNC_VALIDATOR_PROPERTIES.has(parent.name.text);
|
|
1042
|
+
}
|
|
1043
|
+
if (ts.isCallExpression(parent) && ts.isPropertyAccessExpression(parent.expression)) {
|
|
1044
|
+
return ASYNC_VALIDATOR_SETTERS.has(parent.expression.name.text);
|
|
1045
|
+
}
|
|
1046
|
+
if ((ts.isVariableDeclaration(parent) || ts.isPropertyDeclaration(parent)) && parent.type) {
|
|
1047
|
+
return parent.type.getText().includes('AsyncValidatorFn');
|
|
1048
|
+
}
|
|
1049
|
+
return false;
|
|
1050
|
+
};
|
|
1051
|
+
const ONE_SHOT_QUERY_MEMBERS = ['execute', 'onSuccess', 'onFailure'];
|
|
1052
|
+
const isOneShotLocalVariable = (callNode) => {
|
|
1053
|
+
let chainEnd = callNode;
|
|
1054
|
+
let executeCalls = 0;
|
|
1055
|
+
while (chainEnd.parent) {
|
|
1056
|
+
const parent = chainEnd.parent;
|
|
1057
|
+
if (ts.isPropertyAccessExpression(parent) && parent.expression === chainEnd) {
|
|
1058
|
+
if (!ONE_SHOT_QUERY_MEMBERS.includes(parent.name.text))
|
|
1059
|
+
return false;
|
|
1060
|
+
if (parent.name.text === 'execute')
|
|
1061
|
+
executeCalls++;
|
|
1062
|
+
chainEnd = parent;
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
if (ts.isCallExpression(parent) && parent.expression === chainEnd) {
|
|
1066
|
+
chainEnd = parent;
|
|
1067
|
+
continue;
|
|
1068
|
+
}
|
|
1069
|
+
break;
|
|
1070
|
+
}
|
|
1071
|
+
const declaration = chainEnd.parent;
|
|
1072
|
+
if (!declaration ||
|
|
1073
|
+
!ts.isVariableDeclaration(declaration) ||
|
|
1074
|
+
declaration.initializer !== chainEnd ||
|
|
1075
|
+
!ts.isIdentifier(declaration.name)) {
|
|
1076
|
+
return false;
|
|
859
1077
|
}
|
|
860
|
-
|
|
1078
|
+
const scope = declaration.parent.parent.parent;
|
|
1079
|
+
if (!ts.isVariableStatement(declaration.parent.parent) || !ts.isBlock(scope))
|
|
1080
|
+
return false;
|
|
1081
|
+
const variableName = declaration.name.text;
|
|
1082
|
+
let isOneShot = executeCalls <= 1;
|
|
1083
|
+
const visit = (node) => {
|
|
1084
|
+
if (!isOneShot)
|
|
1085
|
+
return;
|
|
1086
|
+
if (ts.isIdentifier(node) && node.text === variableName && node !== declaration.name) {
|
|
1087
|
+
const parent = node.parent;
|
|
1088
|
+
if (ts.isPropertyAccessExpression(parent) &&
|
|
1089
|
+
parent.expression === node &&
|
|
1090
|
+
ONE_SHOT_QUERY_MEMBERS.includes(parent.name.text)) {
|
|
1091
|
+
if (parent.name.text === 'execute')
|
|
1092
|
+
executeCalls++;
|
|
1093
|
+
if (executeCalls > 1)
|
|
1094
|
+
isOneShot = false;
|
|
1095
|
+
}
|
|
1096
|
+
else if (!(ts.isPropertyAccessExpression(parent) && parent.name === node)) {
|
|
1097
|
+
isOneShot = false;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
ts.forEachChild(node, visit);
|
|
1101
|
+
};
|
|
1102
|
+
visit(scope);
|
|
1103
|
+
return isOneShot;
|
|
1104
|
+
};
|
|
1105
|
+
const shouldAddDestroyOnResponse = (queryVariableName, pollingInfo, isDiscarded, isOneShotLocal) => {
|
|
1106
|
+
if (queryVariableName && pollingInfo.get(queryVariableName)?.hasPolling)
|
|
1107
|
+
return false;
|
|
1108
|
+
return isDiscarded || isOneShotLocal;
|
|
861
1109
|
};
|
|
862
1110
|
//# sourceMappingURL=legacy-prepare-migration.js.map
|