@ethlete/query 6.0.0-next.6 → 6.0.0-next.61

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.
Files changed (54) hide show
  1. package/CHANGELOG.md +945 -0
  2. package/fesm2022/ethlete-query-devtools-contract.mjs +1 -0
  3. package/fesm2022/ethlete-query-ngrx-toolkit.mjs +411 -0
  4. package/fesm2022/ethlete-query-ngrx-toolkit.mjs.map +1 -0
  5. package/fesm2022/ethlete-query-testing.mjs +548 -169
  6. package/fesm2022/ethlete-query-testing.mjs.map +1 -1
  7. package/fesm2022/ethlete-query.mjs +14680 -7172
  8. package/fesm2022/ethlete-query.mjs.map +1 -1
  9. package/generators/deprecate-legacy-queries/migration.js +85 -0
  10. package/generators/deprecate-legacy-queries/schema.json +26 -0
  11. package/generators/generators.json +30 -0
  12. package/generators/migrate-from-ngrx-toolkit/args-assignability.js +140 -0
  13. package/generators/migrate-from-ngrx-toolkit/consumers.js +521 -0
  14. package/generators/migrate-from-ngrx-toolkit/creators.js +301 -0
  15. package/generators/migrate-from-ngrx-toolkit/facade.js +295 -0
  16. package/generators/migrate-from-ngrx-toolkit/feature.js +513 -0
  17. package/generators/migrate-from-ngrx-toolkit/migration.js +140 -0
  18. package/generators/migrate-from-ngrx-toolkit/report.js +154 -0
  19. package/generators/migrate-from-ngrx-toolkit/schema.json +42 -0
  20. package/generators/migrate-from-ngrx-toolkit/store-removal.js +633 -0
  21. package/generators/migrate-from-ngrx-toolkit/ts-edits.js +128 -0
  22. package/generators/migrate-query-client-features/migration.js +144 -0
  23. package/generators/migrate-query-client-features/report.js +136 -0
  24. package/generators/migrate-query-client-features/schema.json +26 -0
  25. package/generators/migrate-query-opt-in-features/migration.js +195 -0
  26. package/generators/migrate-query-opt-in-features/report.js +143 -0
  27. package/generators/migrate-query-opt-in-features/schema.json +31 -0
  28. package/generators/migrate-to-query-v3/auth-provider-scaffold.js +160 -0
  29. package/generators/migrate-to-query-v3/cleanup-migration.js +247 -125
  30. package/generators/migrate-to-query-v3/http-client-check.js +93 -0
  31. package/generators/migrate-to-query-v3/legacy-prepare-migration.js +129 -40
  32. package/generators/migrate-to-query-v3/legacy-query-creator-migration.js +274 -152
  33. package/generators/migrate-to-query-v3/migration-scope.js +40 -0
  34. package/generators/migrate-to-query-v3/migration.js +19 -10
  35. package/generators/migrate-to-query-v3/module-graph.js +165 -0
  36. package/generators/migrate-to-query-v3/query-client-migration.js +342 -160
  37. package/generators/migrate-to-query-v3/rename-symbols.js +233 -0
  38. package/generators/migrate-to-query-v3/report.js +40 -7
  39. package/generators/migrate-to-query-v3/schema.json +12 -0
  40. package/generators/migrate-to-query-v3/shared.js +97 -3
  41. package/generators/move-devtools-contract-imports/migration.js +155 -0
  42. package/generators/move-devtools-contract-imports/moved-names.js +153 -0
  43. package/generators/move-devtools-contract-imports/schema.json +26 -0
  44. package/generators/prep-for-query-v3/migration.js +465 -65
  45. package/generators/report-legacy-query-apis/migration.js +256 -0
  46. package/generators/report-legacy-query-apis/schema.json +25 -0
  47. package/migrations/query-field-default-types.md +43 -0
  48. package/migrations/search-query-field-string.md +43 -0
  49. package/migrations.json +87 -0
  50. package/package.json +34 -14
  51. package/types/ethlete-query-devtools-contract.d.ts +2 -0
  52. package/types/ethlete-query-ngrx-toolkit.d.ts +131 -0
  53. package/types/ethlete-query-testing.d.ts +295 -160
  54. package/types/ethlete-query.d.ts +9755 -3682
@@ -1,9 +1,13 @@
1
- import { formatFiles, visitNotIgnoredFiles } from '@nx/devkit';
1
+ import { formatFiles, joinPathFragments, readJson, visitNotIgnoredFiles } from '@nx/devkit';
2
2
  import * as ts from 'typescript';
3
+ import { REMOVED_EXPERIMENTAL_QUERY_HELPERS, createSourceFile, findRemovedExperimentalQueryHelpers, } from '../migrate-to-query-v3/shared.js';
3
4
  export default async function migrate(tree, schema) {
4
5
  console.log('\nšŸ”„ Starting Query v3 symbol conflict migration...');
5
6
  renameConflictingSymbols(tree);
6
7
  replaceExperimentalQueryNamespace(tree);
8
+ reportWithArgsNullReturns(tree);
9
+ replaceClearQueryArgs(tree);
10
+ reportPrebuiltPackagesImportingRenamedSymbols(tree);
7
11
  if (!schema.skipFormat) {
8
12
  await formatFiles(tree);
9
13
  }
@@ -58,7 +62,7 @@ function renameConflictingSymbols(tree) {
58
62
  // Log statistics
59
63
  if (updatedFiles.length > 0) {
60
64
  console.log(`\nšŸ“Š Renamed symbols in ${updatedFiles.length} files:`);
61
- const hasRenames = Array.from(symbolUsageCounts.entries()).filter(([_, count]) => count > 0);
65
+ const hasRenames = Array.from(symbolUsageCounts.entries()).filter(([, count]) => count > 0);
62
66
  if (hasRenames.length > 0) {
63
67
  console.log('\n Symbol usage counts:');
64
68
  hasRenames.forEach(([symbol, count]) => {
@@ -73,64 +77,53 @@ function renameConflictingSymbols(tree) {
73
77
  }
74
78
  function renameSymbolsInFile(content, filePath, symbolUsageCounts) {
75
79
  const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
80
+ const checker = createBindingChecker(sourceFile);
76
81
  const replacements = [];
77
- // Track which symbols are imported from @ethlete/query
78
- const importedSymbols = new Set();
79
- function visit(node) {
80
- // Track imports from @ethlete/query
81
- if (ts.isImportDeclaration(node)) {
82
- const moduleSpecifier = node.moduleSpecifier;
83
- if (ts.isStringLiteral(moduleSpecifier) && moduleSpecifier.text === '@ethlete/query') {
84
- const namedBindings = node.importClause?.namedBindings;
85
- if (namedBindings && ts.isNamedImports(namedBindings)) {
86
- namedBindings.elements.forEach((element) => {
87
- const importedName = element.propertyName?.text || element.name.text;
88
- if (TYPE_RENAMES.has(importedName) || FUNCTION_RENAMES.has(importedName)) {
89
- importedSymbols.add(importedName);
90
- }
91
- });
92
- }
93
- }
94
- }
95
- // Rename identifiers that match our rename maps
96
- if (ts.isIdentifier(node)) {
97
- const name = node.text;
98
- const parent = node.parent;
99
- // Only rename if this symbol was imported from @ethlete/query
100
- if (!importedSymbols.has(name)) {
101
- ts.forEachChild(node, visit);
102
- return;
103
- }
104
- const newName = TYPE_RENAMES.get(name) || FUNCTION_RENAMES.get(name);
105
- if (!newName) {
106
- ts.forEachChild(node, visit);
107
- return;
108
- }
109
- // Skip if it's the imported name in an import specifier (we'll handle that separately)
110
- if (ts.isImportSpecifier(parent) && (parent.propertyName === node || parent.name === node)) {
111
- ts.forEachChild(node, visit);
112
- return;
82
+ const namespaceAliases = collectQueryNamespaceAliases(sourceFile);
83
+ const renamedImports = new Map();
84
+ sourceFile.statements.forEach((statement) => {
85
+ getQueryNamedImports(statement)?.elements.forEach((element) => {
86
+ const newName = TYPE_RENAMES.get(element.name.text) || FUNCTION_RENAMES.get(element.name.text);
87
+ const symbol = checker.getSymbolAtLocation(element.name);
88
+ if (!element.propertyName && newName && symbol) {
89
+ renamedImports.set(symbol, { oldName: element.name.text, newName });
113
90
  }
114
- // Rename the identifier
91
+ });
92
+ });
93
+ function visit(node) {
94
+ const namespaceMember = getNamespaceMember(node, namespaceAliases)?.member;
95
+ const renamedMember = namespaceMember && (TYPE_RENAMES.get(namespaceMember.text) || FUNCTION_RENAMES.get(namespaceMember.text));
96
+ if (namespaceMember && renamedMember) {
115
97
  replacements.push({
116
- start: node.getStart(sourceFile),
117
- end: node.getEnd(),
118
- replacement: newName,
119
- oldName: name,
98
+ start: namespaceMember.getStart(sourceFile),
99
+ end: namespaceMember.getEnd(),
100
+ replacement: renamedMember,
101
+ oldName: namespaceMember.text,
120
102
  });
121
103
  }
104
+ if (ts.isIdentifier(node) && !ts.isImportSpecifier(node.parent)) {
105
+ const symbol = resolveReferenceSymbol(checker, node);
106
+ const rename = symbol && renamedImports.get(symbol);
107
+ if (rename) {
108
+ replacements.push({
109
+ start: node.getStart(sourceFile),
110
+ end: node.getEnd(),
111
+ replacement: ts.isShorthandPropertyAssignment(node.parent)
112
+ ? `${rename.oldName}: ${rename.newName}`
113
+ : rename.newName,
114
+ oldName: rename.oldName,
115
+ });
116
+ }
117
+ }
122
118
  ts.forEachChild(node, visit);
123
119
  }
124
120
  visit(sourceFile);
125
- // Update imports in @ethlete/query import declarations
126
- const importReplacements = updateImports(sourceFile, content);
121
+ const importReplacements = updateImports(sourceFile);
127
122
  replacements.push(...importReplacements);
128
- // Count usages
129
123
  replacements.forEach((r) => {
130
124
  const count = symbolUsageCounts.get(r.oldName) || 0;
131
125
  symbolUsageCounts.set(r.oldName, count + 1);
132
126
  });
133
- // Apply replacements in reverse order
134
127
  let result = content;
135
128
  replacements.sort((a, b) => b.start - a.start);
136
129
  for (const { start, end, replacement } of replacements) {
@@ -138,7 +131,54 @@ function renameSymbolsInFile(content, filePath, symbolUsageCounts) {
138
131
  }
139
132
  return result;
140
133
  }
141
- function updateImports(sourceFile, content) {
134
+ function createBindingChecker(sourceFile) {
135
+ const options = { noLib: true, noResolve: true, types: [] };
136
+ const host = ts.createCompilerHost(options);
137
+ host.getSourceFile = (fileName) => (fileName === sourceFile.fileName ? sourceFile : undefined);
138
+ host.fileExists = (fileName) => fileName === sourceFile.fileName;
139
+ host.readFile = (fileName) => (fileName === sourceFile.fileName ? sourceFile.text : undefined);
140
+ return ts.createProgram([sourceFile.fileName], options, host).getTypeChecker();
141
+ }
142
+ function resolveReferenceSymbol(checker, identifier) {
143
+ const parent = identifier.parent;
144
+ if (ts.isShorthandPropertyAssignment(parent) && parent.name === identifier) {
145
+ return checker.getShorthandAssignmentValueSymbol(parent);
146
+ }
147
+ if (ts.isExportSpecifier(parent)) {
148
+ return (parent.propertyName ?? parent.name) === identifier && !parent.parent.parent.moduleSpecifier
149
+ ? checker.getExportSpecifierLocalTargetSymbol(parent)
150
+ : undefined;
151
+ }
152
+ return checker.getSymbolAtLocation(identifier);
153
+ }
154
+ function collectQueryNamespaceAliases(sourceFile) {
155
+ const aliases = new Set();
156
+ sourceFile.statements.forEach((statement) => {
157
+ if (!ts.isImportDeclaration(statement)) {
158
+ return;
159
+ }
160
+ const namedBindings = statement.importClause?.namedBindings;
161
+ if (namedBindings &&
162
+ ts.isNamespaceImport(namedBindings) &&
163
+ ts.isStringLiteral(statement.moduleSpecifier) &&
164
+ statement.moduleSpecifier.text === '@ethlete/query') {
165
+ aliases.add(namedBindings.name.text);
166
+ }
167
+ });
168
+ return aliases;
169
+ }
170
+ function getNamespaceMember(node, namespaceAliases) {
171
+ const [namespace, member] = ts.isPropertyAccessExpression(node)
172
+ ? [node.expression, node.name]
173
+ : ts.isQualifiedName(node)
174
+ ? [node.left, node.right]
175
+ : [];
176
+ if (!namespace || !member || !ts.isIdentifier(namespace) || !ts.isIdentifier(member)) {
177
+ return undefined;
178
+ }
179
+ return namespaceAliases.has(namespace.text) ? { namespace, member } : undefined;
180
+ }
181
+ function updateImports(sourceFile) {
142
182
  const replacements = [];
143
183
  function visit(node) {
144
184
  if (ts.isImportDeclaration(node)) {
@@ -184,6 +224,7 @@ function updateImports(sourceFile, content) {
184
224
  //#region ExperimentalQuery namespace replacement
185
225
  function replaceExperimentalQueryNamespace(tree) {
186
226
  const updatedFiles = [];
227
+ const removedHelpers = [];
187
228
  let totalReplacements = 0;
188
229
  visitNotIgnoredFiles(tree, '', (filePath) => {
189
230
  if (!filePath.endsWith('.ts') || filePath.endsWith('.spec.ts'))
@@ -200,7 +241,13 @@ function replaceExperimentalQueryNamespace(tree) {
200
241
  updatedFiles.push(filePath);
201
242
  totalReplacements += result.replacementCount;
202
243
  }
244
+ findRemovedExperimentalQueryHelpers(createSourceFile(result.content, filePath)).forEach(({ name, line }) => {
245
+ removedHelpers.push(` - ${filePath}:${line} ${name}: ${REMOVED_EXPERIMENTAL_QUERY_HELPERS[name]}`);
246
+ });
203
247
  });
248
+ if (removedHelpers.length > 0) {
249
+ console.warn(`\nāš ļø These ExperimentalQuery helpers no longer exist in v3 and were left for you to replace by hand (migrate-to-query-v3 lists them again in its report):\n${removedHelpers.join('\n')}`);
250
+ }
204
251
  if (updatedFiles.length > 0) {
205
252
  console.log(`\nšŸ“¦ Replaced ExperimentalQuery namespace in ${updatedFiles.length} files (${totalReplacements} usages)`);
206
253
  }
@@ -232,18 +279,19 @@ function replaceNamespaceInFile(content, filePath) {
232
279
  ts.forEachChild(node, collectImport);
233
280
  }
234
281
  collectImport(sourceFile);
282
+ const namespaceAliases = collectQueryNamespaceAliases(sourceFile);
283
+ const namespaceReplacements = collectNamespacedExperimentalQueryReplacements(sourceFile, namespaceAliases);
235
284
  // If no ExperimentalQuery import found, return unchanged
236
285
  if (!hasExperimentalQueryImport || !experimentalQueryAlias) {
237
- return { content, modified: false, replacementCount: 0 };
286
+ return applyReplacements(content, namespaceReplacements, namespaceReplacements.length);
238
287
  }
288
+ const getAliasMember = (node) => getNamespaceMember(node, new Set([experimentalQueryAlias]))?.member;
239
289
  // Second pass: collect used symbols
240
290
  function findUsages(node) {
241
291
  // Find usages: ExperimentalQuery.someSymbol or E.someSymbol (if aliased)
242
- if (ts.isPropertyAccessExpression(node) &&
243
- ts.isIdentifier(node.expression) &&
244
- node.expression.text === experimentalQueryAlias) {
245
- const symbolName = node.name.text;
246
- usedSymbols.add(symbolName);
292
+ const member = getAliasMember(node);
293
+ if (member) {
294
+ usedSymbols.add(member.text);
247
295
  }
248
296
  ts.forEachChild(node, findUsages);
249
297
  }
@@ -301,29 +349,381 @@ function replaceNamespaceInFile(content, filePath) {
301
349
  }
302
350
  }
303
351
  // Replace usages: ExperimentalQuery.someSymbol → someSymbol
304
- if (ts.isPropertyAccessExpression(node) &&
305
- ts.isIdentifier(node.expression) &&
306
- node.expression.text === experimentalQueryAlias) {
352
+ const member = getAliasMember(node);
353
+ if (member) {
307
354
  replacements.push({
308
355
  start: node.getStart(sourceFile),
309
356
  end: node.getEnd(),
310
- replacement: node.name.text,
357
+ replacement: member.text,
311
358
  });
312
359
  }
313
360
  ts.forEachChild(node, createReplacements);
314
361
  }
315
362
  createReplacements(sourceFile);
316
- // Apply replacements in reverse order
363
+ // Subtract 1 for the import replacement
364
+ return applyReplacements(content, [...replacements, ...namespaceReplacements], replacements.length - 1 + namespaceReplacements.length);
365
+ }
366
+ function collectNamespacedExperimentalQueryReplacements(sourceFile, namespaceAliases) {
367
+ const replacements = [];
368
+ if (namespaceAliases.size === 0) {
369
+ return replacements;
370
+ }
371
+ function visit(node) {
372
+ const access = getNamespaceMember(node, namespaceAliases);
373
+ if (access?.member.text === 'ExperimentalQuery') {
374
+ replacements.push({ start: node.getStart(sourceFile), end: node.getEnd(), replacement: access.namespace.text });
375
+ }
376
+ ts.forEachChild(node, visit);
377
+ }
378
+ visit(sourceFile);
379
+ return replacements;
380
+ }
381
+ function applyReplacements(content, replacements, replacementCount) {
317
382
  let result = content;
318
- replacements.sort((a, b) => b.start - a.start);
319
- for (const { start, end, replacement } of replacements) {
383
+ [...replacements]
384
+ .sort((a, b) => b.start - a.start)
385
+ .forEach(({ start, end, replacement }) => {
320
386
  result = result.slice(0, start) + replacement + result.slice(end);
387
+ });
388
+ return { content: result, modified: replacements.length > 0, replacementCount };
389
+ }
390
+ //#endregion
391
+ //#region withArgs null returns
392
+ function reportWithArgsNullReturns(tree) {
393
+ const sites = [];
394
+ const uncheckedSites = [];
395
+ visitQueryFiles(tree, ['withArgs'], (filePath, content) => {
396
+ const { nullLines, uncheckedLines } = findWithArgsNullReturns(createSourceFile(content, filePath));
397
+ nullLines.forEach((line) => sites.push(` - ${filePath}:${line}`));
398
+ uncheckedLines.forEach((line) => uncheckedSites.push(` - ${filePath}:${line}`));
399
+ });
400
+ if (sites.length > 0) {
401
+ console.warn(`\nāš ļø These withArgs callbacks return null. Before v3, null kept the previous args; v3 parks the query instead (args, response and executionState become null, and polling pauses). Check each one: a null that should park the query is fine, one that relied on keeping the previous args must now return those args itself:\n${sites.join('\n')}`);
321
402
  }
322
- return {
323
- content: result,
324
- modified: replacements.length > 0,
325
- replacementCount: replacements.length - 1, // Subtract 1 for the import replacement
403
+ if (uncheckedSites.length > 0) {
404
+ console.warn(`\nāš ļø TODO: These withArgs callbacks are passed by reference from somewhere this file does not declare, so they could not be checked for a null return. Check each one by hand - v3 parks the query on null, where earlier versions kept the previous args:\n${uncheckedSites.join('\n')}`);
405
+ }
406
+ }
407
+ const isCheckableFunction = (node) => !!node &&
408
+ (ts.isArrowFunction(node) ||
409
+ ts.isFunctionExpression(node) ||
410
+ ts.isFunctionDeclaration(node) ||
411
+ ts.isMethodDeclaration(node)) &&
412
+ !!node.body;
413
+ function resolveCallbackReference(checker, source) {
414
+ const name = ts.isPropertyAccessExpression(source) ? source.name : source;
415
+ if (!ts.isIdentifier(name))
416
+ return undefined;
417
+ const declaration = checker.getSymbolAtLocation(name)?.valueDeclaration;
418
+ if (isCheckableFunction(declaration))
419
+ return declaration;
420
+ if (!declaration || !(ts.isVariableDeclaration(declaration) || ts.isPropertyDeclaration(declaration))) {
421
+ return undefined;
422
+ }
423
+ const initializer = declaration.initializer && skipOuterExpressions(declaration.initializer);
424
+ if (isCheckableFunction(initializer))
425
+ return initializer;
426
+ if (initializer &&
427
+ ts.isCallExpression(initializer) &&
428
+ ts.isIdentifier(initializer.expression) &&
429
+ initializer.expression.text === 'computed') {
430
+ const [computation] = initializer.arguments;
431
+ return isCheckableFunction(computation) ? computation : undefined;
432
+ }
433
+ return undefined;
434
+ }
435
+ function skipOuterExpressions(expression) {
436
+ return ts.isParenthesizedExpression(expression) ||
437
+ ts.isAsExpression(expression) ||
438
+ ts.isSatisfiesExpression(expression)
439
+ ? skipOuterExpressions(expression.expression)
440
+ : expression;
441
+ }
442
+ function findWithArgsNullReturns(sourceFile) {
443
+ const checker = createBindingChecker(sourceFile);
444
+ const withArgsImports = new Set();
445
+ const namespaceAliases = collectQueryNamespaceAliases(sourceFile);
446
+ const nullLines = [];
447
+ const uncheckedLines = [];
448
+ const lineOf = (node) => sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
449
+ sourceFile.statements.forEach((statement) => {
450
+ getQueryNamedImports(statement)?.elements.forEach((element) => {
451
+ const symbol = checker.getSymbolAtLocation(element.name);
452
+ if ((element.propertyName ?? element.name).text === 'withArgs' && symbol)
453
+ withArgsImports.add(symbol);
454
+ });
455
+ });
456
+ const isWithArgsCallee = (callee) => {
457
+ if (ts.isIdentifier(callee)) {
458
+ const symbol = checker.getSymbolAtLocation(callee);
459
+ return !!symbol && withArgsImports.has(symbol);
460
+ }
461
+ return getNamespaceMember(callee, namespaceAliases)?.member.text === 'withArgs';
326
462
  };
463
+ function visit(node) {
464
+ if (ts.isCallExpression(node) && isWithArgsCallee(node.expression)) {
465
+ const [source] = node.arguments;
466
+ const callback = source && (isCheckableFunction(source) ? source : resolveCallbackReference(checker, source));
467
+ if (callback) {
468
+ collectNullReturns(callback).forEach((nullNode) => nullLines.push(lineOf(nullNode)));
469
+ }
470
+ else if (source) {
471
+ uncheckedLines.push(lineOf(source));
472
+ }
473
+ }
474
+ ts.forEachChild(node, visit);
475
+ }
476
+ visit(sourceFile);
477
+ return { nullLines: [...new Set(nullLines)].sort((a, b) => a - b), uncheckedLines };
478
+ }
479
+ function collectNullReturns(fn) {
480
+ if (!ts.isBlock(fn.body)) {
481
+ return findNullResults(fn.body);
482
+ }
483
+ const found = [];
484
+ function visit(node) {
485
+ if (ts.isFunctionLike(node))
486
+ return;
487
+ if (ts.isReturnStatement(node) && node.expression) {
488
+ found.push(...findNullResults(node.expression));
489
+ }
490
+ ts.forEachChild(node, visit);
491
+ }
492
+ ts.forEachChild(fn.body, visit);
493
+ return found;
494
+ }
495
+ function findNullResults(expression) {
496
+ if (expression.kind === ts.SyntaxKind.NullKeyword)
497
+ return [expression];
498
+ if (ts.isParenthesizedExpression(expression) ||
499
+ ts.isAsExpression(expression) ||
500
+ ts.isSatisfiesExpression(expression) ||
501
+ ts.isTypeAssertionExpression(expression)) {
502
+ return findNullResults(expression.expression);
503
+ }
504
+ if (ts.isConditionalExpression(expression)) {
505
+ return [...findNullResults(expression.whenTrue), ...findNullResults(expression.whenFalse)];
506
+ }
507
+ if (ts.isBinaryExpression(expression) &&
508
+ [ts.SyntaxKind.QuestionQuestionToken, ts.SyntaxKind.BarBarToken, ts.SyntaxKind.AmpersandAmpersandToken].includes(expression.operatorToken.kind)) {
509
+ return findNullResults(expression.right);
510
+ }
511
+ return [];
512
+ }
513
+ //#endregion
514
+ //#region CLEAR_QUERY_ARGS replacement
515
+ const CLEAR_QUERY_ARGS_NAMES = new Set(['CLEAR_QUERY_ARGS', 'ClearQueryArgs']);
516
+ function replaceClearQueryArgs(tree) {
517
+ const updatedFiles = [];
518
+ const reExports = [];
519
+ visitQueryFiles(tree, [...CLEAR_QUERY_ARGS_NAMES], (filePath, content) => {
520
+ const result = replaceClearQueryArgsInFile(content, filePath);
521
+ if (result.content !== content) {
522
+ tree.write(filePath, result.content);
523
+ updatedFiles.push(filePath);
524
+ }
525
+ result.reExportLines.forEach((line) => reExports.push(` - ${filePath}:${line}`));
526
+ });
527
+ if (updatedFiles.length > 0) {
528
+ console.log(`\nšŸ…æļø Replaced CLEAR_QUERY_ARGS with null (the v3 park value) in ${updatedFiles.length} files: ${updatedFiles.join(', ')}`);
529
+ }
530
+ if (reExports.length > 0) {
531
+ console.warn(`\nāš ļø These files re-exported CLEAR_QUERY_ARGS or ClearQueryArgs. They now export a local \`null\` under the same name, so their importers keep compiling. Replace each import of it with \`null\`, then delete the export:\n${reExports.join('\n')}`);
532
+ }
533
+ }
534
+ function replaceClearQueryArgsInFile(content, filePath) {
535
+ const sourceFile = createSourceFile(content, filePath);
536
+ const checker = createBindingChecker(sourceFile);
537
+ const namespaceAliases = collectQueryNamespaceAliases(sourceFile);
538
+ const clearImports = new Map();
539
+ const replacements = [];
540
+ const reExportLines = [];
541
+ const statementEnd = (statement) => content[statement.getEnd()] === '\n' ? statement.getEnd() + 1 : statement.getEnd();
542
+ sourceFile.statements.forEach((statement) => {
543
+ const namedBindings = getQueryNamedImports(statement);
544
+ if (!namedBindings)
545
+ return;
546
+ const kept = namedBindings.elements.filter((element) => {
547
+ const importedName = (element.propertyName ?? element.name).text;
548
+ if (!CLEAR_QUERY_ARGS_NAMES.has(importedName))
549
+ return true;
550
+ const symbol = checker.getSymbolAtLocation(element.name);
551
+ if (symbol)
552
+ clearImports.set(symbol, importedName);
553
+ return false;
554
+ });
555
+ if (kept.length === namedBindings.elements.length)
556
+ return;
557
+ const importClause = statement.importClause;
558
+ if (kept.length === 0 && !importClause?.name) {
559
+ replacements.push({ start: statement.getStart(sourceFile), end: statementEnd(statement), replacement: '' });
560
+ return;
561
+ }
562
+ replacements.push({
563
+ start: namedBindings.getStart(sourceFile),
564
+ end: namedBindings.getEnd(),
565
+ replacement: `{ ${kept.map((element) => element.getText(sourceFile)).join(', ')} }`,
566
+ });
567
+ });
568
+ sourceFile.statements.forEach((statement) => {
569
+ if (!ts.isExportDeclaration(statement) || !statement.exportClause || !ts.isNamedExports(statement.exportClause)) {
570
+ return;
571
+ }
572
+ const fromQuery = isQueryModuleSpecifier(statement.moduleSpecifier);
573
+ if (statement.moduleSpecifier && !fromQuery)
574
+ return;
575
+ const clearedName = (element) => {
576
+ if (fromQuery) {
577
+ const exportedName = (element.propertyName ?? element.name).text;
578
+ return CLEAR_QUERY_ARGS_NAMES.has(exportedName) ? exportedName : undefined;
579
+ }
580
+ const symbol = checker.getExportSpecifierLocalTargetSymbol(element);
581
+ return symbol && clearImports.get(symbol);
582
+ };
583
+ const elements = statement.exportClause.elements;
584
+ const nullExports = elements.flatMap((element) => {
585
+ const name = clearedName(element);
586
+ if (!name)
587
+ return [];
588
+ return [`export ${name === 'ClearQueryArgs' ? 'type' : 'const'} ${element.name.text} = null;`];
589
+ });
590
+ if (nullExports.length === 0)
591
+ return;
592
+ reExportLines.push(sourceFile.getLineAndCharacterOfPosition(statement.getStart(sourceFile)).line + 1);
593
+ const kept = elements.filter((element) => !clearedName(element));
594
+ if (kept.length === 0) {
595
+ replacements.push({
596
+ start: statement.getStart(sourceFile),
597
+ end: statement.getEnd(),
598
+ replacement: nullExports.join('\n'),
599
+ });
600
+ return;
601
+ }
602
+ replacements.push({
603
+ start: statement.exportClause.getStart(sourceFile),
604
+ end: statement.exportClause.getEnd(),
605
+ replacement: `{ ${kept.map((element) => element.getText(sourceFile)).join(', ')} }`,
606
+ });
607
+ replacements.push({
608
+ start: statement.getEnd(),
609
+ end: statement.getEnd(),
610
+ replacement: `\n${nullExports.join('\n')}`,
611
+ });
612
+ });
613
+ function visit(node) {
614
+ const namespaceMember = getNamespaceMember(node, namespaceAliases)?.member;
615
+ const referencesImport = () => {
616
+ const symbol = ts.isIdentifier(node) ? resolveReferenceSymbol(checker, node) : undefined;
617
+ return !!symbol && clearImports.has(symbol);
618
+ };
619
+ if ((namespaceMember && CLEAR_QUERY_ARGS_NAMES.has(namespaceMember.text)) || referencesImport()) {
620
+ const target = ts.isTypeQueryNode(node.parent) ? node.parent : node;
621
+ const replacement = ts.isShorthandPropertyAssignment(node.parent) ? `${node.getText(sourceFile)}: null` : 'null';
622
+ replacements.push({ start: target.getStart(sourceFile), end: target.getEnd(), replacement });
623
+ return;
624
+ }
625
+ ts.forEachChild(node, visit);
626
+ }
627
+ sourceFile.statements.forEach((statement) => {
628
+ if (!ts.isImportDeclaration(statement) && !ts.isExportDeclaration(statement))
629
+ visit(statement);
630
+ });
631
+ return { content: applyReplacements(content, replacements, replacements.length).content, reExportLines };
632
+ }
633
+ function isQueryModuleSpecifier(node) {
634
+ return !!node && ts.isStringLiteral(node) && node.text === '@ethlete/query';
635
+ }
636
+ function visitQueryFiles(tree, markers, visitor) {
637
+ visitNotIgnoredFiles(tree, '', (filePath) => {
638
+ if (!filePath.endsWith('.ts') || filePath.endsWith('.spec.ts'))
639
+ return;
640
+ const content = tree.read(filePath, 'utf-8');
641
+ if (!content?.includes('@ethlete/query') || !markers.some((marker) => content.includes(marker)))
642
+ return;
643
+ visitor(filePath, content);
644
+ });
645
+ }
646
+ function getQueryNamedImports(statement) {
647
+ if (!ts.isImportDeclaration(statement) ||
648
+ !ts.isStringLiteral(statement.moduleSpecifier) ||
649
+ statement.moduleSpecifier.text !== '@ethlete/query') {
650
+ return undefined;
651
+ }
652
+ const namedBindings = statement.importClause?.namedBindings;
653
+ return namedBindings && ts.isNamedImports(namedBindings) ? namedBindings : undefined;
654
+ }
655
+ const isRenamedSymbol = (name) => TYPE_RENAMES.has(name) || FUNCTION_RENAMES.has(name);
656
+ function reportPrebuiltPackagesImportingRenamedSymbols(tree) {
657
+ if (!tree.exists('package.json'))
658
+ return;
659
+ const packageJson = readJson(tree, 'package.json');
660
+ const packageNames = new Set([packageJson.dependencies, packageJson.devDependencies, packageJson.optionalDependencies].flatMap((deps) => Object.keys(deps ?? {})));
661
+ const hits = [];
662
+ [...packageNames]
663
+ .filter((name) => !name.startsWith('@ethlete/'))
664
+ .sort()
665
+ .forEach((packageName) => {
666
+ const importedNames = new Set();
667
+ collectDeclarationFiles(tree, joinPathFragments('node_modules', packageName)).forEach((filePath) => {
668
+ const content = tree.read(filePath, 'utf-8');
669
+ if (!content?.includes('@ethlete/query'))
670
+ return;
671
+ findRenamedSymbolsInDeclaration(createSourceFile(content, filePath)).forEach((name) => importedNames.add(name));
672
+ });
673
+ if (importedNames.size > 0) {
674
+ hits.push(` - ${packageName}: ${[...importedNames].sort().join(', ')}`);
675
+ }
676
+ });
677
+ if (hits.length > 0) {
678
+ console.warn(`\nāš ļø These installed packages were built against @ethlete/query v2 and import names v3 no longer exports. No codemod can fix them here: run prep-for-query-v3 in each package's own repository and release a rebuild before upgrading this workspace:\n${hits.join('\n')}`);
679
+ }
680
+ }
681
+ function collectDeclarationFiles(tree, dir) {
682
+ if (!tree.exists(dir))
683
+ return [];
684
+ return tree.children(dir).flatMap((child) => {
685
+ if (child === 'node_modules')
686
+ return [];
687
+ const childPath = joinPathFragments(dir, child);
688
+ if (tree.isFile(childPath)) {
689
+ return /\.d\.(c|m)?ts$/.test(child) ? [childPath] : [];
690
+ }
691
+ return collectDeclarationFiles(tree, childPath);
692
+ });
693
+ }
694
+ function findRenamedSymbolsInDeclaration(sourceFile) {
695
+ const names = new Set();
696
+ const namespaceAliases = collectQueryNamespaceAliases(sourceFile);
697
+ const isQueryModule = (node) => !!node && ts.isStringLiteral(node) && node.text === '@ethlete/query';
698
+ function visit(node) {
699
+ if (ts.isImportDeclaration(node) && isQueryModule(node.moduleSpecifier)) {
700
+ const namedBindings = node.importClause?.namedBindings;
701
+ if (namedBindings && ts.isNamedImports(namedBindings)) {
702
+ namedBindings.elements.forEach((element) => names.add((element.propertyName ?? element.name).text));
703
+ }
704
+ }
705
+ if (ts.isExportDeclaration(node) && isQueryModule(node.moduleSpecifier) && node.exportClause) {
706
+ if (ts.isNamedExports(node.exportClause)) {
707
+ node.exportClause.elements.forEach((element) => names.add((element.propertyName ?? element.name).text));
708
+ }
709
+ }
710
+ if (ts.isImportTypeNode(node) &&
711
+ ts.isLiteralTypeNode(node.argument) &&
712
+ isQueryModule(node.argument.literal) &&
713
+ node.qualifier) {
714
+ names.add(getLeftmostIdentifier(node.qualifier).text);
715
+ }
716
+ const namespaceMember = getNamespaceMember(node, namespaceAliases)?.member;
717
+ if (namespaceMember) {
718
+ names.add(namespaceMember.text);
719
+ }
720
+ ts.forEachChild(node, visit);
721
+ }
722
+ visit(sourceFile);
723
+ return new Set([...names].filter(isRenamedSymbol));
724
+ }
725
+ function getLeftmostIdentifier(name) {
726
+ return ts.isIdentifier(name) ? name : getLeftmostIdentifier(name.left);
327
727
  }
328
728
  //#endregion
329
729
  //# sourceMappingURL=migration.js.map