@ethlete/core 5.0.0-next.60 ā 5.0.0-next.62
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 +75 -0
- package/fesm2022/ethlete-core.mjs +972 -597
- package/fesm2022/ethlete-core.mjs.map +1 -1
- package/generators/generators.json +10 -0
- package/generators/migrate-create-destroy/create-destroy.js +317 -0
- package/generators/migrate-create-destroy/migration.js +46 -0
- package/generators/migrate-create-destroy/schema.json +26 -0
- package/generators/migrate-legacy-color-themes/legacy-color-themes.js +123 -0
- package/generators/migrate-legacy-color-themes/migration.js +93 -0
- package/generators/migrate-legacy-color-themes/schema.json +26 -0
- package/generators/migrate-to-v5/apply-replacements.js +14 -0
- package/generators/migrate-to-v5/color-naming.js +11 -57
- package/generators/migrate-to-v5/create-provider.js +56 -44
- package/generators/migrate-to-v5/migration-files.js +13 -0
- package/generators/migrate-to-v5/migration.js +31 -27
- package/generators/migrate-to-v5/removed-exports.js +101 -33
- package/generators/migrate-to-v5/router-state-service.js +118 -350
- package/generators/migrate-to-v5/schema.json +12 -0
- package/generators/migrate-to-v5/service-classes.js +108 -0
- package/generators/migrate-to-v5/viewport-service.js +195 -443
- package/generators/tailwind-4-color-theme/generator.js +71 -85
- package/migrations/legacy-color-themes.md +38 -0
- package/migrations/seo-directive-removed.md +2 -1
- package/migrations.json +15 -0
- package/package.json +11 -11
- package/types/ethlete-core.d.ts +300 -202
|
@@ -1,78 +1,40 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
1
|
import { logger } from '@nx/devkit';
|
|
3
2
|
import * as ts from 'typescript';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
function findFiles(dir) {
|
|
10
|
-
const children = tree.children(dir);
|
|
11
|
-
for (const child of children) {
|
|
12
|
-
const path = dir === '.' ? child : `${dir}/${child}`;
|
|
13
|
-
if (tree.isFile(path)) {
|
|
14
|
-
if (path.endsWith('.ts') && !path.includes('node_modules')) {
|
|
15
|
-
tsFiles.push(path);
|
|
16
|
-
}
|
|
17
|
-
else if (path.endsWith('.css') ||
|
|
18
|
-
path.endsWith('.scss') ||
|
|
19
|
-
path.endsWith('.sass') ||
|
|
20
|
-
path.endsWith('.less')) {
|
|
21
|
-
styleFiles.push(path);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
if (child === 'node_modules')
|
|
26
|
-
continue;
|
|
27
|
-
findFiles(path);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
findFiles('.');
|
|
32
|
-
// Detect which CSS variables are being used across the codebase
|
|
33
|
-
const cssVariablesUsed = detectCssVariableUsage(tree, styleFiles);
|
|
3
|
+
import { collectFiles } from './migration-files.js';
|
|
4
|
+
import { applyReplacementsInClass, collectClasses, findServiceClasses, getServiceImport, isServiceParameter, isServiceProperty, isServiceStillUsed, removeServiceImport, } from './service-classes.js';
|
|
5
|
+
export default async function migrateViewportService(tree, scope) {
|
|
6
|
+
const tsFiles = collectFiles(tree, scope, ['.ts']);
|
|
7
|
+
const review = [];
|
|
34
8
|
let filesModified = 0;
|
|
35
9
|
let templatesModified = 0;
|
|
36
|
-
|
|
37
|
-
// Track which properties became signals for template migration
|
|
38
|
-
const componentTemplateMigrations = new Map();
|
|
39
|
-
// Process each TypeScript file
|
|
10
|
+
const externalTemplateMigrations = new Map();
|
|
40
11
|
for (const filePath of tsFiles) {
|
|
41
12
|
const content = tree.read(filePath, 'utf-8');
|
|
42
13
|
if (!content)
|
|
43
14
|
continue;
|
|
44
|
-
// Skip files that don't use ViewportService
|
|
45
15
|
if (!content.includes('ViewportService'))
|
|
46
16
|
continue;
|
|
47
|
-
logger.log(`Processing: ${filePath}`);
|
|
48
17
|
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
49
|
-
const
|
|
50
|
-
|
|
18
|
+
const serviceImport = getServiceImport(sourceFile, 'ViewportService');
|
|
19
|
+
const viewportPackage = serviceImport?.packageName;
|
|
20
|
+
if (!serviceImport || (viewportPackage !== '@ethlete/core' && !viewportPackage?.startsWith('@fifa-gg/uikit'))) {
|
|
51
21
|
continue;
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
const templateMigrationInfo = {
|
|
56
|
-
signalProperties: new Set(),
|
|
57
|
-
};
|
|
58
|
-
// First handle inline inject patterns (e.g., toSignal(inject(ViewportService).isXs$))
|
|
59
|
-
const inlineResult = handleInlineInjectPatterns(sourceFile, content);
|
|
22
|
+
}
|
|
23
|
+
const serviceName = serviceImport.localName;
|
|
24
|
+
const inlineResult = handleInlineInjectPatterns(sourceFile, content, serviceName);
|
|
60
25
|
let updatedContent = inlineResult.content;
|
|
61
|
-
// Re-parse if content changed
|
|
62
26
|
const updatedSourceFile = updatedContent !== content
|
|
63
27
|
? ts.createSourceFile(filePath, updatedContent, ts.ScriptTarget.Latest, true)
|
|
64
28
|
: sourceFile;
|
|
65
|
-
|
|
66
|
-
const
|
|
29
|
+
const viewportServiceVars = findViewportServiceVariables(updatedSourceFile, serviceName);
|
|
30
|
+
const serviceClasses = findServiceClasses(updatedSourceFile, serviceName);
|
|
67
31
|
if (viewportServiceVars.length === 0 && inlineResult.importsNeeded.size === 0) {
|
|
68
32
|
continue;
|
|
69
33
|
}
|
|
70
|
-
viewportServiceUsed = true;
|
|
71
34
|
const allImportsNeeded = {
|
|
72
35
|
'@ethlete/core': new Set(),
|
|
73
36
|
'@angular/core/rxjs-interop': new Set(),
|
|
74
37
|
};
|
|
75
|
-
// Merge imports from inline patterns
|
|
76
38
|
for (const importName of inlineResult.importsNeeded) {
|
|
77
39
|
if (importName === 'toObservable' || importName === 'toSignal') {
|
|
78
40
|
allImportsNeeded['@angular/core/rxjs-interop'].add(importName);
|
|
@@ -81,205 +43,143 @@ export default async function migrateViewportService(tree) {
|
|
|
81
43
|
allImportsNeeded['@ethlete/core'].add(importName);
|
|
82
44
|
}
|
|
83
45
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
allImportsNeeded['@angular/core/rxjs-interop'].add('toObservable');
|
|
104
|
-
}
|
|
105
|
-
if (replacement.includes('toSignal(')) {
|
|
106
|
-
allImportsNeeded['@angular/core/rxjs-interop'].add('toSignal');
|
|
46
|
+
for (const { index, fields } of serviceClasses) {
|
|
47
|
+
for (const viewportServiceVar of fields) {
|
|
48
|
+
const currentSourceFile = ts.createSourceFile(filePath, updatedContent, ts.ScriptTarget.Latest, true);
|
|
49
|
+
const classNode = collectClasses(currentSourceFile)[index];
|
|
50
|
+
const context = analyzeClassMigration(currentSourceFile, classNode, viewportServiceVar);
|
|
51
|
+
context.importsNeeded.forEach((imp) => allImportsNeeded['@ethlete/core'].add(imp));
|
|
52
|
+
context.membersToAdd.forEach((member) => {
|
|
53
|
+
allImportsNeeded['@ethlete/core'].add(member.injectFn);
|
|
54
|
+
if (member.type === 'observable' && !member.wrappedInToSignal) {
|
|
55
|
+
allImportsNeeded['@angular/core/rxjs-interop'].add('toObservable');
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
for (const replacement of context.replacements.values()) {
|
|
59
|
+
if (replacement.includes('toObservable(')) {
|
|
60
|
+
allImportsNeeded['@angular/core/rxjs-interop'].add('toObservable');
|
|
61
|
+
}
|
|
62
|
+
if (replacement.includes('toSignal(')) {
|
|
63
|
+
allImportsNeeded['@angular/core/rxjs-interop'].add('toSignal');
|
|
64
|
+
}
|
|
107
65
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
updatedContent = updatedContent.replace(regex, replacement);
|
|
113
|
-
}
|
|
114
|
-
// Then add new members to the updated content
|
|
115
|
-
if (context.membersToAdd.length > 0) {
|
|
116
|
-
const sourceFileUpdated = ts.createSourceFile(filePath, updatedContent, ts.ScriptTarget.Latest, true);
|
|
117
|
-
const classNodeUpdated = findClassForViewportService(sourceFileUpdated, viewportServiceVar);
|
|
118
|
-
if (classNodeUpdated) {
|
|
119
|
-
updatedContent = addMembersToClass(sourceFileUpdated, updatedContent, classNodeUpdated, context.membersToAdd, viewportServiceVar);
|
|
66
|
+
updatedContent = applyReplacementsInClass(updatedContent, currentSourceFile, classNode, context.replacements);
|
|
67
|
+
if (context.membersToAdd.length > 0) {
|
|
68
|
+
const sourceFileUpdated = ts.createSourceFile(filePath, updatedContent, ts.ScriptTarget.Latest, true);
|
|
69
|
+
updatedContent = addMembersToClass(sourceFileUpdated, updatedContent, collectClasses(sourceFileUpdated)[index], context.membersToAdd, viewportServiceVar);
|
|
120
70
|
}
|
|
121
71
|
}
|
|
122
72
|
}
|
|
123
|
-
// Handle monitorViewport migrations
|
|
124
73
|
const sourceFileAfterReplacements = ts.createSourceFile(filePath, updatedContent, ts.ScriptTarget.Latest, true);
|
|
125
|
-
const monitorViewportResult = migrateMonitorViewport(sourceFileAfterReplacements, updatedContent, viewportServiceVars
|
|
74
|
+
const monitorViewportResult = migrateMonitorViewport(sourceFileAfterReplacements, updatedContent, viewportServiceVars);
|
|
126
75
|
updatedContent = monitorViewportResult.content;
|
|
127
76
|
monitorViewportResult.imports.forEach((imp) => allImportsNeeded['@ethlete/core'].add(imp));
|
|
128
|
-
// Add necessary imports
|
|
129
77
|
for (const [packageName, importsSet] of Object.entries(allImportsNeeded)) {
|
|
130
78
|
if (importsSet.size > 0) {
|
|
131
79
|
const sourceFileUpdated = ts.createSourceFile(filePath, updatedContent, ts.ScriptTarget.Latest, true);
|
|
132
80
|
updatedContent = addImportsToPackage(sourceFileUpdated, updatedContent, importsSet, packageName);
|
|
133
81
|
}
|
|
134
82
|
}
|
|
135
|
-
// Remove ViewportService injection (properties and constructor params)
|
|
136
83
|
const sourceFileFinal = ts.createSourceFile(filePath, updatedContent, ts.ScriptTarget.Latest, true);
|
|
137
|
-
updatedContent = removeViewportServiceInjection(sourceFileFinal, updatedContent,
|
|
138
|
-
// Remove ViewportService import if no longer needed
|
|
84
|
+
updatedContent = removeViewportServiceInjection(sourceFileFinal, updatedContent, serviceName, filePath);
|
|
139
85
|
const sourceFileAfterRemoval = ts.createSourceFile(filePath, updatedContent, ts.ScriptTarget.Latest, true);
|
|
140
|
-
if (
|
|
141
|
-
|
|
86
|
+
if (isServiceStillUsed(sourceFileAfterRemoval, serviceName, serviceClasses)) {
|
|
87
|
+
review.push(`${filePath}: ViewportService is still used. Migrate the remaining usages manually.`);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
updatedContent = removeServiceImport(sourceFileAfterRemoval, updatedContent, 'ViewportService');
|
|
142
91
|
}
|
|
143
|
-
// Clean up unused imports (toSignal, toObservable)
|
|
144
92
|
const sourceFileAfterCleanup = ts.createSourceFile(filePath, updatedContent, ts.ScriptTarget.Latest, true);
|
|
145
93
|
updatedContent = removeUnusedImports(sourceFileAfterCleanup, updatedContent);
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
updatedSourceFileForSignals.forEachChild((node) => {
|
|
149
|
-
if (ts.isClassDeclaration(node)) {
|
|
150
|
-
node.members.forEach((member) => {
|
|
151
|
-
if (ts.isPropertyDeclaration(member) && ts.isIdentifier(member.name)) {
|
|
152
|
-
const memberName = member.name.text;
|
|
153
|
-
const isPublicOrProtected = !member.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.PrivateKeyword);
|
|
154
|
-
if (isPublicOrProtected && member.initializer) {
|
|
155
|
-
const initText = member.initializer.getText(updatedSourceFileForSignals);
|
|
156
|
-
// Check if this property uses any of our inject functions (which return signals)
|
|
157
|
-
const usesSignalInject = initText.includes('injectIsXs()') ||
|
|
158
|
-
initText.includes('injectIsSm()') ||
|
|
159
|
-
initText.includes('injectIsMd()') ||
|
|
160
|
-
initText.includes('injectIsLg()') ||
|
|
161
|
-
initText.includes('injectIsXl()') ||
|
|
162
|
-
initText.includes('injectIs2Xl()') ||
|
|
163
|
-
initText.includes('injectIsBase()') ||
|
|
164
|
-
initText.includes('injectCurrentBreakpoint()') ||
|
|
165
|
-
initText.includes('injectViewportDimensions()') ||
|
|
166
|
-
initText.includes('injectScrollbarDimensions()') ||
|
|
167
|
-
initText.includes('injectObserveBreakpoint()') ||
|
|
168
|
-
initText.includes('injectBreakpointIsMatched()');
|
|
169
|
-
if (usesSignalInject && !initText.includes('toObservable(')) {
|
|
170
|
-
templateMigrationInfo.signalProperties.add(memberName);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
// Only add to componentTemplateMigrations if there's an external template
|
|
178
|
-
if (templatePath && templateMigrationInfo.signalProperties.size > 0) {
|
|
179
|
-
componentTemplateMigrations.set(filePath, templateMigrationInfo);
|
|
180
|
-
}
|
|
181
|
-
const updatedSourceFileForInline = ts.createSourceFile(filePath, updatedContent, ts.ScriptTarget.Latest, true);
|
|
94
|
+
const sourceFileForTemplates = ts.createSourceFile(filePath, updatedContent, ts.ScriptTarget.Latest, true);
|
|
95
|
+
const originalClasses = collectClasses(sourceFile);
|
|
182
96
|
const inlineTemplateEdits = [];
|
|
183
|
-
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
// Remove the surrounding quotes/backticks
|
|
200
|
-
templateText = templateText.slice(1, -1);
|
|
201
|
-
templateText = migrateTemplateText(templateText, templateMigrationInfo.signalProperties);
|
|
202
|
-
// Re-wrap with original quotes/backticks
|
|
203
|
-
const quote = prop.initializer.getText()[0];
|
|
204
|
-
const newInitializer = quote + templateText + quote;
|
|
205
|
-
inlineTemplateEdits.push({
|
|
206
|
-
start: prop.initializer.getStart(updatedSourceFileForInline),
|
|
207
|
-
end: prop.initializer.getEnd(),
|
|
208
|
-
replacement: newInitializer,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
});
|
|
97
|
+
collectClasses(sourceFileForTemplates).forEach((classNode, index) => {
|
|
98
|
+
if (originalClasses[index]?.getText(sourceFile) === classNode.getText(sourceFileForTemplates))
|
|
99
|
+
return;
|
|
100
|
+
const signalProperties = collectSignalProperties(sourceFileForTemplates, classNode);
|
|
101
|
+
if (signalProperties.size === 0)
|
|
102
|
+
return;
|
|
103
|
+
const template = findComponentTemplate(classNode);
|
|
104
|
+
if (!template)
|
|
105
|
+
return;
|
|
106
|
+
if (template.kind === 'url') {
|
|
107
|
+
const componentDir = filePath.substring(0, filePath.lastIndexOf('/'));
|
|
108
|
+
const templatePath = `${componentDir}/${template.path}`.replace(/\/\.\//g, '/').replace(/\/\//g, '/');
|
|
109
|
+
const existing = externalTemplateMigrations.get(templatePath) ?? new Set();
|
|
110
|
+
signalProperties.forEach((property) => existing.add(property));
|
|
111
|
+
externalTemplateMigrations.set(templatePath, existing);
|
|
112
|
+
return;
|
|
214
113
|
}
|
|
114
|
+
const initializerText = template.initializer.getText(sourceFileForTemplates);
|
|
115
|
+
const quote = initializerText[0];
|
|
116
|
+
inlineTemplateEdits.push({
|
|
117
|
+
start: template.initializer.getStart(sourceFileForTemplates),
|
|
118
|
+
end: template.initializer.getEnd(),
|
|
119
|
+
replacement: quote + migrateTemplateText(initializerText.slice(1, -1), signalProperties) + quote,
|
|
120
|
+
});
|
|
215
121
|
});
|
|
216
122
|
inlineTemplateEdits.sort((a, b) => b.start - a.start);
|
|
217
123
|
for (const edit of inlineTemplateEdits) {
|
|
218
124
|
updatedContent = updatedContent.slice(0, edit.start) + edit.replacement + updatedContent.slice(edit.end);
|
|
219
125
|
}
|
|
220
|
-
// Write the updated content if changes were made
|
|
221
126
|
if (updatedContent !== content) {
|
|
222
127
|
tree.write(filePath, updatedContent);
|
|
223
128
|
filesModified++;
|
|
224
129
|
}
|
|
225
130
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const templatePath = findTemplateForComponent(tree, tsFilePath, sourceFile);
|
|
230
|
-
if (templatePath && tree.exists(templatePath)) {
|
|
231
|
-
logger.log(`Processing template: ${templatePath}`);
|
|
232
|
-
const wasModified = migrateTemplateFile(tree, templatePath, migrationInfo);
|
|
233
|
-
if (wasModified) {
|
|
234
|
-
templatesModified++;
|
|
235
|
-
}
|
|
131
|
+
for (const [templatePath, signalProperties] of externalTemplateMigrations) {
|
|
132
|
+
if (tree.exists(templatePath) && migrateTemplateFile(tree, templatePath, { signalProperties })) {
|
|
133
|
+
templatesModified++;
|
|
236
134
|
}
|
|
237
135
|
}
|
|
238
|
-
|
|
239
|
-
if (filesModified > 0 || templatesModified > 0) {
|
|
240
|
-
logger.log(`\nā
Successfully migrated ViewportService in ${filesModified} TypeScript file(s) and ${templatesModified} template(s)\n`);
|
|
241
|
-
}
|
|
242
|
-
else if (viewportServiceUsed) {
|
|
243
|
-
logger.log('\nā¹ļø ViewportService detected but no migrations needed\n');
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
logger.log('\nā¹ļø No ViewportService usage found\n');
|
|
247
|
-
}
|
|
136
|
+
return { filesChanged: filesModified + templatesModified, review };
|
|
248
137
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
138
|
+
const SIGNAL_INJECT_CALLS = [
|
|
139
|
+
'injectIsXs()',
|
|
140
|
+
'injectIsSm()',
|
|
141
|
+
'injectIsMd()',
|
|
142
|
+
'injectIsLg()',
|
|
143
|
+
'injectIsXl()',
|
|
144
|
+
'injectIs2Xl()',
|
|
145
|
+
'injectIsBase()',
|
|
146
|
+
'injectCurrentBreakpoint()',
|
|
147
|
+
'injectViewportDimensions()',
|
|
148
|
+
'injectScrollbarDimensions()',
|
|
149
|
+
'injectObserveBreakpoint()',
|
|
150
|
+
'injectBreakpointIsMatched()',
|
|
151
|
+
];
|
|
152
|
+
function collectSignalProperties(sourceFile, classNode) {
|
|
153
|
+
const signalProperties = new Set();
|
|
154
|
+
for (const member of classNode.members) {
|
|
155
|
+
if (!ts.isPropertyDeclaration(member) || !ts.isIdentifier(member.name) || !member.initializer)
|
|
156
|
+
continue;
|
|
157
|
+
if (member.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.PrivateKeyword))
|
|
158
|
+
continue;
|
|
159
|
+
const initText = member.initializer.getText(sourceFile);
|
|
160
|
+
if (SIGNAL_INJECT_CALLS.some((call) => initText.includes(call)) && !initText.includes('toObservable(')) {
|
|
161
|
+
signalProperties.add(member.name.text);
|
|
265
162
|
}
|
|
266
163
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
164
|
+
return signalProperties;
|
|
165
|
+
}
|
|
166
|
+
function findComponentTemplate(classNode) {
|
|
167
|
+
for (const decorator of ts.getDecorators(classNode) ?? []) {
|
|
168
|
+
const metadata = ts.isCallExpression(decorator.expression) ? decorator.expression.arguments[0] : undefined;
|
|
169
|
+
if (!metadata || !ts.isObjectLiteralExpression(metadata))
|
|
271
170
|
continue;
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
171
|
+
for (const prop of metadata.properties) {
|
|
172
|
+
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
|
|
173
|
+
continue;
|
|
174
|
+
if (!ts.isNoSubstitutionTemplateLiteral(prop.initializer) && !ts.isStringLiteral(prop.initializer))
|
|
175
|
+
continue;
|
|
176
|
+
if (prop.name.text === 'template')
|
|
177
|
+
return { kind: 'inline', initializer: prop.initializer };
|
|
178
|
+
if (prop.name.text === 'templateUrl')
|
|
179
|
+
return { kind: 'url', path: prop.initializer.text };
|
|
280
180
|
}
|
|
281
181
|
}
|
|
282
|
-
return
|
|
182
|
+
return null;
|
|
283
183
|
}
|
|
284
184
|
const migrateTemplateExpression = (expression, signalProperties) => {
|
|
285
185
|
let migrated = expression;
|
|
@@ -304,38 +204,6 @@ function migrateTemplateFile(tree, htmlFilePath, migrationInfo) {
|
|
|
304
204
|
}
|
|
305
205
|
return hasChanges;
|
|
306
206
|
}
|
|
307
|
-
function findTemplateForComponent(tree, tsFilePath, sourceFile) {
|
|
308
|
-
let templatePath = null;
|
|
309
|
-
function visit(node) {
|
|
310
|
-
if (templatePath)
|
|
311
|
-
return;
|
|
312
|
-
// Look for @Component decorator
|
|
313
|
-
if (ts.isDecorator(node)) {
|
|
314
|
-
const expression = node.expression;
|
|
315
|
-
if (ts.isCallExpression(expression)) {
|
|
316
|
-
const args = expression.arguments;
|
|
317
|
-
if (args.length > 0 && ts.isObjectLiteralExpression(args[0])) {
|
|
318
|
-
const properties = args[0].properties;
|
|
319
|
-
for (const prop of properties) {
|
|
320
|
-
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === 'templateUrl') {
|
|
321
|
-
if (ts.isStringLiteral(prop.initializer)) {
|
|
322
|
-
const templateRelativePath = prop.initializer.text;
|
|
323
|
-
// Resolve the template path relative to the component file
|
|
324
|
-
const componentDir = tsFilePath.substring(0, tsFilePath.lastIndexOf('/'));
|
|
325
|
-
templatePath = `${componentDir}/${templateRelativePath}`;
|
|
326
|
-
// Normalize the path
|
|
327
|
-
templatePath = templatePath.replace(/\/\.\//g, '/').replace(/\/\//g, '/');
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
ts.forEachChild(node, visit);
|
|
335
|
-
}
|
|
336
|
-
visit(sourceFile);
|
|
337
|
-
return templatePath;
|
|
338
|
-
}
|
|
339
207
|
function getViewportServicePackage(sourceFile) {
|
|
340
208
|
let packageName = null;
|
|
341
209
|
sourceFile.forEachChild((node) => {
|
|
@@ -501,7 +369,7 @@ function analyzeClassMigration(sourceFile, classNode, viewportServiceVar) {
|
|
|
501
369
|
});
|
|
502
370
|
// Group property initializer usages by their ViewportService usage pattern
|
|
503
371
|
const propertyInitializerUsages = new Map();
|
|
504
|
-
for (const [
|
|
372
|
+
for (const [, { propertyName, usage, propertyDecl }] of usagesInPropertyInitializers) {
|
|
505
373
|
let injectFn;
|
|
506
374
|
let args;
|
|
507
375
|
let type = 'signal';
|
|
@@ -569,7 +437,7 @@ function analyzeClassMigration(sourceFile, classNode, viewportServiceVar) {
|
|
|
569
437
|
});
|
|
570
438
|
if (isUsedOutsideInitializers && type === 'observable') {
|
|
571
439
|
// Create a shared member for all properties that use this observable
|
|
572
|
-
const baseName = args ? generateNameFromArgs(
|
|
440
|
+
const baseName = args ? generateNameFromArgs(args, type) : usageKey;
|
|
573
441
|
const memberName = findAvailableMemberName(baseName, context.existingMembers);
|
|
574
442
|
const memberInfo = {
|
|
575
443
|
name: memberName,
|
|
@@ -600,7 +468,7 @@ function analyzeClassMigration(sourceFile, classNode, viewportServiceVar) {
|
|
|
600
468
|
// OR if the property is used elsewhere in the class
|
|
601
469
|
const propertyName = ts.isIdentifier(propertyDecl.name) ? propertyDecl.name.text : '';
|
|
602
470
|
const isObservableProperty = propertyName.endsWith('$');
|
|
603
|
-
const isPropertyUsedElsewhere = checkIfPropertyIsUsedElsewhere(
|
|
471
|
+
const isPropertyUsedElsewhere = checkIfPropertyIsUsedElsewhere(classNode, propertyName, propertyDecl);
|
|
604
472
|
// Wrap with toObservable if:
|
|
605
473
|
// 1. It's an observable type AND
|
|
606
474
|
// 2. (Property name ends with $ OR property is used elsewhere)
|
|
@@ -719,7 +587,7 @@ function analyzeClassMigration(sourceFile, classNode, viewportServiceVar) {
|
|
|
719
587
|
else {
|
|
720
588
|
let baseName;
|
|
721
589
|
if (usage.args) {
|
|
722
|
-
baseName = generateNameFromArgs(
|
|
590
|
+
baseName = generateNameFromArgs(usage.args, usage.type);
|
|
723
591
|
}
|
|
724
592
|
else {
|
|
725
593
|
baseName = usage.type === 'observable' ? originalProperty : originalProperty.replace(/\$$/, '');
|
|
@@ -844,7 +712,7 @@ function addMembersToClass(sourceFile, content, classNode, membersToAdd, viewpor
|
|
|
844
712
|
const classText = classNode.getText(sourceFile);
|
|
845
713
|
const openBraceIndex = classText.indexOf('{');
|
|
846
714
|
if (openBraceIndex === -1) {
|
|
847
|
-
|
|
715
|
+
logger.warn('Could not find class body opening brace');
|
|
848
716
|
return content;
|
|
849
717
|
}
|
|
850
718
|
const classStart = classNode.getStart(sourceFile);
|
|
@@ -890,70 +758,57 @@ function addMembersToClass(sourceFile, content, classNode, membersToAdd, viewpor
|
|
|
890
758
|
}
|
|
891
759
|
return content.slice(0, finalInsertPosition) + prefix + newMembers + suffix + content.slice(finalInsertPosition);
|
|
892
760
|
}
|
|
893
|
-
function removeViewportServiceInjection(sourceFile, content,
|
|
761
|
+
function removeViewportServiceInjection(sourceFile, content, serviceName, filePath) {
|
|
894
762
|
let updatedContent = content;
|
|
895
763
|
const modifications = [];
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
// Find the line boundaries
|
|
907
|
-
let lineStart = memberStart;
|
|
908
|
-
while (lineStart > 0 && content[lineStart - 1] !== '\n') {
|
|
909
|
-
lineStart--;
|
|
910
|
-
}
|
|
911
|
-
let lineEnd = memberEnd;
|
|
912
|
-
while (lineEnd < content.length && content[lineEnd] !== '\n') {
|
|
913
|
-
lineEnd++;
|
|
914
|
-
}
|
|
915
|
-
if (content[lineEnd] === '\n') {
|
|
916
|
-
lineEnd++;
|
|
917
|
-
}
|
|
918
|
-
modifications.push({ start: lineStart, end: lineEnd, replacement: '' });
|
|
764
|
+
collectClasses(sourceFile).forEach((node) => {
|
|
765
|
+
node.members.forEach((member) => {
|
|
766
|
+
if (ts.isPropertyDeclaration(member) && ts.isIdentifier(member.name)) {
|
|
767
|
+
if (isServiceProperty(member, serviceName)) {
|
|
768
|
+
const memberStart = member.getStart(sourceFile, true);
|
|
769
|
+
const memberEnd = member.getEnd();
|
|
770
|
+
// Find the line boundaries
|
|
771
|
+
let lineStart = memberStart;
|
|
772
|
+
while (lineStart > 0 && content[lineStart - 1] !== '\n') {
|
|
773
|
+
lineStart--;
|
|
919
774
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
node.members.forEach((member) => {
|
|
924
|
-
if (ts.isConstructorDeclaration(member) && member.parameters.length > 0) {
|
|
925
|
-
const newParams = [];
|
|
926
|
-
member.parameters.forEach((param) => {
|
|
927
|
-
if (ts.isIdentifier(param.name)) {
|
|
928
|
-
const paramName = param.name.text;
|
|
929
|
-
// Skip ViewportService parameters
|
|
930
|
-
if (viewportServiceVars.includes(paramName)) {
|
|
931
|
-
return;
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
// Keep this parameter
|
|
935
|
-
newParams.push(param.getText(sourceFile));
|
|
936
|
-
});
|
|
937
|
-
// Find parameter list boundaries
|
|
938
|
-
const constructorText = member.getText(sourceFile);
|
|
939
|
-
const openParenIndex = constructorText.indexOf('(');
|
|
940
|
-
const closeParenIndex = findMatchingParen(constructorText, openParenIndex);
|
|
941
|
-
if (openParenIndex === -1 || closeParenIndex === -1) {
|
|
942
|
-
console.warn(`Could not find constructor parameters in ${filePath}`);
|
|
943
|
-
return;
|
|
775
|
+
let lineEnd = memberEnd;
|
|
776
|
+
while (lineEnd < content.length && content[lineEnd] !== '\n') {
|
|
777
|
+
lineEnd++;
|
|
944
778
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
modifications.push({
|
|
950
|
-
start: paramListStart,
|
|
951
|
-
end: paramListEnd,
|
|
952
|
-
replacement: newParamsText,
|
|
953
|
-
});
|
|
779
|
+
if (content[lineEnd] === '\n') {
|
|
780
|
+
lineEnd++;
|
|
781
|
+
}
|
|
782
|
+
modifications.push({ start: lineStart, end: lineEnd, replacement: '' });
|
|
954
783
|
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
// Collect constructor parameter modifications
|
|
787
|
+
node.members.forEach((member) => {
|
|
788
|
+
if (ts.isConstructorDeclaration(member) &&
|
|
789
|
+
member.parameters.some((param) => isServiceParameter(param, serviceName))) {
|
|
790
|
+
const newParams = member.parameters
|
|
791
|
+
.filter((param) => !isServiceParameter(param, serviceName))
|
|
792
|
+
.map((param) => param.getText(sourceFile));
|
|
793
|
+
// Find parameter list boundaries
|
|
794
|
+
const constructorText = member.getText(sourceFile);
|
|
795
|
+
const openParenIndex = constructorText.indexOf('(');
|
|
796
|
+
const closeParenIndex = findMatchingParen(constructorText, openParenIndex);
|
|
797
|
+
if (openParenIndex === -1 || closeParenIndex === -1) {
|
|
798
|
+
logger.warn(`Could not find constructor parameters in ${filePath}`);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
const constructorStart = member.getStart(sourceFile);
|
|
802
|
+
const paramListStart = constructorStart + openParenIndex + 1;
|
|
803
|
+
const paramListEnd = constructorStart + closeParenIndex;
|
|
804
|
+
const newParamsText = newParams.join(', ');
|
|
805
|
+
modifications.push({
|
|
806
|
+
start: paramListStart,
|
|
807
|
+
end: paramListEnd,
|
|
808
|
+
replacement: newParamsText,
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
});
|
|
957
812
|
});
|
|
958
813
|
// Sort modifications by start position (descending) to apply from end to start
|
|
959
814
|
modifications.sort((a, b) => b.start - a.start);
|
|
@@ -977,7 +832,7 @@ function findMatchingParen(text, openIndex) {
|
|
|
977
832
|
}
|
|
978
833
|
return depth === 0 ? i - 1 : -1;
|
|
979
834
|
}
|
|
980
|
-
function generateNameFromArgs(
|
|
835
|
+
function generateNameFromArgs(args, type) {
|
|
981
836
|
// Parse the arguments to generate a meaningful name
|
|
982
837
|
// e.g., { min: 'lg' } => isMinLg or isMinLg$
|
|
983
838
|
// e.g., { max: 'sm' } => isMaxSm or isMaxSm$
|
|
@@ -1062,35 +917,6 @@ function createReplacementsForMember(sourceFile, classNode, viewportServiceVar,
|
|
|
1062
917
|
visitNode(member);
|
|
1063
918
|
});
|
|
1064
919
|
}
|
|
1065
|
-
function findClassForViewportService(sourceFile, viewportServiceVar) {
|
|
1066
|
-
let classNode;
|
|
1067
|
-
function visit(node) {
|
|
1068
|
-
if (classNode)
|
|
1069
|
-
return;
|
|
1070
|
-
if (ts.isClassDeclaration(node)) {
|
|
1071
|
-
// Check if this class has the viewportService member
|
|
1072
|
-
const hasMember = node.members.some((member) => {
|
|
1073
|
-
if (ts.isPropertyDeclaration(member) && ts.isIdentifier(member.name)) {
|
|
1074
|
-
return member.name.text === viewportServiceVar;
|
|
1075
|
-
}
|
|
1076
|
-
if (ts.isParameter(member) && ts.isIdentifier(member.name)) {
|
|
1077
|
-
return member.name.text === viewportServiceVar;
|
|
1078
|
-
}
|
|
1079
|
-
// Check constructor parameters
|
|
1080
|
-
if (ts.isConstructorDeclaration(member)) {
|
|
1081
|
-
return member.parameters.some((param) => ts.isIdentifier(param.name) && param.name.text === viewportServiceVar);
|
|
1082
|
-
}
|
|
1083
|
-
return false;
|
|
1084
|
-
});
|
|
1085
|
-
if (hasMember) {
|
|
1086
|
-
classNode = node;
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
ts.forEachChild(node, visit);
|
|
1090
|
-
}
|
|
1091
|
-
visit(sourceFile);
|
|
1092
|
-
return classNode;
|
|
1093
|
-
}
|
|
1094
920
|
function findAvailableMemberName(baseName, existingMembers) {
|
|
1095
921
|
// baseName already has $ suffix for observables at this point
|
|
1096
922
|
// Try variations: name, _name, #name
|
|
@@ -1107,7 +933,7 @@ function findAvailableMemberName(baseName, existingMembers) {
|
|
|
1107
933
|
}
|
|
1108
934
|
return `${baseName}${counter}`;
|
|
1109
935
|
}
|
|
1110
|
-
function migrateMonitorViewport(sourceFile, content, viewportServiceVars
|
|
936
|
+
function migrateMonitorViewport(sourceFile, content, viewportServiceVars) {
|
|
1111
937
|
let updatedContent = content;
|
|
1112
938
|
const imports = [];
|
|
1113
939
|
const monitorViewportCalls = [];
|
|
@@ -1212,7 +1038,7 @@ function migrateMonitorViewport(sourceFile, content, viewportServiceVars, cssVar
|
|
|
1212
1038
|
const classText = classNode.getText(sourceFile);
|
|
1213
1039
|
const openBraceIndex = classText.indexOf('{');
|
|
1214
1040
|
if (openBraceIndex === -1) {
|
|
1215
|
-
|
|
1041
|
+
logger.warn('Could not find class body opening brace');
|
|
1216
1042
|
continue;
|
|
1217
1043
|
}
|
|
1218
1044
|
const insertPosition = classStart + openBraceIndex + 1;
|
|
@@ -1266,7 +1092,7 @@ function addImportsToPackage(sourceFile, content, neededImports, packageName) {
|
|
|
1266
1092
|
const allImports = [...existingImportNames, ...newImports].sort();
|
|
1267
1093
|
const importText = existingImport.getText(sourceFile);
|
|
1268
1094
|
const newImportText = `import { ${allImports.join(', ')} } from '${packageName}';`;
|
|
1269
|
-
return content.replace(importText, newImportText);
|
|
1095
|
+
return content.replace(importText, () => newImportText);
|
|
1270
1096
|
}
|
|
1271
1097
|
}
|
|
1272
1098
|
else if (!existingImport) {
|
|
@@ -1277,87 +1103,7 @@ function addImportsToPackage(sourceFile, content, neededImports, packageName) {
|
|
|
1277
1103
|
}
|
|
1278
1104
|
return content;
|
|
1279
1105
|
}
|
|
1280
|
-
function
|
|
1281
|
-
let stillUsed = false;
|
|
1282
|
-
function visit(node) {
|
|
1283
|
-
if (stillUsed || !ts.isPropertyAccessExpression(node)) {
|
|
1284
|
-
if (!stillUsed)
|
|
1285
|
-
ts.forEachChild(node, visit);
|
|
1286
|
-
return;
|
|
1287
|
-
}
|
|
1288
|
-
const expression = node.expression;
|
|
1289
|
-
const isUsed = (ts.isPropertyAccessExpression(expression) && viewportServiceVars.includes(expression.name.text)) ||
|
|
1290
|
-
(ts.isIdentifier(expression) && viewportServiceVars.includes(expression.text));
|
|
1291
|
-
if (isUsed) {
|
|
1292
|
-
stillUsed = true;
|
|
1293
|
-
}
|
|
1294
|
-
else {
|
|
1295
|
-
ts.forEachChild(node, visit);
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
visit(sourceFile);
|
|
1299
|
-
return stillUsed;
|
|
1300
|
-
}
|
|
1301
|
-
function removeViewportServiceImport(sourceFile, content) {
|
|
1302
|
-
let updatedContent = content;
|
|
1303
|
-
sourceFile.forEachChild((node) => {
|
|
1304
|
-
if (ts.isImportDeclaration(node)) {
|
|
1305
|
-
const moduleSpecifier = node.moduleSpecifier;
|
|
1306
|
-
if (ts.isStringLiteral(moduleSpecifier)) {
|
|
1307
|
-
const importPath = moduleSpecifier.text;
|
|
1308
|
-
// Check if this import contains ViewportService from any package
|
|
1309
|
-
// (e.g., '@ethlete/core' or '@fifa-gg/uikit/core')
|
|
1310
|
-
if (!node.importClause?.namedBindings)
|
|
1311
|
-
return;
|
|
1312
|
-
if (ts.isNamedImports(node.importClause.namedBindings)) {
|
|
1313
|
-
const imports = node.importClause.namedBindings.elements;
|
|
1314
|
-
const viewportServiceImport = imports.find((imp) => ts.isImportSpecifier(imp) && imp.name.text === 'ViewportService');
|
|
1315
|
-
if (!viewportServiceImport)
|
|
1316
|
-
return;
|
|
1317
|
-
// If ViewportService is the only import, remove the entire import statement
|
|
1318
|
-
if (imports.length === 1) {
|
|
1319
|
-
const importText = node.getText(sourceFile);
|
|
1320
|
-
const importStart = node.getStart(sourceFile);
|
|
1321
|
-
const importEnd = node.getEnd();
|
|
1322
|
-
// Find the end of the line (including newline)
|
|
1323
|
-
let lineEnd = importEnd;
|
|
1324
|
-
while (lineEnd < content.length && content[lineEnd] !== '\n') {
|
|
1325
|
-
lineEnd++;
|
|
1326
|
-
}
|
|
1327
|
-
if (content[lineEnd] === '\n') {
|
|
1328
|
-
lineEnd++;
|
|
1329
|
-
}
|
|
1330
|
-
updatedContent = content.slice(0, importStart) + content.slice(lineEnd);
|
|
1331
|
-
}
|
|
1332
|
-
else {
|
|
1333
|
-
// Remove only ViewportService from the named imports
|
|
1334
|
-
const viewportServiceText = viewportServiceImport.getText(sourceFile);
|
|
1335
|
-
const namedImportsText = node.importClause.namedBindings.getText(sourceFile);
|
|
1336
|
-
// Handle different formatting cases
|
|
1337
|
-
const patterns = [
|
|
1338
|
-
new RegExp(`ViewportService,\\s*`, 'g'), // ViewportService at start/middle
|
|
1339
|
-
new RegExp(`,\\s*ViewportService`, 'g'), // ViewportService at end
|
|
1340
|
-
new RegExp(`\\s*ViewportService\\s*`, 'g'), // ViewportService alone
|
|
1341
|
-
];
|
|
1342
|
-
let newNamedImports = namedImportsText;
|
|
1343
|
-
for (const pattern of patterns) {
|
|
1344
|
-
const temp = newNamedImports.replace(pattern, '');
|
|
1345
|
-
if (temp !== newNamedImports) {
|
|
1346
|
-
newNamedImports = temp;
|
|
1347
|
-
break;
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
// Clean up any double commas or spaces
|
|
1351
|
-
newNamedImports = newNamedImports.replace(/,\s*,/g, ',').replace(/{\s*,/g, '{').replace(/,\s*}/g, '}');
|
|
1352
|
-
updatedContent = updatedContent.replace(namedImportsText, newNamedImports);
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
});
|
|
1358
|
-
return updatedContent;
|
|
1359
|
-
}
|
|
1360
|
-
function findViewportServiceVariables(sourceFile) {
|
|
1106
|
+
function findViewportServiceVariables(sourceFile, serviceName) {
|
|
1361
1107
|
const variables = [];
|
|
1362
1108
|
function visit(node) {
|
|
1363
1109
|
// Check for inject(ViewportService) pattern
|
|
@@ -1366,7 +1112,7 @@ function findViewportServiceVariables(sourceFile) {
|
|
|
1366
1112
|
node.expression.text === 'inject' &&
|
|
1367
1113
|
node.arguments.length > 0) {
|
|
1368
1114
|
const arg = node.arguments[0];
|
|
1369
|
-
if (ts.isIdentifier(arg) && arg.text ===
|
|
1115
|
+
if (ts.isIdentifier(arg) && arg.text === serviceName) {
|
|
1370
1116
|
// Find the variable name
|
|
1371
1117
|
let parent = node.parent;
|
|
1372
1118
|
while (parent) {
|
|
@@ -1386,7 +1132,7 @@ function findViewportServiceVariables(sourceFile) {
|
|
|
1386
1132
|
if (ts.isParameter(node)) {
|
|
1387
1133
|
const typeNode = node.type;
|
|
1388
1134
|
if (typeNode && ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
|
|
1389
|
-
if (typeNode.typeName.text ===
|
|
1135
|
+
if (typeNode.typeName.text === serviceName && node.name && ts.isIdentifier(node.name)) {
|
|
1390
1136
|
variables.push(node.name.text);
|
|
1391
1137
|
}
|
|
1392
1138
|
}
|
|
@@ -1396,9 +1142,9 @@ function findViewportServiceVariables(sourceFile) {
|
|
|
1396
1142
|
visit(sourceFile);
|
|
1397
1143
|
return variables;
|
|
1398
1144
|
}
|
|
1399
|
-
function handleInlineInjectPatterns(sourceFile, content) {
|
|
1145
|
+
function handleInlineInjectPatterns(sourceFile, content, serviceName) {
|
|
1400
1146
|
const importsNeeded = new Set();
|
|
1401
|
-
|
|
1147
|
+
const edits = [];
|
|
1402
1148
|
// Get the ViewportService package to determine mapping
|
|
1403
1149
|
const packageName = getViewportServicePackage(sourceFile);
|
|
1404
1150
|
const { signalPropertyMap, observablePropertyMap } = getPropertyMaps(packageName);
|
|
@@ -1432,12 +1178,11 @@ function handleInlineInjectPatterns(sourceFile, content) {
|
|
|
1432
1178
|
arg.expression.expression.text === 'inject' &&
|
|
1433
1179
|
arg.expression.arguments.length > 0) {
|
|
1434
1180
|
const injectArg = arg.expression.arguments[0];
|
|
1435
|
-
if (ts.isIdentifier(injectArg) && injectArg.text ===
|
|
1181
|
+
if (ts.isIdentifier(injectArg) && injectArg.text === serviceName) {
|
|
1436
1182
|
const injectFn = signalPropertyMap[propertyName] || observablePropertyMap[propertyName];
|
|
1437
1183
|
if (injectFn) {
|
|
1438
|
-
const oldText = node.getText(sourceFile);
|
|
1439
1184
|
const newText = `${injectFn}()`;
|
|
1440
|
-
|
|
1185
|
+
edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), text: newText });
|
|
1441
1186
|
importsNeeded.add(injectFn);
|
|
1442
1187
|
processedNodes.add(arg);
|
|
1443
1188
|
}
|
|
@@ -1454,13 +1199,12 @@ function handleInlineInjectPatterns(sourceFile, content) {
|
|
|
1454
1199
|
arg.expression.expression.expression.text === 'inject' &&
|
|
1455
1200
|
arg.expression.expression.arguments.length > 0) {
|
|
1456
1201
|
const injectArg = arg.expression.expression.arguments[0];
|
|
1457
|
-
if (ts.isIdentifier(injectArg) && injectArg.text ===
|
|
1458
|
-
const oldText = node.getText(sourceFile);
|
|
1202
|
+
if (ts.isIdentifier(injectArg) && injectArg.text === serviceName) {
|
|
1459
1203
|
// Extract arguments from the method call
|
|
1460
1204
|
const args = arg.arguments.map((a) => a.getText(sourceFile)).join(', ');
|
|
1461
1205
|
// Since it's wrapped in toSignal and our inject functions return signals, remove toSignal
|
|
1462
1206
|
const newText = `${methodInfo.injectFn}(${args})`;
|
|
1463
|
-
|
|
1207
|
+
edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), text: newText });
|
|
1464
1208
|
importsNeeded.add(methodInfo.injectFn);
|
|
1465
1209
|
processedNodes.add(arg);
|
|
1466
1210
|
}
|
|
@@ -1481,12 +1225,11 @@ function handleInlineInjectPatterns(sourceFile, content) {
|
|
|
1481
1225
|
node.expression.expression.expression.text === 'inject' &&
|
|
1482
1226
|
node.expression.expression.arguments.length > 0) {
|
|
1483
1227
|
const injectArg = node.expression.expression.arguments[0];
|
|
1484
|
-
if (ts.isIdentifier(injectArg) && injectArg.text ===
|
|
1228
|
+
if (ts.isIdentifier(injectArg) && injectArg.text === serviceName) {
|
|
1485
1229
|
if (isInsideToSignal(node)) {
|
|
1486
1230
|
ts.forEachChild(node, visitNode);
|
|
1487
1231
|
return;
|
|
1488
1232
|
}
|
|
1489
|
-
const oldText = node.getText(sourceFile);
|
|
1490
1233
|
const args = node.arguments.map((arg) => arg.getText(sourceFile)).join(', ');
|
|
1491
1234
|
// Only wrap with toObservable if the assigned variable/property ends with $
|
|
1492
1235
|
let needsToObservable = false;
|
|
@@ -1500,7 +1243,7 @@ function handleInlineInjectPatterns(sourceFile, content) {
|
|
|
1500
1243
|
newText = `toObservable(${newText})`;
|
|
1501
1244
|
importsNeeded.add('toObservable');
|
|
1502
1245
|
}
|
|
1503
|
-
|
|
1246
|
+
edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), text: newText });
|
|
1504
1247
|
importsNeeded.add(methodInfo.injectFn);
|
|
1505
1248
|
}
|
|
1506
1249
|
}
|
|
@@ -1517,14 +1260,13 @@ function handleInlineInjectPatterns(sourceFile, content) {
|
|
|
1517
1260
|
node.expression.expression.text === 'inject' &&
|
|
1518
1261
|
node.expression.arguments.length > 0) {
|
|
1519
1262
|
const injectArg = node.expression.arguments[0];
|
|
1520
|
-
if (ts.isIdentifier(injectArg) && injectArg.text ===
|
|
1263
|
+
if (ts.isIdentifier(injectArg) && injectArg.text === serviceName) {
|
|
1521
1264
|
const injectFn = signalPropertyMap[propertyName] || observablePropertyMap[propertyName];
|
|
1522
1265
|
if (injectFn) {
|
|
1523
1266
|
if (isInsideToSignal(node)) {
|
|
1524
1267
|
ts.forEachChild(node, visitNode);
|
|
1525
1268
|
return;
|
|
1526
1269
|
}
|
|
1527
|
-
const oldText = node.getText(sourceFile);
|
|
1528
1270
|
// Check if this is an observable property (ends with $)
|
|
1529
1271
|
const isObservable = !!observablePropertyMap[propertyName];
|
|
1530
1272
|
let newText = `${injectFn}()`;
|
|
@@ -1532,7 +1274,7 @@ function handleInlineInjectPatterns(sourceFile, content) {
|
|
|
1532
1274
|
newText = `toObservable(${newText})`;
|
|
1533
1275
|
importsNeeded.add('toObservable');
|
|
1534
1276
|
}
|
|
1535
|
-
|
|
1277
|
+
edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), text: newText });
|
|
1536
1278
|
importsNeeded.add(injectFn);
|
|
1537
1279
|
}
|
|
1538
1280
|
}
|
|
@@ -1541,7 +1283,7 @@ function handleInlineInjectPatterns(sourceFile, content) {
|
|
|
1541
1283
|
ts.forEachChild(node, visitNode);
|
|
1542
1284
|
}
|
|
1543
1285
|
sourceFile.forEachChild(visitNode);
|
|
1544
|
-
return { content:
|
|
1286
|
+
return { content: applyInlineEdits(content, edits), importsNeeded };
|
|
1545
1287
|
}
|
|
1546
1288
|
function removeUnusedImports(sourceFile, content) {
|
|
1547
1289
|
const importsToCheck = [
|
|
@@ -1551,14 +1293,14 @@ function removeUnusedImports(sourceFile, content) {
|
|
|
1551
1293
|
let updatedContent = content;
|
|
1552
1294
|
for (const importToCheck of importsToCheck) {
|
|
1553
1295
|
// Check if the import specifier is used anywhere in the code
|
|
1554
|
-
const isUsed = checkIfImportIsUsed(sourceFile,
|
|
1296
|
+
const isUsed = checkIfImportIsUsed(sourceFile, importToCheck.specifier);
|
|
1555
1297
|
if (!isUsed) {
|
|
1556
1298
|
updatedContent = removeImportSpecifier(sourceFile, updatedContent, importToCheck.specifier, importToCheck.package);
|
|
1557
1299
|
}
|
|
1558
1300
|
}
|
|
1559
1301
|
return updatedContent;
|
|
1560
1302
|
}
|
|
1561
|
-
function checkIfImportIsUsed(sourceFile,
|
|
1303
|
+
function checkIfImportIsUsed(sourceFile, specifier) {
|
|
1562
1304
|
let isUsed = false;
|
|
1563
1305
|
function visit(node) {
|
|
1564
1306
|
if (isUsed)
|
|
@@ -1623,7 +1365,7 @@ function removeImportSpecifier(sourceFile, content, specifier, packageName) {
|
|
|
1623
1365
|
});
|
|
1624
1366
|
return updatedContent;
|
|
1625
1367
|
}
|
|
1626
|
-
function checkIfPropertyIsUsedElsewhere(
|
|
1368
|
+
function checkIfPropertyIsUsedElsewhere(classNode, propertyName, propertyDecl) {
|
|
1627
1369
|
let isUsed = false;
|
|
1628
1370
|
function visit(node) {
|
|
1629
1371
|
if (isUsed)
|
|
@@ -1657,3 +1399,13 @@ function checkIfPropertyIsUsedElsewhere(sourceFile, classNode, propertyName, pro
|
|
|
1657
1399
|
});
|
|
1658
1400
|
return isUsed;
|
|
1659
1401
|
}
|
|
1402
|
+
function applyInlineEdits(content, edits) {
|
|
1403
|
+
const accepted = [];
|
|
1404
|
+
for (const edit of [...edits].sort((a, b) => a.start - b.start || b.end - a.end)) {
|
|
1405
|
+
const last = accepted.at(-1);
|
|
1406
|
+
if (last && edit.start < last.end)
|
|
1407
|
+
continue;
|
|
1408
|
+
accepted.push(edit);
|
|
1409
|
+
}
|
|
1410
|
+
return accepted.reduceRight((result, edit) => result.slice(0, edit.start) + edit.text + result.slice(edit.end), content);
|
|
1411
|
+
}
|