@ethlete/query 6.0.0-next.6 ā 6.0.0-next.60
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 +939 -0
- package/fesm2022/ethlete-query-devtools-contract.mjs +1 -0
- package/fesm2022/ethlete-query-ngrx-toolkit.mjs +411 -0
- package/fesm2022/ethlete-query-ngrx-toolkit.mjs.map +1 -0
- package/fesm2022/ethlete-query-testing.mjs +548 -169
- package/fesm2022/ethlete-query-testing.mjs.map +1 -1
- package/fesm2022/ethlete-query.mjs +14680 -7172
- package/fesm2022/ethlete-query.mjs.map +1 -1
- package/generators/deprecate-legacy-queries/migration.js +85 -0
- package/generators/deprecate-legacy-queries/schema.json +26 -0
- package/generators/generators.json +30 -0
- package/generators/migrate-from-ngrx-toolkit/args-assignability.js +140 -0
- package/generators/migrate-from-ngrx-toolkit/consumers.js +521 -0
- package/generators/migrate-from-ngrx-toolkit/creators.js +301 -0
- package/generators/migrate-from-ngrx-toolkit/facade.js +295 -0
- package/generators/migrate-from-ngrx-toolkit/feature.js +513 -0
- package/generators/migrate-from-ngrx-toolkit/migration.js +140 -0
- package/generators/migrate-from-ngrx-toolkit/report.js +154 -0
- package/generators/migrate-from-ngrx-toolkit/schema.json +42 -0
- package/generators/migrate-from-ngrx-toolkit/store-removal.js +633 -0
- package/generators/migrate-from-ngrx-toolkit/ts-edits.js +128 -0
- package/generators/migrate-query-client-features/migration.js +144 -0
- package/generators/migrate-query-client-features/report.js +136 -0
- package/generators/migrate-query-client-features/schema.json +26 -0
- package/generators/migrate-query-opt-in-features/migration.js +195 -0
- package/generators/migrate-query-opt-in-features/report.js +143 -0
- package/generators/migrate-query-opt-in-features/schema.json +31 -0
- package/generators/migrate-to-query-v3/auth-provider-scaffold.js +160 -0
- package/generators/migrate-to-query-v3/cleanup-migration.js +247 -125
- package/generators/migrate-to-query-v3/http-client-check.js +93 -0
- package/generators/migrate-to-query-v3/legacy-prepare-migration.js +129 -40
- package/generators/migrate-to-query-v3/legacy-query-creator-migration.js +274 -152
- package/generators/migrate-to-query-v3/migration-scope.js +40 -0
- package/generators/migrate-to-query-v3/migration.js +19 -10
- package/generators/migrate-to-query-v3/module-graph.js +165 -0
- package/generators/migrate-to-query-v3/query-client-migration.js +342 -160
- package/generators/migrate-to-query-v3/rename-symbols.js +233 -0
- package/generators/migrate-to-query-v3/report.js +40 -7
- package/generators/migrate-to-query-v3/schema.json +12 -0
- package/generators/migrate-to-query-v3/shared.js +97 -3
- package/generators/move-devtools-contract-imports/migration.js +155 -0
- package/generators/move-devtools-contract-imports/moved-names.js +153 -0
- package/generators/move-devtools-contract-imports/schema.json +26 -0
- package/generators/prep-for-query-v3/migration.js +465 -65
- package/generators/report-legacy-query-apis/migration.js +256 -0
- package/generators/report-legacy-query-apis/schema.json +25 -0
- package/migrations/query-field-default-types.md +43 -0
- package/migrations/search-query-field-string.md +43 -0
- package/migrations.json +87 -0
- package/package.json +34 -14
- package/types/ethlete-query-devtools-contract.d.ts +2 -0
- package/types/ethlete-query-ngrx-toolkit.d.ts +131 -0
- package/types/ethlete-query-testing.d.ts +295 -160
- 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(([
|
|
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
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
84
|
-
|
|
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
|
-
|
|
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:
|
|
117
|
-
end:
|
|
118
|
-
replacement:
|
|
119
|
-
oldName:
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
305
|
-
|
|
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:
|
|
357
|
+
replacement: member.text,
|
|
311
358
|
});
|
|
312
359
|
}
|
|
313
360
|
ts.forEachChild(node, createReplacements);
|
|
314
361
|
}
|
|
315
362
|
createReplacements(sourceFile);
|
|
316
|
-
//
|
|
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
|
|
319
|
-
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|