@ethlete/query 5.42.0 ā 5.42.1
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 +6 -0
- package/fesm2022/ethlete-query.mjs +0 -10
- package/fesm2022/ethlete-query.mjs.map +1 -1
- package/generators/migrate-to-query-v3/migration.js +1212 -4
- package/index.d.ts +15 -14
- package/package.json +1 -1
|
@@ -60,6 +60,10 @@ async function migrate(tree, schema) {
|
|
|
60
60
|
replaceAnyQueryWithLegacy(tree);
|
|
61
61
|
// Remove devtools usage
|
|
62
62
|
removeDevtoolsUsage(tree);
|
|
63
|
+
// Migrate empty .prepare() calls to .prepare({})
|
|
64
|
+
migrateEmptyPrepareCalls(tree);
|
|
65
|
+
// Check for legacy query creator usage (step 1: just collect and report)
|
|
66
|
+
checkLegacyQueryCreatorUsage(tree);
|
|
63
67
|
if (!schema.skipFormat) {
|
|
64
68
|
await (0, devkit_1.formatFiles)(tree);
|
|
65
69
|
}
|
|
@@ -1196,9 +1200,14 @@ function updateLegacyCreatorUsages(content, legacyCreators) {
|
|
|
1196
1200
|
}
|
|
1197
1201
|
});
|
|
1198
1202
|
}
|
|
1199
|
-
// Handle regular identifier references
|
|
1203
|
+
// Handle regular identifier references - but skip declarations and property access names
|
|
1200
1204
|
if (ts.isIdentifier(node) && legacyCreators.has(node.text)) {
|
|
1201
1205
|
const parent = node.parent;
|
|
1206
|
+
// Skip if it's a property name in PropertyAccessExpression (e.g., obj.methodName)
|
|
1207
|
+
if (ts.isPropertyAccessExpression(parent) && parent.name === node) {
|
|
1208
|
+
ts.forEachChild(node, visit);
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1202
1211
|
// Skip if it's a property name in an object literal (already handled above)
|
|
1203
1212
|
if (ts.isPropertyAssignment(parent) && parent.name === node) {
|
|
1204
1213
|
ts.forEachChild(node, visit);
|
|
@@ -1224,6 +1233,25 @@ function updateLegacyCreatorUsages(content, legacyCreators) {
|
|
|
1224
1233
|
ts.forEachChild(node, visit);
|
|
1225
1234
|
return;
|
|
1226
1235
|
}
|
|
1236
|
+
// Skip if it's a function/method name declaration
|
|
1237
|
+
if (ts.isFunctionDeclaration(parent) && parent.name === node) {
|
|
1238
|
+
ts.forEachChild(node, visit);
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
if (ts.isMethodDeclaration(parent) && parent.name === node) {
|
|
1242
|
+
ts.forEachChild(node, visit);
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
// Skip if it's a parameter name
|
|
1246
|
+
if (ts.isParameter(parent) && parent.name === node) {
|
|
1247
|
+
ts.forEachChild(node, visit);
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
// Skip if it's a property declaration name (class field)
|
|
1251
|
+
if (ts.isPropertyDeclaration(parent) && parent.name === node) {
|
|
1252
|
+
ts.forEachChild(node, visit);
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1227
1255
|
// Replace with legacy name
|
|
1228
1256
|
const newName = legacyCreators.get(node.text);
|
|
1229
1257
|
replacements.push({
|
|
@@ -1244,9 +1272,6 @@ function updateLegacyCreatorUsages(content, legacyCreators) {
|
|
|
1244
1272
|
return result;
|
|
1245
1273
|
}
|
|
1246
1274
|
//#endregion
|
|
1247
|
-
//#region Check all files for usage of legacy query creators and add injector if needed
|
|
1248
|
-
// TODO
|
|
1249
|
-
//#endregion
|
|
1250
1275
|
//#region Remove provideQueryClientForDevtools and QueryDevtoolsComponent in ts / et-query-devtools in html usage
|
|
1251
1276
|
function removeDevtoolsUsage(tree) {
|
|
1252
1277
|
const projects = (0, devkit_1.getProjects)(tree);
|
|
@@ -1740,5 +1765,1188 @@ function generateInjectFunction(configName) {
|
|
|
1740
1765
|
const functionName = `inject${capitalizedName}`;
|
|
1741
1766
|
return `export const ${functionName} = () => inject(${configName}Config.token);`;
|
|
1742
1767
|
}
|
|
1768
|
+
function checkLegacyQueryCreatorUsage(tree) {
|
|
1769
|
+
console.log('\nš Checking for legacy query creator usages...');
|
|
1770
|
+
// Step 1: Collect all usages BEFORE any transformations
|
|
1771
|
+
const allUsages = [];
|
|
1772
|
+
(0, devkit_1.visitNotIgnoredFiles)(tree, '', (filePath) => {
|
|
1773
|
+
if (!filePath.endsWith('.ts') || filePath.endsWith('.spec.ts'))
|
|
1774
|
+
return;
|
|
1775
|
+
const content = tree.read(filePath, 'utf-8');
|
|
1776
|
+
if (!content)
|
|
1777
|
+
return;
|
|
1778
|
+
const usages = findLegacyCreatorUsages(content, filePath);
|
|
1779
|
+
allUsages.push(...usages);
|
|
1780
|
+
});
|
|
1781
|
+
if (allUsages.length === 0) {
|
|
1782
|
+
console.log('\nā
No legacy query creator usages found');
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
console.log(`\nš Found ${allUsages.length} legacy query creator usages`);
|
|
1786
|
+
// Log statistics
|
|
1787
|
+
const contextCounts = new Map();
|
|
1788
|
+
allUsages.forEach((usage) => {
|
|
1789
|
+
contextCounts.set(usage.context, (contextCounts.get(usage.context) || 0) + 1);
|
|
1790
|
+
});
|
|
1791
|
+
contextCounts.forEach((count, context) => {
|
|
1792
|
+
console.log(` - ${context}: ${count} usages`);
|
|
1793
|
+
});
|
|
1794
|
+
// Step 1.5: Detect polling usage BEFORE any transformations
|
|
1795
|
+
console.log('\nš Detecting polling usage...');
|
|
1796
|
+
const pollingInfo = detectPollingUsage(tree);
|
|
1797
|
+
if (pollingInfo.size > 0) {
|
|
1798
|
+
console.log(`\nā ļø Found ${pollingInfo.size} queries with polling:`);
|
|
1799
|
+
pollingInfo.forEach((info) => {
|
|
1800
|
+
console.log(` ${info.queryVariableName}:`);
|
|
1801
|
+
info.locations.forEach((loc) => console.log(` - ${loc}`));
|
|
1802
|
+
});
|
|
1803
|
+
console.log('\n ā These queries will NOT have destroyOnResponse: true added');
|
|
1804
|
+
}
|
|
1805
|
+
// Step 2: Find or create injector members in classes
|
|
1806
|
+
const classInjectors = findOrCreateInjectorMembers(tree, allUsages);
|
|
1807
|
+
createInjectorMembers(tree, classInjectors);
|
|
1808
|
+
// Step 3: Transform prepare() calls with polling info
|
|
1809
|
+
transformLegacyCreatorPrepareCall(tree, classInjectors, pollingInfo);
|
|
1810
|
+
// Step 4: Report manual review locations (using original line numbers from allUsages)
|
|
1811
|
+
reportManualReviewLocations(tree, allUsages);
|
|
1812
|
+
}
|
|
1813
|
+
function findLegacyCreatorUsages(content, filePath) {
|
|
1814
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
1815
|
+
const usages = [];
|
|
1816
|
+
// First, find all legacy query creator names imported/defined in this file
|
|
1817
|
+
const legacyCreatorNames = findLegacyCreatorNames(sourceFile, content);
|
|
1818
|
+
if (legacyCreatorNames.size === 0) {
|
|
1819
|
+
return usages;
|
|
1820
|
+
}
|
|
1821
|
+
// Now find all .prepare() calls on these creators
|
|
1822
|
+
function visit(node) {
|
|
1823
|
+
// Look for: legacyCreator.prepare()
|
|
1824
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
|
|
1825
|
+
const propAccess = node.expression;
|
|
1826
|
+
if (ts.isIdentifier(propAccess.name) && propAccess.name.text === 'prepare') {
|
|
1827
|
+
// Check if the object is a legacy creator
|
|
1828
|
+
let creatorName;
|
|
1829
|
+
if (ts.isIdentifier(propAccess.expression)) {
|
|
1830
|
+
creatorName = propAccess.expression.text;
|
|
1831
|
+
}
|
|
1832
|
+
if (creatorName && legacyCreatorNames.has(creatorName)) {
|
|
1833
|
+
const context = determineUsageContext(node, sourceFile);
|
|
1834
|
+
const hasInjector = checkForExistingInjector(node, sourceFile);
|
|
1835
|
+
const lineNumber = getLineNumber(node, sourceFile);
|
|
1836
|
+
usages.push({
|
|
1837
|
+
filePath,
|
|
1838
|
+
creatorName,
|
|
1839
|
+
line: lineNumber,
|
|
1840
|
+
position: {
|
|
1841
|
+
start: node.getStart(sourceFile),
|
|
1842
|
+
end: node.getEnd(),
|
|
1843
|
+
},
|
|
1844
|
+
context,
|
|
1845
|
+
hasExistingInjector: hasInjector,
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
ts.forEachChild(node, visit);
|
|
1851
|
+
}
|
|
1852
|
+
visit(sourceFile);
|
|
1853
|
+
return usages;
|
|
1854
|
+
}
|
|
1855
|
+
function findLegacyCreatorNames(sourceFile, content) {
|
|
1856
|
+
const names = new Set();
|
|
1857
|
+
function visit(node) {
|
|
1858
|
+
// Find imports of legacy creators (names starting with 'legacy')
|
|
1859
|
+
if (ts.isImportDeclaration(node) &&
|
|
1860
|
+
node.importClause?.namedBindings &&
|
|
1861
|
+
ts.isNamedImports(node.importClause.namedBindings)) {
|
|
1862
|
+
node.importClause.namedBindings.elements.forEach((element) => {
|
|
1863
|
+
const name = element.name.text;
|
|
1864
|
+
if (name.startsWith('legacy')) {
|
|
1865
|
+
names.add(name);
|
|
1866
|
+
}
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
// Find local legacy creator definitions
|
|
1870
|
+
if (ts.isVariableDeclaration(node) &&
|
|
1871
|
+
ts.isIdentifier(node.name) &&
|
|
1872
|
+
node.name.text.startsWith('legacy') &&
|
|
1873
|
+
node.initializer &&
|
|
1874
|
+
ts.isCallExpression(node.initializer)) {
|
|
1875
|
+
const callExpr = node.initializer;
|
|
1876
|
+
if (ts.isPropertyAccessExpression(callExpr.expression) &&
|
|
1877
|
+
ts.isIdentifier(callExpr.expression.expression) &&
|
|
1878
|
+
callExpr.expression.expression.text === 'E' &&
|
|
1879
|
+
ts.isIdentifier(callExpr.expression.name) &&
|
|
1880
|
+
callExpr.expression.name.text === 'createLegacyQueryCreator') {
|
|
1881
|
+
names.add(node.name.text);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
ts.forEachChild(node, visit);
|
|
1885
|
+
}
|
|
1886
|
+
visit(sourceFile);
|
|
1887
|
+
return names;
|
|
1888
|
+
}
|
|
1889
|
+
function determineUsageContext(node, sourceFile) {
|
|
1890
|
+
let current = node;
|
|
1891
|
+
while (current) {
|
|
1892
|
+
// Check for queryComputed FIRST (before checking for generic functions)
|
|
1893
|
+
// We need to look at the parent of arrow/function expressions
|
|
1894
|
+
if (ts.isArrowFunction(current) || ts.isFunctionExpression(current)) {
|
|
1895
|
+
const parent = current.parent;
|
|
1896
|
+
// Check if this function is a callback to queryComputed
|
|
1897
|
+
if (ts.isCallExpression(parent)) {
|
|
1898
|
+
const expr = parent.expression;
|
|
1899
|
+
// Check for queryComputed()
|
|
1900
|
+
if (ts.isIdentifier(expr) && expr.text === 'queryComputed') {
|
|
1901
|
+
return 'queryComputed';
|
|
1902
|
+
}
|
|
1903
|
+
// Check for E.queryComputed()
|
|
1904
|
+
if (ts.isPropertyAccessExpression(expr) &&
|
|
1905
|
+
ts.isIdentifier(expr.expression) &&
|
|
1906
|
+
expr.expression.text === 'E' &&
|
|
1907
|
+
ts.isIdentifier(expr.name) &&
|
|
1908
|
+
expr.name.text === 'queryComputed') {
|
|
1909
|
+
return 'queryComputed';
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
// Check for constructor
|
|
1914
|
+
if (ts.isConstructorDeclaration(current)) {
|
|
1915
|
+
return 'constructor';
|
|
1916
|
+
}
|
|
1917
|
+
// Check for method
|
|
1918
|
+
if (ts.isMethodDeclaration(current)) {
|
|
1919
|
+
return 'method';
|
|
1920
|
+
}
|
|
1921
|
+
// Check for class field
|
|
1922
|
+
if (ts.isPropertyDeclaration(current)) {
|
|
1923
|
+
return 'class-field';
|
|
1924
|
+
}
|
|
1925
|
+
// Check for function declarations
|
|
1926
|
+
if (ts.isFunctionDeclaration(current)) {
|
|
1927
|
+
return 'function';
|
|
1928
|
+
}
|
|
1929
|
+
// Check for arrow functions and function expressions (standalone, not callbacks)
|
|
1930
|
+
// But make sure we're not inside a class (which would make it a method/field)
|
|
1931
|
+
if ((ts.isArrowFunction(current) || ts.isFunctionExpression(current)) && !isInsideClass(current)) {
|
|
1932
|
+
return 'function';
|
|
1933
|
+
}
|
|
1934
|
+
current = current.parent;
|
|
1935
|
+
}
|
|
1936
|
+
return 'unknown';
|
|
1937
|
+
}
|
|
1938
|
+
function isInsideClass(node) {
|
|
1939
|
+
let current = node.parent;
|
|
1940
|
+
while (current) {
|
|
1941
|
+
if (ts.isClassDeclaration(current)) {
|
|
1942
|
+
return true;
|
|
1943
|
+
}
|
|
1944
|
+
current = current.parent;
|
|
1945
|
+
}
|
|
1946
|
+
return false;
|
|
1947
|
+
}
|
|
1948
|
+
function checkForExistingInjector(node, sourceFile) {
|
|
1949
|
+
// Walk up to find the containing class
|
|
1950
|
+
let current = node;
|
|
1951
|
+
while (current && !ts.isClassDeclaration(current)) {
|
|
1952
|
+
current = current.parent;
|
|
1953
|
+
}
|
|
1954
|
+
if (!current || !ts.isClassDeclaration(current)) {
|
|
1955
|
+
return false;
|
|
1956
|
+
}
|
|
1957
|
+
const classDecl = current;
|
|
1958
|
+
// Check if class has any member that calls inject(Injector)
|
|
1959
|
+
for (const member of classDecl.members) {
|
|
1960
|
+
if (ts.isPropertyDeclaration(member) && member.initializer) {
|
|
1961
|
+
if (hasInjectInjectorCall(member.initializer)) {
|
|
1962
|
+
return true;
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
if (ts.isConstructorDeclaration(member)) {
|
|
1966
|
+
for (const param of member.parameters) {
|
|
1967
|
+
if (param.initializer && hasInjectInjectorCall(param.initializer)) {
|
|
1968
|
+
return true;
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
return false;
|
|
1974
|
+
}
|
|
1975
|
+
function hasInjectInjectorCall(node) {
|
|
1976
|
+
let found = false;
|
|
1977
|
+
function visit(n) {
|
|
1978
|
+
if (found)
|
|
1979
|
+
return;
|
|
1980
|
+
// Look for: inject(Injector)
|
|
1981
|
+
if (ts.isCallExpression(n) && ts.isIdentifier(n.expression) && n.expression.text === 'inject') {
|
|
1982
|
+
if (n.arguments.length > 0 && ts.isIdentifier(n.arguments[0]) && n.arguments[0].text === 'Injector') {
|
|
1983
|
+
found = true;
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
ts.forEachChild(n, visit);
|
|
1988
|
+
}
|
|
1989
|
+
visit(node);
|
|
1990
|
+
return found;
|
|
1991
|
+
}
|
|
1992
|
+
function getLineNumber(node, sourceFile) {
|
|
1993
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
1994
|
+
return line + 1;
|
|
1995
|
+
}
|
|
1996
|
+
function findOrCreateInjectorMembers(tree, usages) {
|
|
1997
|
+
const classInjectors = new Map();
|
|
1998
|
+
// Group usages by file and class
|
|
1999
|
+
const usagesByFile = new Map();
|
|
2000
|
+
usages.forEach((usage) => {
|
|
2001
|
+
// Skip usages that don't need injector (queryComputed, class-field, constructor)
|
|
2002
|
+
if (['queryComputed', 'class-field', 'constructor'].includes(usage.context)) {
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
if (!usagesByFile.has(usage.filePath)) {
|
|
2006
|
+
usagesByFile.set(usage.filePath, []);
|
|
2007
|
+
}
|
|
2008
|
+
usagesByFile.get(usage.filePath).push(usage);
|
|
2009
|
+
});
|
|
2010
|
+
// Process each file
|
|
2011
|
+
usagesByFile.forEach((fileUsages, filePath) => {
|
|
2012
|
+
const content = tree.read(filePath, 'utf-8');
|
|
2013
|
+
if (!content)
|
|
2014
|
+
return;
|
|
2015
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
2016
|
+
// Find all classes in this file that have usages
|
|
2017
|
+
const classesNeedingInjector = findClassesNeedingInjector(sourceFile, fileUsages);
|
|
2018
|
+
classesNeedingInjector.forEach((classInfo) => {
|
|
2019
|
+
const key = `${filePath}:${classInfo.className}`;
|
|
2020
|
+
classInjectors.set(key, {
|
|
2021
|
+
...classInfo,
|
|
2022
|
+
filePath,
|
|
2023
|
+
});
|
|
2024
|
+
});
|
|
2025
|
+
});
|
|
2026
|
+
return classInjectors;
|
|
2027
|
+
}
|
|
2028
|
+
function findClassesNeedingInjector(sourceFile, usages) {
|
|
2029
|
+
const classes = [];
|
|
2030
|
+
const actualFilePath = usages[0]?.filePath || sourceFile.fileName; // Get the real file path from usages
|
|
2031
|
+
function visit(node) {
|
|
2032
|
+
if (ts.isClassDeclaration(node)) {
|
|
2033
|
+
const className = node.name?.text || 'UnnamedClass';
|
|
2034
|
+
// Check if any usage is within this class
|
|
2035
|
+
const usagesInClass = usages.filter((usage) => {
|
|
2036
|
+
return usage.position.start >= node.getStart(sourceFile) && usage.position.end <= node.getEnd();
|
|
2037
|
+
});
|
|
2038
|
+
if (usagesInClass.length === 0) {
|
|
2039
|
+
ts.forEachChild(node, visit);
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
// Check if class already has an injector member
|
|
2043
|
+
const existingInjector = findExistingInjectorMember(node, sourceFile);
|
|
2044
|
+
if (existingInjector) {
|
|
2045
|
+
classes.push({
|
|
2046
|
+
filePath: actualFilePath,
|
|
2047
|
+
className,
|
|
2048
|
+
injectorMemberName: existingInjector.name,
|
|
2049
|
+
needsToCreate: false,
|
|
2050
|
+
classStart: node.getStart(sourceFile),
|
|
2051
|
+
classEnd: node.getEnd(),
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
else {
|
|
2055
|
+
// Need to create one
|
|
2056
|
+
classes.push({
|
|
2057
|
+
filePath: actualFilePath,
|
|
2058
|
+
className,
|
|
2059
|
+
injectorMemberName: 'injector', // Default name
|
|
2060
|
+
needsToCreate: true,
|
|
2061
|
+
classStart: node.getStart(sourceFile),
|
|
2062
|
+
classEnd: node.getEnd(),
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
ts.forEachChild(node, visit);
|
|
2067
|
+
}
|
|
2068
|
+
visit(sourceFile);
|
|
2069
|
+
return classes;
|
|
2070
|
+
}
|
|
2071
|
+
function findExistingInjectorMember(classNode, sourceFile) {
|
|
2072
|
+
for (const member of classNode.members) {
|
|
2073
|
+
if (ts.isPropertyDeclaration(member) && member.initializer) {
|
|
2074
|
+
// Check if it's: inject(Injector)
|
|
2075
|
+
if (hasInjectInjectorCall(member.initializer)) {
|
|
2076
|
+
const memberName = member.name && ts.isIdentifier(member.name) ? member.name.text : undefined;
|
|
2077
|
+
if (memberName) {
|
|
2078
|
+
return { name: memberName, node: member };
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
return undefined;
|
|
2084
|
+
}
|
|
2085
|
+
function createInjectorMembers(tree, classInjectors) {
|
|
2086
|
+
const filesToUpdate = new Map();
|
|
2087
|
+
// Group by file
|
|
2088
|
+
classInjectors.forEach((classInfo) => {
|
|
2089
|
+
if (!classInfo.needsToCreate)
|
|
2090
|
+
return;
|
|
2091
|
+
if (!filesToUpdate.has(classInfo.filePath)) {
|
|
2092
|
+
filesToUpdate.set(classInfo.filePath, []);
|
|
2093
|
+
}
|
|
2094
|
+
filesToUpdate.get(classInfo.filePath).push(classInfo);
|
|
2095
|
+
});
|
|
2096
|
+
// Update each file
|
|
2097
|
+
filesToUpdate.forEach((classes, filePath) => {
|
|
2098
|
+
let content = tree.read(filePath, 'utf-8');
|
|
2099
|
+
if (!content)
|
|
2100
|
+
return;
|
|
2101
|
+
// Add inject and Injector imports if needed
|
|
2102
|
+
content = ensureInjectAndInjectorImports(content);
|
|
2103
|
+
// Add injector member to each class (in reverse order to maintain positions)
|
|
2104
|
+
const sortedClasses = [...classes].sort((a, b) => b.classStart - a.classStart);
|
|
2105
|
+
for (const classInfo of sortedClasses) {
|
|
2106
|
+
content = addInjectorMemberToClass(content, classInfo);
|
|
2107
|
+
}
|
|
2108
|
+
tree.write(filePath, content);
|
|
2109
|
+
});
|
|
2110
|
+
if (filesToUpdate.size > 0) {
|
|
2111
|
+
console.log(`\nā
Added injector members to ${Array.from(filesToUpdate.values()).flat().length} classes`);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
function ensureInjectAndInjectorImports(content) {
|
|
2115
|
+
const sourceFile = ts.createSourceFile('temp.ts', content, ts.ScriptTarget.Latest, true);
|
|
2116
|
+
let angularCoreImport;
|
|
2117
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
2118
|
+
if (ts.isImportDeclaration(node) &&
|
|
2119
|
+
ts.isStringLiteral(node.moduleSpecifier) &&
|
|
2120
|
+
node.moduleSpecifier.text === '@angular/core') {
|
|
2121
|
+
angularCoreImport = node;
|
|
2122
|
+
}
|
|
2123
|
+
});
|
|
2124
|
+
const neededImports = new Set();
|
|
2125
|
+
let hasInject = false;
|
|
2126
|
+
let hasInjector = false;
|
|
2127
|
+
if (angularCoreImport?.importClause?.namedBindings &&
|
|
2128
|
+
ts.isNamedImports(angularCoreImport.importClause.namedBindings)) {
|
|
2129
|
+
const namedBindings = angularCoreImport.importClause.namedBindings;
|
|
2130
|
+
namedBindings.elements.forEach((el) => {
|
|
2131
|
+
if (el.name.text === 'inject')
|
|
2132
|
+
hasInject = true;
|
|
2133
|
+
if (el.name.text === 'Injector')
|
|
2134
|
+
hasInjector = true;
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
if (!hasInject)
|
|
2138
|
+
neededImports.add('inject');
|
|
2139
|
+
if (!hasInjector)
|
|
2140
|
+
neededImports.add('Injector');
|
|
2141
|
+
if (neededImports.size === 0) {
|
|
2142
|
+
return content; // Already has both imports
|
|
2143
|
+
}
|
|
2144
|
+
if (angularCoreImport?.importClause?.namedBindings &&
|
|
2145
|
+
ts.isNamedImports(angularCoreImport.importClause.namedBindings)) {
|
|
2146
|
+
// Add to existing import
|
|
2147
|
+
const namedBindings = angularCoreImport.importClause.namedBindings;
|
|
2148
|
+
const existingImports = namedBindings.elements.map((el) => el.name.text);
|
|
2149
|
+
const allImports = [...existingImports, ...Array.from(neededImports)].sort();
|
|
2150
|
+
const newImportStatement = `import { ${allImports.join(', ')} } from '@angular/core';`;
|
|
2151
|
+
const importStart = angularCoreImport.getStart(sourceFile);
|
|
2152
|
+
const importEnd = angularCoreImport.getEnd();
|
|
2153
|
+
return content.slice(0, importStart) + newImportStatement + content.slice(importEnd);
|
|
2154
|
+
}
|
|
2155
|
+
// Find last import to add after it
|
|
2156
|
+
let lastImportEnd = 0;
|
|
2157
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
2158
|
+
if (ts.isImportDeclaration(node)) {
|
|
2159
|
+
const end = node.getEnd();
|
|
2160
|
+
if (end > lastImportEnd) {
|
|
2161
|
+
lastImportEnd = end;
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
});
|
|
2165
|
+
const newImports = Array.from(neededImports).sort();
|
|
2166
|
+
const newImportStatement = `\nimport { ${newImports.join(', ')} } from '@angular/core';`;
|
|
2167
|
+
if (lastImportEnd > 0) {
|
|
2168
|
+
return content.slice(0, lastImportEnd) + newImportStatement + content.slice(lastImportEnd);
|
|
2169
|
+
}
|
|
2170
|
+
return `import { ${newImports.join(', ')} } from '@angular/core';\n\n${content}`;
|
|
2171
|
+
}
|
|
2172
|
+
function addInjectorMemberToClass(content, classInfo) {
|
|
2173
|
+
const sourceFile = ts.createSourceFile('temp.ts', content, ts.ScriptTarget.Latest, true);
|
|
2174
|
+
// Find the class
|
|
2175
|
+
let targetClass;
|
|
2176
|
+
function visit(node) {
|
|
2177
|
+
if (ts.isClassDeclaration(node)) {
|
|
2178
|
+
const className = node.name?.text;
|
|
2179
|
+
if (className === classInfo.className) {
|
|
2180
|
+
targetClass = node;
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
ts.forEachChild(node, visit);
|
|
2184
|
+
}
|
|
2185
|
+
visit(sourceFile);
|
|
2186
|
+
if (!targetClass)
|
|
2187
|
+
return content;
|
|
2188
|
+
// Find where to insert the injector member (after the opening brace)
|
|
2189
|
+
const classBodyStart = targetClass.members.length > 0 ? targetClass.members[0].getStart(sourceFile) : targetClass.getEnd() - 1; // Before closing brace
|
|
2190
|
+
const indentation = getIndentation(content, classBodyStart);
|
|
2191
|
+
const injectorMember = `${indentation}private ${classInfo.injectorMemberName} = inject(Injector);\n\n`;
|
|
2192
|
+
return content.slice(0, classBodyStart) + injectorMember + content.slice(classBodyStart);
|
|
2193
|
+
}
|
|
2194
|
+
function getIndentation(content, position) {
|
|
2195
|
+
// Walk backwards to find the start of the line
|
|
2196
|
+
let lineStart = position;
|
|
2197
|
+
while (lineStart > 0 && content[lineStart - 1] !== '\n') {
|
|
2198
|
+
lineStart--;
|
|
2199
|
+
}
|
|
2200
|
+
// Count spaces/tabs
|
|
2201
|
+
let indentation = '';
|
|
2202
|
+
for (let i = lineStart; i < position && /\s/.test(content[i]); i++) {
|
|
2203
|
+
indentation += content[i];
|
|
2204
|
+
}
|
|
2205
|
+
return indentation || ' '; // Default to 2 spaces if no indentation found
|
|
2206
|
+
}
|
|
2207
|
+
//#endregion
|
|
2208
|
+
//#region Step 3: Transform legacy query creator prepare() calls to add injector
|
|
2209
|
+
function transformLegacyCreatorPrepareCall(tree, classInjectors, pollingInfo) {
|
|
2210
|
+
const updatedFiles = [];
|
|
2211
|
+
// Get all files that have classes with injector OR have standalone function usages
|
|
2212
|
+
const filesByPath = new Map();
|
|
2213
|
+
classInjectors.forEach((classInfo) => {
|
|
2214
|
+
if (!filesByPath.has(classInfo.filePath)) {
|
|
2215
|
+
filesByPath.set(classInfo.filePath, []);
|
|
2216
|
+
}
|
|
2217
|
+
filesByPath.get(classInfo.filePath).push(classInfo);
|
|
2218
|
+
});
|
|
2219
|
+
// Also check files with standalone function usages
|
|
2220
|
+
(0, devkit_1.visitNotIgnoredFiles)(tree, '', (filePath) => {
|
|
2221
|
+
if (!filePath.endsWith('.ts') || filePath.endsWith('.spec.ts'))
|
|
2222
|
+
return;
|
|
2223
|
+
if (filesByPath.has(filePath))
|
|
2224
|
+
return; // Already processing
|
|
2225
|
+
const content = tree.read(filePath, 'utf-8');
|
|
2226
|
+
if (!content)
|
|
2227
|
+
return;
|
|
2228
|
+
// Only analyze files that have .prepare() calls
|
|
2229
|
+
if (!content.includes('.prepare('))
|
|
2230
|
+
return;
|
|
2231
|
+
const usages = findLegacyCreatorUsages(content, filePath);
|
|
2232
|
+
const functionUsages = usages.filter((u) => u.context === 'function');
|
|
2233
|
+
if (functionUsages.length > 0) {
|
|
2234
|
+
filesByPath.set(filePath, []); // Empty array means standalone functions only
|
|
2235
|
+
}
|
|
2236
|
+
});
|
|
2237
|
+
filesByPath.forEach((classes, filePath) => {
|
|
2238
|
+
let content = tree.read(filePath, 'utf-8');
|
|
2239
|
+
if (!content)
|
|
2240
|
+
return;
|
|
2241
|
+
// Ensure imports are added (for both classes and functions)
|
|
2242
|
+
content = ensureInjectAndInjectorImports(content);
|
|
2243
|
+
// Transform prepare calls for each class and functions
|
|
2244
|
+
const newContent = transformPrepareCallsInFile(content, classes, pollingInfo);
|
|
2245
|
+
if (newContent !== content) {
|
|
2246
|
+
tree.write(filePath, newContent);
|
|
2247
|
+
updatedFiles.push(filePath);
|
|
2248
|
+
}
|
|
2249
|
+
});
|
|
2250
|
+
if (updatedFiles.length > 0) {
|
|
2251
|
+
console.log(`\nā
Transformed legacy query creator prepare() calls in ${updatedFiles.length} files`);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
function transformPrepareCallsInFile(content, classes, pollingInfo) {
|
|
2255
|
+
const sourceFile = ts.createSourceFile('temp.ts', content, ts.ScriptTarget.Latest, true);
|
|
2256
|
+
const replacements = [];
|
|
2257
|
+
// Find all legacy creator names
|
|
2258
|
+
const legacyCreatorNames = findLegacyCreatorNames(sourceFile, content);
|
|
2259
|
+
if (legacyCreatorNames.size === 0)
|
|
2260
|
+
return content;
|
|
2261
|
+
// Build a map of class NAME to their injector member name
|
|
2262
|
+
const classNameToInjector = new Map();
|
|
2263
|
+
classes.forEach((classInfo) => {
|
|
2264
|
+
classNameToInjector.set(classInfo.className, classInfo.injectorMemberName);
|
|
2265
|
+
});
|
|
2266
|
+
// Track standalone functions that need injector by their position
|
|
2267
|
+
const functionsNeedingInjector = new Map();
|
|
2268
|
+
function visit(node) {
|
|
2269
|
+
// Find: legacyCreator.prepare(...)
|
|
2270
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
|
|
2271
|
+
const propAccess = node.expression;
|
|
2272
|
+
if (ts.isIdentifier(propAccess.name) && propAccess.name.text === 'prepare') {
|
|
2273
|
+
let creatorName;
|
|
2274
|
+
if (ts.isIdentifier(propAccess.expression)) {
|
|
2275
|
+
creatorName = propAccess.expression.text;
|
|
2276
|
+
}
|
|
2277
|
+
if (!creatorName || !legacyCreatorNames.has(creatorName)) {
|
|
2278
|
+
ts.forEachChild(node, visit);
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
// Check if this is in a context that needs injector
|
|
2282
|
+
const context = determineUsageContext(node, sourceFile);
|
|
2283
|
+
if (['queryComputed', 'constructor', 'class-field'].includes(context)) {
|
|
2284
|
+
// Safe contexts that don't need injector or destroyOnResponse
|
|
2285
|
+
ts.forEachChild(node, visit);
|
|
2286
|
+
return;
|
|
2287
|
+
}
|
|
2288
|
+
// Find the containing class or function
|
|
2289
|
+
const containingClass = findContainingClass(node, sourceFile);
|
|
2290
|
+
const containingFunction = findContainingStandaloneFunction(node, sourceFile);
|
|
2291
|
+
if (containingClass) {
|
|
2292
|
+
// Handle class method (NOT class fields - they were filtered above)
|
|
2293
|
+
const className = containingClass.name?.text;
|
|
2294
|
+
if (!className) {
|
|
2295
|
+
ts.forEachChild(node, visit);
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2298
|
+
const injectorMember = classNameToInjector.get(className);
|
|
2299
|
+
if (!injectorMember) {
|
|
2300
|
+
ts.forEachChild(node, visit);
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
// Transform the prepare call
|
|
2304
|
+
const transformedCall = transformSinglePrepareCall(node, `this.${injectorMember}`, sourceFile, pollingInfo);
|
|
2305
|
+
if (transformedCall) {
|
|
2306
|
+
replacements.push({
|
|
2307
|
+
start: node.getStart(sourceFile),
|
|
2308
|
+
end: node.getEnd(),
|
|
2309
|
+
replacement: transformedCall,
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
else if (containingFunction) {
|
|
2314
|
+
// Handle standalone function - find the outermost function with inject()
|
|
2315
|
+
const functionWithInject = findOutermostFunctionWithInject(containingFunction, sourceFile);
|
|
2316
|
+
if (functionWithInject) {
|
|
2317
|
+
const funcPosition = functionWithInject.getStart(sourceFile);
|
|
2318
|
+
// Check if we haven't already processed this function
|
|
2319
|
+
if (!functionsNeedingInjector.has(funcPosition)) {
|
|
2320
|
+
// Check if the function already has an injector parameter
|
|
2321
|
+
const existingInjectorParam = findInjectorParameter(functionWithInject);
|
|
2322
|
+
if (existingInjectorParam) {
|
|
2323
|
+
// Use the existing parameter name
|
|
2324
|
+
functionsNeedingInjector.set(funcPosition, {
|
|
2325
|
+
injectorName: existingInjectorParam,
|
|
2326
|
+
bodyStart: -1, // No declaration needed
|
|
2327
|
+
needsDeclaration: false,
|
|
2328
|
+
});
|
|
2329
|
+
}
|
|
2330
|
+
else {
|
|
2331
|
+
// Need to declare injector
|
|
2332
|
+
let bodyStart;
|
|
2333
|
+
if ((ts.isFunctionDeclaration(functionWithInject) || ts.isFunctionExpression(functionWithInject)) &&
|
|
2334
|
+
functionWithInject.body &&
|
|
2335
|
+
ts.isBlock(functionWithInject.body)) {
|
|
2336
|
+
if (functionWithInject.body.statements.length > 0) {
|
|
2337
|
+
bodyStart = functionWithInject.body.statements[0].getStart(sourceFile);
|
|
2338
|
+
}
|
|
2339
|
+
else {
|
|
2340
|
+
bodyStart = functionWithInject.body.getStart(sourceFile) + 1;
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
else if (ts.isArrowFunction(functionWithInject) &&
|
|
2344
|
+
functionWithInject.body &&
|
|
2345
|
+
ts.isBlock(functionWithInject.body)) {
|
|
2346
|
+
if (functionWithInject.body.statements.length > 0) {
|
|
2347
|
+
bodyStart = functionWithInject.body.statements[0].getStart(sourceFile);
|
|
2348
|
+
}
|
|
2349
|
+
else {
|
|
2350
|
+
bodyStart = functionWithInject.body.getStart(sourceFile) + 1;
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
if (bodyStart !== undefined) {
|
|
2354
|
+
functionsNeedingInjector.set(funcPosition, {
|
|
2355
|
+
injectorName: 'injector',
|
|
2356
|
+
bodyStart,
|
|
2357
|
+
needsDeclaration: true,
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
const injectorInfo = functionsNeedingInjector.get(funcPosition);
|
|
2363
|
+
const injectorName = injectorInfo?.injectorName || 'injector';
|
|
2364
|
+
// Transform the prepare call
|
|
2365
|
+
const transformedCall = transformSinglePrepareCall(node, injectorName, sourceFile, pollingInfo);
|
|
2366
|
+
if (transformedCall) {
|
|
2367
|
+
replacements.push({
|
|
2368
|
+
start: node.getStart(sourceFile),
|
|
2369
|
+
end: node.getEnd(),
|
|
2370
|
+
replacement: transformedCall,
|
|
2371
|
+
});
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
ts.forEachChild(node, visit);
|
|
2378
|
+
}
|
|
2379
|
+
visit(sourceFile);
|
|
2380
|
+
// Apply injector declarations first (in reverse order) - only for functions that need them
|
|
2381
|
+
let result = content;
|
|
2382
|
+
const injectorDeclarations = [];
|
|
2383
|
+
functionsNeedingInjector.forEach(({ injectorName, bodyStart, needsDeclaration }) => {
|
|
2384
|
+
if (needsDeclaration && bodyStart > 0) {
|
|
2385
|
+
const indentation = getIndentation(content, bodyStart);
|
|
2386
|
+
const injectorDecl = `${indentation}const ${injectorName} = inject(Injector);\n`;
|
|
2387
|
+
injectorDeclarations.push({
|
|
2388
|
+
position: bodyStart,
|
|
2389
|
+
text: injectorDecl,
|
|
2390
|
+
});
|
|
2391
|
+
}
|
|
2392
|
+
});
|
|
2393
|
+
injectorDeclarations.sort((a, b) => b.position - a.position);
|
|
2394
|
+
for (const { position, text } of injectorDeclarations) {
|
|
2395
|
+
result = result.slice(0, position) + text + result.slice(position);
|
|
2396
|
+
}
|
|
2397
|
+
// Then apply prepare call replacements (need to adjust positions for injector insertions)
|
|
2398
|
+
const sortedReplacements = [...replacements].sort((a, b) => b.start - a.start);
|
|
2399
|
+
for (const { start, end, replacement } of sortedReplacements) {
|
|
2400
|
+
// Calculate position adjustment from injector declarations that came before this replacement
|
|
2401
|
+
let adjustment = 0;
|
|
2402
|
+
for (const decl of injectorDeclarations) {
|
|
2403
|
+
if (decl.position <= start) {
|
|
2404
|
+
adjustment += decl.text.length;
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
result = result.slice(0, start + adjustment) + replacement + result.slice(end + adjustment);
|
|
2408
|
+
}
|
|
2409
|
+
return result;
|
|
2410
|
+
}
|
|
2411
|
+
function findInjectorParameter(func) {
|
|
2412
|
+
if (!func.parameters || func.parameters.length === 0) {
|
|
2413
|
+
return undefined;
|
|
2414
|
+
}
|
|
2415
|
+
// Look for a parameter named 'injector' or with a type containing 'Injector'
|
|
2416
|
+
for (const param of func.parameters) {
|
|
2417
|
+
if (ts.isIdentifier(param.name)) {
|
|
2418
|
+
const paramName = param.name.text;
|
|
2419
|
+
// Check if parameter is named 'injector'
|
|
2420
|
+
if (paramName.toLowerCase() === 'injector') {
|
|
2421
|
+
return paramName;
|
|
2422
|
+
}
|
|
2423
|
+
// Check if parameter type contains 'Injector'
|
|
2424
|
+
if (param.type) {
|
|
2425
|
+
const typeText = param.type.getText();
|
|
2426
|
+
if (typeText.includes('Injector')) {
|
|
2427
|
+
return paramName;
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
return undefined;
|
|
2433
|
+
}
|
|
2434
|
+
function findOutermostFunctionWithInject(startFunction, sourceFile) {
|
|
2435
|
+
let current = startFunction;
|
|
2436
|
+
let outermostWithInject;
|
|
2437
|
+
// Walk up the tree looking for functions with inject() calls
|
|
2438
|
+
while (current) {
|
|
2439
|
+
// Stop if we hit a class - we don't want to go outside the function scope
|
|
2440
|
+
if (ts.isClassDeclaration(current)) {
|
|
2441
|
+
break;
|
|
2442
|
+
}
|
|
2443
|
+
// Check if this is a function with inject()
|
|
2444
|
+
if ((ts.isFunctionDeclaration(current) || ts.isArrowFunction(current) || ts.isFunctionExpression(current)) &&
|
|
2445
|
+
containsInjectCall(current)) {
|
|
2446
|
+
outermostWithInject = current;
|
|
2447
|
+
}
|
|
2448
|
+
current = current.parent;
|
|
2449
|
+
}
|
|
2450
|
+
return outermostWithInject;
|
|
2451
|
+
}
|
|
2452
|
+
function findContainingStandaloneFunction(node, sourceFile) {
|
|
2453
|
+
let current = node;
|
|
2454
|
+
while (current) {
|
|
2455
|
+
// Check if we hit a class first - then it's not a standalone function
|
|
2456
|
+
if (ts.isClassDeclaration(current)) {
|
|
2457
|
+
return undefined;
|
|
2458
|
+
}
|
|
2459
|
+
// Check for function declarations, arrow functions, or function expressions
|
|
2460
|
+
if (ts.isFunctionDeclaration(current) || ts.isArrowFunction(current) || ts.isFunctionExpression(current)) {
|
|
2461
|
+
return current;
|
|
2462
|
+
}
|
|
2463
|
+
current = current.parent;
|
|
2464
|
+
}
|
|
2465
|
+
return undefined;
|
|
2466
|
+
}
|
|
2467
|
+
function findOrCreateInjectorInFunction(func, sourceFile, functionsNeedingInjector) {
|
|
2468
|
+
// Check if function already has an injector variable
|
|
2469
|
+
const existingInjector = findExistingInjectorVariable(func, sourceFile);
|
|
2470
|
+
if (existingInjector) {
|
|
2471
|
+
return existingInjector;
|
|
2472
|
+
}
|
|
2473
|
+
// Check if we already marked this function for injector creation
|
|
2474
|
+
const existing = functionsNeedingInjector.get(func);
|
|
2475
|
+
if (existing) {
|
|
2476
|
+
return existing;
|
|
2477
|
+
}
|
|
2478
|
+
// Mark for creation
|
|
2479
|
+
const injectorName = 'injector';
|
|
2480
|
+
functionsNeedingInjector.set(func, injectorName);
|
|
2481
|
+
return injectorName;
|
|
2482
|
+
}
|
|
2483
|
+
function findExistingInjectorVariable(func, sourceFile) {
|
|
2484
|
+
const body = func.body;
|
|
2485
|
+
if (!body || !ts.isBlock(body)) {
|
|
2486
|
+
return undefined;
|
|
2487
|
+
}
|
|
2488
|
+
for (const statement of body.statements) {
|
|
2489
|
+
if (ts.isVariableStatement(statement)) {
|
|
2490
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
2491
|
+
if (ts.isIdentifier(declaration.name) &&
|
|
2492
|
+
declaration.initializer &&
|
|
2493
|
+
hasInjectInjectorCall(declaration.initializer)) {
|
|
2494
|
+
return declaration.name.text;
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
return undefined;
|
|
2500
|
+
}
|
|
2501
|
+
function findContainingClass(node, sourceFile) {
|
|
2502
|
+
let current = node;
|
|
2503
|
+
while (current) {
|
|
2504
|
+
if (ts.isClassDeclaration(current)) {
|
|
2505
|
+
return current;
|
|
2506
|
+
}
|
|
2507
|
+
current = current.parent;
|
|
2508
|
+
}
|
|
2509
|
+
return undefined;
|
|
2510
|
+
}
|
|
2511
|
+
function transformSinglePrepareCall(callNode, injectorReference, sourceFile, pollingInfo) {
|
|
2512
|
+
const propAccess = callNode.expression;
|
|
2513
|
+
const creatorName = propAccess.expression.text;
|
|
2514
|
+
// Get existing arguments
|
|
2515
|
+
const existingArgs = callNode.arguments.length > 0 ? callNode.arguments[0] : undefined;
|
|
2516
|
+
// Build new argument object
|
|
2517
|
+
const newArgProperties = [];
|
|
2518
|
+
let existingConfig;
|
|
2519
|
+
let needsSpread = false;
|
|
2520
|
+
let spreadExpression;
|
|
2521
|
+
// If there's an existing argument and it's an object literal, extract its properties
|
|
2522
|
+
if (existingArgs) {
|
|
2523
|
+
if (ts.isObjectLiteralExpression(existingArgs)) {
|
|
2524
|
+
existingArgs.properties.forEach((prop) => {
|
|
2525
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
2526
|
+
const propName = prop.name.text;
|
|
2527
|
+
// Handle config specially - we need to merge it
|
|
2528
|
+
if (propName === 'config') {
|
|
2529
|
+
existingConfig = prop;
|
|
2530
|
+
return; // Don't add it yet
|
|
2531
|
+
}
|
|
2532
|
+
const propValue = prop.initializer.getText(sourceFile);
|
|
2533
|
+
newArgProperties.push(`${propName}: ${propValue}`);
|
|
2534
|
+
}
|
|
2535
|
+
else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
2536
|
+
const propName = prop.name.text;
|
|
2537
|
+
newArgProperties.push(propName);
|
|
2538
|
+
}
|
|
2539
|
+
else if (ts.isSpreadAssignment(prop)) {
|
|
2540
|
+
// Existing spread - preserve it
|
|
2541
|
+
spreadExpression = prop.expression.getText(sourceFile);
|
|
2542
|
+
needsSpread = true;
|
|
2543
|
+
}
|
|
2544
|
+
});
|
|
2545
|
+
}
|
|
2546
|
+
else {
|
|
2547
|
+
// If it's not an object literal (e.g., a variable or expression), we need to spread it
|
|
2548
|
+
spreadExpression = existingArgs.getText(sourceFile);
|
|
2549
|
+
needsSpread = true;
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
// Add injector
|
|
2553
|
+
newArgProperties.push(`injector: ${injectorReference}`);
|
|
2554
|
+
// Conditionally add or merge config
|
|
2555
|
+
const queryVariableName = findQueryVariableNameForPrepareCall(callNode, sourceFile);
|
|
2556
|
+
const addDestroyOnResponse = shouldAddDestroyOnResponse(queryVariableName, pollingInfo);
|
|
2557
|
+
if (existingConfig) {
|
|
2558
|
+
// Merge with existing config
|
|
2559
|
+
const existingConfigObj = existingConfig.initializer;
|
|
2560
|
+
if (ts.isObjectLiteralExpression(existingConfigObj)) {
|
|
2561
|
+
const configProps = [];
|
|
2562
|
+
// Add existing config properties
|
|
2563
|
+
existingConfigObj.properties.forEach((prop) => {
|
|
2564
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
2565
|
+
const propName = prop.name.text;
|
|
2566
|
+
const propValue = prop.initializer.getText(sourceFile);
|
|
2567
|
+
configProps.push(`${propName}: ${propValue}`);
|
|
2568
|
+
}
|
|
2569
|
+
else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
2570
|
+
const propName = prop.name.text;
|
|
2571
|
+
configProps.push(propName);
|
|
2572
|
+
}
|
|
2573
|
+
});
|
|
2574
|
+
// Add destroyOnResponse if needed
|
|
2575
|
+
if (addDestroyOnResponse) {
|
|
2576
|
+
configProps.push('destroyOnResponse: true');
|
|
2577
|
+
}
|
|
2578
|
+
if (configProps.length > 2) {
|
|
2579
|
+
newArgProperties.push(`config: {\n ${configProps.join(',\n ')}\n }`);
|
|
2580
|
+
}
|
|
2581
|
+
else {
|
|
2582
|
+
newArgProperties.push(`config: { ${configProps.join(', ')} }`);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
else {
|
|
2586
|
+
// Config is not an object literal (e.g., a variable) - can't merge
|
|
2587
|
+
// Just keep the existing config
|
|
2588
|
+
newArgProperties.push(`config: ${existingConfigObj.getText(sourceFile)}`);
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
else if (addDestroyOnResponse) {
|
|
2592
|
+
// No existing config - add destroyOnResponse
|
|
2593
|
+
newArgProperties.push(`config: { destroyOnResponse: true }`);
|
|
2594
|
+
}
|
|
2595
|
+
// Format based on whether we need to spread and number of properties
|
|
2596
|
+
let argsString;
|
|
2597
|
+
if (needsSpread && spreadExpression) {
|
|
2598
|
+
// Need to spread the existing argument
|
|
2599
|
+
if (newArgProperties.length <= 2 && !newArgProperties.some((p) => p.includes('\n'))) {
|
|
2600
|
+
// Single line for short objects
|
|
2601
|
+
argsString = `{ ...${spreadExpression}, ${newArgProperties.join(', ')} }`;
|
|
2602
|
+
}
|
|
2603
|
+
else {
|
|
2604
|
+
// Multi-line for longer objects
|
|
2605
|
+
argsString = `{\n ...${spreadExpression},\n ${newArgProperties.join(',\n ')}\n}`;
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
else {
|
|
2609
|
+
// No spread needed
|
|
2610
|
+
if (newArgProperties.length <= 2 && !newArgProperties.some((p) => p.includes('\n'))) {
|
|
2611
|
+
// Single line for short objects
|
|
2612
|
+
argsString = `{ ${newArgProperties.join(', ')} }`;
|
|
2613
|
+
}
|
|
2614
|
+
else {
|
|
2615
|
+
// Multi-line for longer objects
|
|
2616
|
+
argsString = `{\n ${newArgProperties.join(',\n ')}\n}`;
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
return `${creatorName}.prepare(${argsString})`;
|
|
2620
|
+
}
|
|
2621
|
+
function findQueryVariableNameForPrepareCall(prepareCallNode, sourceFile) {
|
|
2622
|
+
// Walk up to find if this prepare call is assigned to a variable
|
|
2623
|
+
// e.g., const query = legacyGetUsers.prepare()
|
|
2624
|
+
let parent = prepareCallNode.parent;
|
|
2625
|
+
while (parent) {
|
|
2626
|
+
// Check if assigned to a variable
|
|
2627
|
+
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
|
|
2628
|
+
return parent.name.text;
|
|
2629
|
+
}
|
|
2630
|
+
// Check if assigned to a class property
|
|
2631
|
+
if (ts.isPropertyDeclaration(parent) && ts.isIdentifier(parent.name)) {
|
|
2632
|
+
return parent.name.text;
|
|
2633
|
+
}
|
|
2634
|
+
// Check if it's a return statement - look for the method/function name
|
|
2635
|
+
if (ts.isReturnStatement(parent)) {
|
|
2636
|
+
const containingFunction = findContainingMethod(parent, sourceFile);
|
|
2637
|
+
if (containingFunction) {
|
|
2638
|
+
return containingFunction;
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
parent = parent.parent;
|
|
2642
|
+
}
|
|
2643
|
+
return undefined;
|
|
2644
|
+
}
|
|
2645
|
+
function findContainingMethod(node, sourceFile) {
|
|
2646
|
+
let current = node;
|
|
2647
|
+
while (current) {
|
|
2648
|
+
if (ts.isMethodDeclaration(current) && ts.isIdentifier(current.name)) {
|
|
2649
|
+
return current.name.text;
|
|
2650
|
+
}
|
|
2651
|
+
if (ts.isFunctionDeclaration(current) && current.name && ts.isIdentifier(current.name)) {
|
|
2652
|
+
return current.name.text;
|
|
2653
|
+
}
|
|
2654
|
+
if (ts.isArrowFunction(current)) {
|
|
2655
|
+
// Check if it's assigned to a variable
|
|
2656
|
+
const parent = current.parent;
|
|
2657
|
+
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
|
|
2658
|
+
return parent.name.text;
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
current = current.parent;
|
|
2662
|
+
}
|
|
2663
|
+
return undefined;
|
|
2664
|
+
}
|
|
2665
|
+
function reportManualReviewLocations(tree, allUsages) {
|
|
2666
|
+
const needsReview = [];
|
|
2667
|
+
allUsages.forEach((usage) => {
|
|
2668
|
+
// Check if this usage needs manual review
|
|
2669
|
+
if (usage.context === 'function') {
|
|
2670
|
+
// For function context, we need to check if it was in a function WITHOUT inject()
|
|
2671
|
+
// This means it should have been flagged for manual review during transformation
|
|
2672
|
+
// We can check if the function had inject by looking at the original file
|
|
2673
|
+
// (before transformation) since we need to report the original line number anyway
|
|
2674
|
+
const originalContent = tree.read(usage.filePath, 'utf-8');
|
|
2675
|
+
if (!originalContent)
|
|
2676
|
+
return;
|
|
2677
|
+
// Parse the CURRENT file to check if inject was added
|
|
2678
|
+
// But use the ORIGINAL line number from usage
|
|
2679
|
+
const sourceFile = ts.createSourceFile(usage.filePath, originalContent, ts.ScriptTarget.Latest, true);
|
|
2680
|
+
// Find any function that contains a .prepare() call around the original position
|
|
2681
|
+
let foundFunctionWithoutInject = false;
|
|
2682
|
+
function checkFunctions(node) {
|
|
2683
|
+
// Look for functions that might contain our usage
|
|
2684
|
+
if (ts.isFunctionDeclaration(node) || ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
2685
|
+
const funcStart = node.getStart(sourceFile);
|
|
2686
|
+
const funcEnd = node.getEnd();
|
|
2687
|
+
// Check if our usage position is within this function
|
|
2688
|
+
if (usage.position.start >= funcStart && usage.position.end <= funcEnd) {
|
|
2689
|
+
// Check if this function or any parent function has inject()
|
|
2690
|
+
if (!containsInjectCall(node) && !findOutermostFunctionWithInject(node, sourceFile)) {
|
|
2691
|
+
foundFunctionWithoutInject = true;
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
ts.forEachChild(node, checkFunctions);
|
|
2696
|
+
}
|
|
2697
|
+
checkFunctions(sourceFile);
|
|
2698
|
+
if (foundFunctionWithoutInject) {
|
|
2699
|
+
needsReview.push({
|
|
2700
|
+
filePath: usage.filePath,
|
|
2701
|
+
line: usage.line, // Use the original line number from Step 1
|
|
2702
|
+
creatorName: usage.creatorName,
|
|
2703
|
+
reason: 'Used in standalone function without inject() - may need manual injector passing',
|
|
2704
|
+
});
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
else if (usage.context === 'unknown') {
|
|
2708
|
+
needsReview.push({
|
|
2709
|
+
filePath: usage.filePath,
|
|
2710
|
+
line: usage.line, // Use the original line number from Step 1
|
|
2711
|
+
creatorName: usage.creatorName,
|
|
2712
|
+
reason: 'Could not determine execution context - please verify manually',
|
|
2713
|
+
});
|
|
2714
|
+
}
|
|
2715
|
+
});
|
|
2716
|
+
if (needsReview.length > 0) {
|
|
2717
|
+
console.warn(`\nā ļø Found ${needsReview.length} locations that may need manual review:`);
|
|
2718
|
+
// Group by file for cleaner output
|
|
2719
|
+
const byFile = new Map();
|
|
2720
|
+
needsReview.forEach((location) => {
|
|
2721
|
+
if (!byFile.has(location.filePath)) {
|
|
2722
|
+
byFile.set(location.filePath, []);
|
|
2723
|
+
}
|
|
2724
|
+
byFile.get(location.filePath).push(location);
|
|
2725
|
+
});
|
|
2726
|
+
byFile.forEach((locations, filePath) => {
|
|
2727
|
+
console.warn(`\n š ${filePath}:`);
|
|
2728
|
+
locations.forEach((location) => {
|
|
2729
|
+
console.warn(` Line ${location.line}: ${location.creatorName}`);
|
|
2730
|
+
console.warn(` ā ${location.reason}`);
|
|
2731
|
+
});
|
|
2732
|
+
});
|
|
2733
|
+
console.warn('\nš” Solve these warnings by:');
|
|
2734
|
+
console.warn(' - [HIGHLY RECOMMENDED] Passing an injector as a parameter to the prepare method call alongside a config: { destroyOnResponse: true }');
|
|
2735
|
+
console.warn(' - Using queryComputed() if appropriate');
|
|
2736
|
+
console.warn(' - Putting the prepare call into an injection context');
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
//#endregion
|
|
2740
|
+
//#region Helper functions for inject detection
|
|
2741
|
+
function containsInjectCall(node) {
|
|
2742
|
+
let hasInject = false;
|
|
2743
|
+
function visit(n) {
|
|
2744
|
+
if (hasInject)
|
|
2745
|
+
return;
|
|
2746
|
+
// Check for direct inject() call
|
|
2747
|
+
if (ts.isCallExpression(n) && ts.isIdentifier(n.expression) && n.expression.text === 'inject') {
|
|
2748
|
+
hasInject = true;
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
// Check for any function call that starts with "inject"
|
|
2752
|
+
if (ts.isCallExpression(n) && ts.isIdentifier(n.expression)) {
|
|
2753
|
+
if (n.expression.text.startsWith('inject')) {
|
|
2754
|
+
hasInject = true;
|
|
2755
|
+
return;
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
ts.forEachChild(n, visit);
|
|
2759
|
+
}
|
|
2760
|
+
visit(node);
|
|
2761
|
+
return hasInject;
|
|
2762
|
+
}
|
|
2763
|
+
//#endregion
|
|
2764
|
+
//#region Migrate empty .prepare() calls to .prepare({})
|
|
2765
|
+
function migrateEmptyPrepareCalls(tree) {
|
|
2766
|
+
const updatedFiles = [];
|
|
2767
|
+
(0, devkit_1.visitNotIgnoredFiles)(tree, '', (filePath) => {
|
|
2768
|
+
if (!filePath.endsWith('.ts') || filePath.endsWith('.spec.ts'))
|
|
2769
|
+
return;
|
|
2770
|
+
const content = tree.read(filePath, 'utf-8');
|
|
2771
|
+
if (!content)
|
|
2772
|
+
return;
|
|
2773
|
+
// Only analyze files that have .prepare() calls
|
|
2774
|
+
if (!content.includes('.prepare()'))
|
|
2775
|
+
return;
|
|
2776
|
+
const newContent = transformEmptyPrepareCalls(content);
|
|
2777
|
+
if (newContent !== content) {
|
|
2778
|
+
tree.write(filePath, newContent);
|
|
2779
|
+
updatedFiles.push(filePath);
|
|
2780
|
+
}
|
|
2781
|
+
});
|
|
2782
|
+
if (updatedFiles.length > 0) {
|
|
2783
|
+
console.log(`\nā
Migrated empty .prepare() calls to .prepare({}) in ${updatedFiles.length} files`);
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
function transformEmptyPrepareCalls(content) {
|
|
2787
|
+
const sourceFile = ts.createSourceFile('temp.ts', content, ts.ScriptTarget.Latest, true);
|
|
2788
|
+
const replacements = [];
|
|
2789
|
+
function visit(node) {
|
|
2790
|
+
// Find: something.prepare()
|
|
2791
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
|
|
2792
|
+
const propAccess = node.expression;
|
|
2793
|
+
if (ts.isIdentifier(propAccess.name) && propAccess.name.text === 'prepare') {
|
|
2794
|
+
// Check if it has no arguments
|
|
2795
|
+
if (node.arguments.length === 0) {
|
|
2796
|
+
// Get the full text of the call: "something.prepare()"
|
|
2797
|
+
const callStart = node.getStart(sourceFile);
|
|
2798
|
+
const callEnd = node.getEnd();
|
|
2799
|
+
const callText = content.slice(callStart, callEnd);
|
|
2800
|
+
// Replace with: "something.prepare({})"
|
|
2801
|
+
const replacement = callText.replace(/\.prepare\(\)/, '.prepare({})');
|
|
2802
|
+
replacements.push({
|
|
2803
|
+
start: callStart,
|
|
2804
|
+
end: callEnd,
|
|
2805
|
+
replacement,
|
|
2806
|
+
});
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
ts.forEachChild(node, visit);
|
|
2811
|
+
}
|
|
2812
|
+
visit(sourceFile);
|
|
2813
|
+
// Apply replacements in reverse order
|
|
2814
|
+
let result = content;
|
|
2815
|
+
replacements.sort((a, b) => b.start - a.start);
|
|
2816
|
+
for (const { start, end, replacement } of replacements) {
|
|
2817
|
+
result = result.slice(0, start) + replacement + result.slice(end);
|
|
2818
|
+
}
|
|
2819
|
+
return result;
|
|
2820
|
+
}
|
|
2821
|
+
function detectPollingUsage(tree) {
|
|
2822
|
+
const pollingInfo = new Map();
|
|
2823
|
+
(0, devkit_1.visitNotIgnoredFiles)(tree, '', (filePath) => {
|
|
2824
|
+
if (filePath.endsWith('.spec.ts'))
|
|
2825
|
+
return;
|
|
2826
|
+
const content = tree.read(filePath, 'utf-8');
|
|
2827
|
+
if (!content)
|
|
2828
|
+
return;
|
|
2829
|
+
// Check TypeScript files
|
|
2830
|
+
if (filePath.endsWith('.ts')) {
|
|
2831
|
+
detectPollingInTypeScript(content, filePath, pollingInfo);
|
|
2832
|
+
}
|
|
2833
|
+
// Check HTML templates
|
|
2834
|
+
if (filePath.endsWith('.html')) {
|
|
2835
|
+
detectPollingInTemplate(content, filePath, pollingInfo);
|
|
2836
|
+
}
|
|
2837
|
+
});
|
|
2838
|
+
return pollingInfo;
|
|
2839
|
+
}
|
|
2840
|
+
function detectPollingInTypeScript(content, filePath, pollingInfo) {
|
|
2841
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
2842
|
+
function visit(node) {
|
|
2843
|
+
// Find .poll() or .stopPolling() calls
|
|
2844
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
|
|
2845
|
+
const propAccess = node.expression;
|
|
2846
|
+
const methodName = propAccess.name;
|
|
2847
|
+
if (ts.isIdentifier(methodName) && (methodName.text === 'poll' || methodName.text === 'stopPolling')) {
|
|
2848
|
+
// Walk back to find the root variable name
|
|
2849
|
+
const queryVariableName = getQueryVariableName(propAccess.expression, sourceFile);
|
|
2850
|
+
if (queryVariableName) {
|
|
2851
|
+
const existing = pollingInfo.get(queryVariableName);
|
|
2852
|
+
if (existing) {
|
|
2853
|
+
existing.hasPolling = true;
|
|
2854
|
+
existing.locations.push(`${filePath}:${getLineNumber(node, sourceFile)} (.${methodName.text})`);
|
|
2855
|
+
}
|
|
2856
|
+
else {
|
|
2857
|
+
pollingInfo.set(queryVariableName, {
|
|
2858
|
+
queryVariableName,
|
|
2859
|
+
hasPolling: true,
|
|
2860
|
+
locations: [`${filePath}:${getLineNumber(node, sourceFile)} (.${methodName.text})`],
|
|
2861
|
+
});
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
ts.forEachChild(node, visit);
|
|
2867
|
+
}
|
|
2868
|
+
visit(sourceFile);
|
|
2869
|
+
}
|
|
2870
|
+
function getQueryVariableName(expr, sourceFile) {
|
|
2871
|
+
// Walk back through the chain to find the root variable
|
|
2872
|
+
let current = expr;
|
|
2873
|
+
while (true) {
|
|
2874
|
+
if (ts.isIdentifier(current)) {
|
|
2875
|
+
return current.text;
|
|
2876
|
+
}
|
|
2877
|
+
if (ts.isPropertyAccessExpression(current)) {
|
|
2878
|
+
current = current.expression;
|
|
2879
|
+
continue;
|
|
2880
|
+
}
|
|
2881
|
+
if (ts.isCallExpression(current)) {
|
|
2882
|
+
// For chained calls like query.prepare().execute().poll()
|
|
2883
|
+
// We want to get the root query variable
|
|
2884
|
+
current = current.expression;
|
|
2885
|
+
continue;
|
|
2886
|
+
}
|
|
2887
|
+
// Can't determine the root variable
|
|
2888
|
+
return undefined;
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
function detectPollingInTemplate(content, filePath, pollingInfo) {
|
|
2892
|
+
// Use regex to find .poll() or .stopPolling() in templates
|
|
2893
|
+
// This is not perfect but catches most cases
|
|
2894
|
+
const pollRegex = /(\w+)\.poll\(\)/g;
|
|
2895
|
+
const stopPollingRegex = /(\w+)\.stopPolling\(\)/g;
|
|
2896
|
+
let match;
|
|
2897
|
+
// Find .poll() calls
|
|
2898
|
+
while ((match = pollRegex.exec(content)) !== null) {
|
|
2899
|
+
const queryVariableName = match[1];
|
|
2900
|
+
const lineNumber = getLineNumberFromPosition(content, match.index);
|
|
2901
|
+
const existing = pollingInfo.get(queryVariableName);
|
|
2902
|
+
if (existing) {
|
|
2903
|
+
existing.hasPolling = true;
|
|
2904
|
+
existing.locations.push(`${filePath}:${lineNumber} (.poll() in template)`);
|
|
2905
|
+
}
|
|
2906
|
+
else {
|
|
2907
|
+
pollingInfo.set(queryVariableName, {
|
|
2908
|
+
queryVariableName,
|
|
2909
|
+
hasPolling: true,
|
|
2910
|
+
locations: [`${filePath}:${lineNumber} (.poll() in template)`],
|
|
2911
|
+
});
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
// Find .stopPolling() calls
|
|
2915
|
+
while ((match = stopPollingRegex.exec(content)) !== null) {
|
|
2916
|
+
const queryVariableName = match[1];
|
|
2917
|
+
const lineNumber = getLineNumberFromPosition(content, match.index);
|
|
2918
|
+
const existing = pollingInfo.get(queryVariableName);
|
|
2919
|
+
if (existing) {
|
|
2920
|
+
existing.hasPolling = true;
|
|
2921
|
+
existing.locations.push(`${filePath}:${lineNumber} (.stopPolling() in template)`);
|
|
2922
|
+
}
|
|
2923
|
+
else {
|
|
2924
|
+
pollingInfo.set(queryVariableName, {
|
|
2925
|
+
queryVariableName,
|
|
2926
|
+
hasPolling: true,
|
|
2927
|
+
locations: [`${filePath}:${lineNumber} (.stopPolling() in template)`],
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
function getLineNumberFromPosition(content, position) {
|
|
2933
|
+
const lines = content.slice(0, position).split('\n');
|
|
2934
|
+
return lines.length;
|
|
2935
|
+
}
|
|
2936
|
+
function shouldAddDestroyOnResponse(queryVariableName, pollingInfo) {
|
|
2937
|
+
if (!queryVariableName) {
|
|
2938
|
+
// Can't determine - be conservative and don't add destroyOnResponse
|
|
2939
|
+
return false;
|
|
2940
|
+
}
|
|
2941
|
+
const info = pollingInfo.get(queryVariableName);
|
|
2942
|
+
// If we found polling usage, don't add destroyOnResponse
|
|
2943
|
+
if (info && info.hasPolling) {
|
|
2944
|
+
return false;
|
|
2945
|
+
}
|
|
2946
|
+
// No polling detected - safe to add destroyOnResponse
|
|
2947
|
+
return true;
|
|
2948
|
+
}
|
|
1743
2949
|
//#endregion
|
|
2950
|
+
// TODO:
|
|
2951
|
+
// - Only add destroyOnResponse if we are 00% sure there is no .poll / stopPolling calls on it
|
|
1744
2952
|
//# sourceMappingURL=migration.js.map
|