Skip to content

Commit 8d5b8ae

Browse files
authored
fix: migrate angular app config (#27)
Resolves #25
1 parent 56ea6e2 commit 8d5b8ae

File tree

5 files changed

+217
-28
lines changed

5 files changed

+217
-28
lines changed

packages/cli/src/angular/migrations/standalone/0003-migrate-bootstrap-application.ts

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import type { ObjectLiteralExpression, Project, SourceFile } from "ts-morph";
1+
import type { ObjectLiteralExpression, Project } from "ts-morph";
22
import { SyntaxKind } from "ts-morph";
33
import type { CliOptions } from "../../../types/cli-options";
44
import { saveFileChanges } from "../../utils/log-utils";
5+
import { migrateProvideIonicAngularImportDeclarations } from "../../utils/ionic-utils";
56

67
export const migrateBootstrapApplication = async (
78
project: Project,
@@ -136,35 +137,9 @@ export const migrateBootstrapApplication = async (
136137

137138
providersArray.formatText();
138139

139-
migrateIonicAngularImportDeclarations(sourceFile);
140+
migrateProvideIonicAngularImportDeclarations(sourceFile);
140141

141142
return await saveFileChanges(sourceFile, cliOptions);
142143
}
143144
}
144145
};
145-
146-
function migrateIonicAngularImportDeclarations(sourceFile: SourceFile) {
147-
const importDeclaration = sourceFile.getImportDeclaration("@ionic/angular");
148-
149-
if (!importDeclaration) {
150-
// If the @ionic/angular import does not exist, then this is not an @ionic/angular application.
151-
// This migration only applies to @ionic/angular applications.
152-
return;
153-
}
154-
155-
// Update the import statement to import from @ionic/angular/standalone
156-
importDeclaration.setModuleSpecifier("@ionic/angular/standalone");
157-
158-
const namedImports = importDeclaration.getNamedImports();
159-
const importSpecifier = namedImports.find(
160-
(n) => n.getName() === "IonicModule",
161-
);
162-
163-
if (importSpecifier) {
164-
// Remove the IonicModule import specifier
165-
importSpecifier.remove();
166-
}
167-
168-
// Add the provideIonicAngular import specifier
169-
importDeclaration.addNamedImport("provideIonicAngular");
170-
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { describe, it, expect } from "vitest";
2+
import { Project } from "ts-morph";
3+
import { dedent } from "ts-dedent";
4+
5+
import { migrateAngularAppConfig } from "./0006-migrate-angular-app-config";
6+
7+
describe("migrateAngularAppConfig", () => {
8+
it("should migrate app.config.ts", async () => {
9+
const project = new Project({ useInMemoryFileSystem: true });
10+
11+
const appConfig = dedent(`
12+
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
13+
import { provideRouter } from '@angular/router';
14+
import { IonicModule } from '@ionic/angular';
15+
16+
import { routes } from './app.routes';
17+
18+
export const appConfig: ApplicationConfig = {
19+
providers: [provideRouter(routes), importProvidersFrom(IonicModule.forRoot())]
20+
};
21+
`);
22+
23+
const configSourceFile = project.createSourceFile(
24+
"src/app/app.config.ts",
25+
appConfig,
26+
);
27+
28+
await migrateAngularAppConfig(project, { dryRun: false });
29+
30+
expect(dedent(configSourceFile.getText())).toBe(
31+
dedent(`
32+
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
33+
import { provideRouter } from '@angular/router';
34+
import { provideIonicAngular } from '@ionic/angular/standalone';
35+
36+
import { routes } from './app.routes';
37+
38+
export const appConfig: ApplicationConfig = {
39+
providers: [provideRouter(routes), provideIonicAngular()]
40+
};
41+
`),
42+
);
43+
});
44+
});
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import type { ObjectLiteralExpression, Project, SourceFile } from "ts-morph";
2+
import { SyntaxKind } from "ts-morph";
3+
import type { CliOptions } from "../../../types/cli-options";
4+
import { saveFileChanges } from "../../utils/log-utils";
5+
import { migrateProvideIonicAngularImportDeclarations } from "../../utils/ionic-utils";
6+
7+
export const migrateAngularAppConfig = async (
8+
project: Project,
9+
cliOptions: CliOptions,
10+
) => {
11+
const sourceFile = project
12+
.getSourceFiles()
13+
.find((sourceFile) => sourceFile.getFilePath().endsWith("app.config.ts"));
14+
15+
if (sourceFile === undefined) {
16+
return;
17+
}
18+
19+
const appConfigVariableStatement = sourceFile
20+
.getVariableStatements()
21+
.find((variableStatement) => {
22+
const declarationList = variableStatement.getDeclarationList();
23+
const variableDeclaration = declarationList.getDeclarations()[0];
24+
return variableDeclaration.getName() === "appConfig";
25+
});
26+
27+
if (appConfigVariableStatement === undefined) {
28+
return;
29+
}
30+
31+
const appConfigVariableStatementDeclarationList =
32+
appConfigVariableStatement.getDeclarationList();
33+
const appConfigVariableDeclaration =
34+
appConfigVariableStatementDeclarationList.getDeclarations()[0];
35+
const appConfigInitializer = appConfigVariableDeclaration.getInitializer();
36+
if (appConfigInitializer === undefined) {
37+
return;
38+
}
39+
40+
const appConfigObjectLiteralExpression =
41+
appConfigInitializer as ObjectLiteralExpression;
42+
const providersPropertyAssignment =
43+
appConfigObjectLiteralExpression.getProperty("providers");
44+
if (providersPropertyAssignment === undefined) {
45+
return;
46+
}
47+
48+
const providersArray = providersPropertyAssignment.getFirstChildByKind(
49+
SyntaxKind.ArrayLiteralExpression,
50+
);
51+
52+
if (providersArray === undefined) {
53+
return;
54+
}
55+
56+
const importProvidersFromFunctionCall = providersArray
57+
.getChildrenOfKind(SyntaxKind.CallExpression)
58+
.find((callExpression) => {
59+
const identifier = callExpression.getFirstChildByKind(
60+
SyntaxKind.Identifier,
61+
);
62+
return (
63+
identifier !== undefined &&
64+
identifier.getText() === "importProvidersFrom"
65+
);
66+
});
67+
68+
if (importProvidersFromFunctionCall === undefined) {
69+
return;
70+
}
71+
72+
const importProvidersFromFunctionCallIdentifier =
73+
importProvidersFromFunctionCall.getFirstChildByKind(SyntaxKind.Identifier);
74+
75+
if (importProvidersFromFunctionCallIdentifier === undefined) {
76+
return;
77+
}
78+
79+
if (
80+
importProvidersFromFunctionCallIdentifier.getText() !==
81+
"importProvidersFrom"
82+
) {
83+
return;
84+
}
85+
86+
const importProvidersFromFunctionCallArguments =
87+
importProvidersFromFunctionCall.getArguments();
88+
89+
if (importProvidersFromFunctionCallArguments.length === 0) {
90+
return;
91+
}
92+
93+
const importProvidersFromFunctionCallIonicModuleForRootCallExpression =
94+
importProvidersFromFunctionCallArguments.find((argument) => {
95+
return argument.getText().includes("IonicModule.forRoot");
96+
});
97+
98+
if (
99+
importProvidersFromFunctionCallIonicModuleForRootCallExpression ===
100+
undefined
101+
) {
102+
return;
103+
}
104+
105+
if (
106+
importProvidersFromFunctionCallIonicModuleForRootCallExpression.isKind(
107+
SyntaxKind.CallExpression,
108+
)
109+
) {
110+
const importProvidersFromFunctionCallIonicModuleForRootCallExpressionArguments =
111+
importProvidersFromFunctionCallIonicModuleForRootCallExpression.getArguments();
112+
const ionicConfigObjectLiteralExpression =
113+
importProvidersFromFunctionCallIonicModuleForRootCallExpressionArguments[0];
114+
115+
const ionicConfigValue = ionicConfigObjectLiteralExpression
116+
? ionicConfigObjectLiteralExpression.getText()
117+
: "";
118+
119+
providersArray.addElement(`provideIonicAngular(${ionicConfigValue})`);
120+
121+
// Remove the IonicModule.forRoot from the importProvidersFrom function call.
122+
importProvidersFromFunctionCall.removeArgument(
123+
importProvidersFromFunctionCallIonicModuleForRootCallExpression,
124+
);
125+
126+
if (importProvidersFromFunctionCall.getArguments().length === 0) {
127+
// If there are no remaining arguments, remove the importProvidersFrom function call.
128+
providersArray.removeElement(importProvidersFromFunctionCall);
129+
}
130+
131+
providersArray.formatText();
132+
133+
migrateProvideIonicAngularImportDeclarations(sourceFile);
134+
135+
return await saveFileChanges(sourceFile, cliOptions);
136+
}
137+
};

packages/cli/src/angular/migrations/standalone/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { migrateAngularJsonAssets } from "./0005-migrate-angular-json-assets";
88

99
import { group, confirm, log, spinner } from "@clack/prompts";
1010
import { getActualPackageVersion } from "../../utils/package-utils";
11+
import { migrateAngularAppConfig } from "./0006-migrate-angular-app-config";
1112

1213
interface StandaloneMigrationOptions {
1314
/**
@@ -51,6 +52,8 @@ export const runStandaloneMigration = async ({
5152
await migrateImportStatements(project, cliOptions);
5253
// Migrate the assets array in angular.json
5354
await migrateAngularJsonAssets(project, cliOptions);
55+
// Migrate angular projects with an app config
56+
await migrateAngularAppConfig(project, cliOptions);
5457

5558
spinner.stop(`Project migration at ${dir} completed successfully.`);
5659

packages/cli/src/angular/utils/ionic-utils.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { SourceFile } from "ts-morph";
2+
13
/**
24
* List of Ionic components by tag name.
35
*/
@@ -91,3 +93,31 @@ export const IONIC_COMPONENTS = [
9193
"ion-toggle",
9294
"ion-title",
9395
]; // TODO can we generate this from @ionic/core and import it here?
96+
97+
export const migrateProvideIonicAngularImportDeclarations = (
98+
sourceFile: SourceFile,
99+
) => {
100+
const importDeclaration = sourceFile.getImportDeclaration("@ionic/angular");
101+
102+
if (!importDeclaration) {
103+
// If the @ionic/angular import does not exist, then this is not an @ionic/angular application.
104+
// This migration only applies to @ionic/angular applications.
105+
return;
106+
}
107+
108+
// Update the import statement to import from @ionic/angular/standalone
109+
importDeclaration.setModuleSpecifier("@ionic/angular/standalone");
110+
111+
const namedImports = importDeclaration.getNamedImports();
112+
const importSpecifier = namedImports.find(
113+
(n) => n.getName() === "IonicModule",
114+
);
115+
116+
if (importSpecifier) {
117+
// Remove the IonicModule import specifier
118+
importSpecifier.remove();
119+
}
120+
121+
// Add the provideIonicAngular import specifier
122+
importDeclaration.addNamedImport("provideIonicAngular");
123+
};

0 commit comments

Comments
 (0)