1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
10 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
11 | };
|
12 | Object.defineProperty(exports, "__esModule", { value: true });
|
13 | exports.findBootstrapApplicationCall = exports.addFunctionalProvidersToStandaloneBootstrap = exports.addModuleImportToStandaloneBootstrap = exports.callsProvidersFunction = exports.importsProvidersFrom = void 0;
|
14 | const schematics_1 = require("@angular-devkit/schematics");
|
15 | const path_1 = require("path");
|
16 | const typescript_1 = __importDefault(require("../third_party/github.com/Microsoft/TypeScript/lib/typescript"));
|
17 | const ast_utils_1 = require("../utility/ast-utils");
|
18 | const change_1 = require("../utility/change");
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | function importsProvidersFrom(tree, filePath, className) {
|
28 | const sourceFile = createSourceFile(tree, filePath);
|
29 | const bootstrapCall = findBootstrapApplicationCall(sourceFile);
|
30 | const appConfig = bootstrapCall ? findAppConfig(bootstrapCall, tree, filePath) : null;
|
31 | const importProvidersFromCall = appConfig ? findImportProvidersFromCall(appConfig.node) : null;
|
32 | return !!importProvidersFromCall?.arguments.some((arg) => typescript_1.default.isIdentifier(arg) && arg.text === className);
|
33 | }
|
34 | exports.importsProvidersFrom = importsProvidersFrom;
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 | function callsProvidersFunction(tree, filePath, functionName) {
|
44 | const sourceFile = createSourceFile(tree, filePath);
|
45 | const bootstrapCall = findBootstrapApplicationCall(sourceFile);
|
46 | const appConfig = bootstrapCall ? findAppConfig(bootstrapCall, tree, filePath) : null;
|
47 | const providersLiteral = appConfig ? findProvidersLiteral(appConfig.node) : null;
|
48 | return !!providersLiteral?.elements.some((el) => typescript_1.default.isCallExpression(el) &&
|
49 | typescript_1.default.isIdentifier(el.expression) &&
|
50 | el.expression.text === functionName);
|
51 | }
|
52 | exports.callsProvidersFunction = callsProvidersFunction;
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 | function addModuleImportToStandaloneBootstrap(tree, filePath, moduleName, modulePath) {
|
63 | const sourceFile = createSourceFile(tree, filePath);
|
64 | const bootstrapCall = findBootstrapApplicationCall(sourceFile);
|
65 | const addImports = (file, recorder) => {
|
66 | const sourceText = file.getText();
|
67 | [
|
68 | (0, ast_utils_1.insertImport)(file, sourceText, moduleName, modulePath),
|
69 | (0, ast_utils_1.insertImport)(file, sourceText, 'importProvidersFrom', '@angular/core'),
|
70 | ].forEach((change) => {
|
71 | if (change instanceof change_1.InsertChange) {
|
72 | recorder.insertLeft(change.pos, change.toAdd);
|
73 | }
|
74 | });
|
75 | };
|
76 | if (!bootstrapCall) {
|
77 | throw new schematics_1.SchematicsException(`Could not find bootstrapApplication call in ${filePath}`);
|
78 | }
|
79 | const importProvidersCall = typescript_1.default.factory.createCallExpression(typescript_1.default.factory.createIdentifier('importProvidersFrom'), [], [typescript_1.default.factory.createIdentifier(moduleName)]);
|
80 |
|
81 | if (bootstrapCall.arguments.length === 1) {
|
82 | const recorder = tree.beginUpdate(filePath);
|
83 | addNewAppConfigToCall(bootstrapCall, importProvidersCall, recorder);
|
84 | addImports(sourceFile, recorder);
|
85 | tree.commitUpdate(recorder);
|
86 | return;
|
87 | }
|
88 |
|
89 | if (isMergeAppConfigCall(bootstrapCall.arguments[1])) {
|
90 | const recorder = tree.beginUpdate(filePath);
|
91 | addNewAppConfigToCall(bootstrapCall.arguments[1], importProvidersCall, recorder);
|
92 | addImports(sourceFile, recorder);
|
93 | tree.commitUpdate(recorder);
|
94 | return;
|
95 | }
|
96 |
|
97 | const appConfig = findAppConfig(bootstrapCall, tree, filePath);
|
98 | if (!appConfig) {
|
99 | throw new schematics_1.SchematicsException(`Could not statically analyze config in bootstrapApplication call in ${filePath}`);
|
100 | }
|
101 | const { filePath: configFilePath, node: config } = appConfig;
|
102 | const recorder = tree.beginUpdate(configFilePath);
|
103 | const importCall = findImportProvidersFromCall(config);
|
104 | addImports(config.getSourceFile(), recorder);
|
105 | if (importCall) {
|
106 |
|
107 | recorder.insertRight(importCall.arguments[importCall.arguments.length - 1].getEnd(), `, ${moduleName}`);
|
108 | }
|
109 | else {
|
110 | const providersLiteral = findProvidersLiteral(config);
|
111 | if (providersLiteral) {
|
112 |
|
113 | addElementToArray(providersLiteral, importProvidersCall, recorder);
|
114 | }
|
115 | else {
|
116 |
|
117 | addProvidersToObjectLiteral(config, importProvidersCall, recorder);
|
118 | }
|
119 | }
|
120 | tree.commitUpdate(recorder);
|
121 | }
|
122 | exports.addModuleImportToStandaloneBootstrap = addModuleImportToStandaloneBootstrap;
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 | function addFunctionalProvidersToStandaloneBootstrap(tree, filePath, functionName, importPath, args = []) {
|
135 | const sourceFile = createSourceFile(tree, filePath);
|
136 | const bootstrapCall = findBootstrapApplicationCall(sourceFile);
|
137 | const addImports = (file, recorder) => {
|
138 | const change = (0, ast_utils_1.insertImport)(file, file.getText(), functionName, importPath);
|
139 | if (change instanceof change_1.InsertChange) {
|
140 | recorder.insertLeft(change.pos, change.toAdd);
|
141 | }
|
142 | };
|
143 | if (!bootstrapCall) {
|
144 | throw new schematics_1.SchematicsException(`Could not find bootstrapApplication call in ${filePath}`);
|
145 | }
|
146 | const providersCall = typescript_1.default.factory.createCallExpression(typescript_1.default.factory.createIdentifier(functionName), undefined, args);
|
147 |
|
148 | if (bootstrapCall.arguments.length === 1) {
|
149 | const recorder = tree.beginUpdate(filePath);
|
150 | addNewAppConfigToCall(bootstrapCall, providersCall, recorder);
|
151 | addImports(sourceFile, recorder);
|
152 | tree.commitUpdate(recorder);
|
153 | return filePath;
|
154 | }
|
155 |
|
156 | if (isMergeAppConfigCall(bootstrapCall.arguments[1])) {
|
157 | const recorder = tree.beginUpdate(filePath);
|
158 | addNewAppConfigToCall(bootstrapCall.arguments[1], providersCall, recorder);
|
159 | addImports(sourceFile, recorder);
|
160 | tree.commitUpdate(recorder);
|
161 | return filePath;
|
162 | }
|
163 |
|
164 | const appConfig = findAppConfig(bootstrapCall, tree, filePath);
|
165 | if (!appConfig) {
|
166 | throw new schematics_1.SchematicsException(`Could not statically analyze config in bootstrapApplication call in ${filePath}`);
|
167 | }
|
168 | const { filePath: configFilePath, node: config } = appConfig;
|
169 | const recorder = tree.beginUpdate(configFilePath);
|
170 | const providersLiteral = findProvidersLiteral(config);
|
171 | addImports(config.getSourceFile(), recorder);
|
172 | if (providersLiteral) {
|
173 |
|
174 | addElementToArray(providersLiteral, providersCall, recorder);
|
175 | }
|
176 | else {
|
177 |
|
178 | addProvidersToObjectLiteral(config, providersCall, recorder);
|
179 | }
|
180 | tree.commitUpdate(recorder);
|
181 | return configFilePath;
|
182 | }
|
183 | exports.addFunctionalProvidersToStandaloneBootstrap = addFunctionalProvidersToStandaloneBootstrap;
|
184 |
|
185 |
|
186 |
|
187 |
|
188 |
|
189 | function findBootstrapApplicationCall(sourceFile) {
|
190 | const localName = findImportLocalName(sourceFile, 'bootstrapApplication', '@angular/platform-browser');
|
191 | if (!localName) {
|
192 | return null;
|
193 | }
|
194 | let result = null;
|
195 | sourceFile.forEachChild(function walk(node) {
|
196 | if (typescript_1.default.isCallExpression(node) &&
|
197 | typescript_1.default.isIdentifier(node.expression) &&
|
198 | node.expression.text === localName) {
|
199 | result = node;
|
200 | }
|
201 | if (!result) {
|
202 | node.forEachChild(walk);
|
203 | }
|
204 | });
|
205 | return result;
|
206 | }
|
207 | exports.findBootstrapApplicationCall = findBootstrapApplicationCall;
|
208 |
|
209 | function findImportProvidersFromCall(config) {
|
210 | const importProvidersName = findImportLocalName(config.getSourceFile(), 'importProvidersFrom', '@angular/core');
|
211 | const providersLiteral = findProvidersLiteral(config);
|
212 | if (providersLiteral && importProvidersName) {
|
213 | for (const element of providersLiteral.elements) {
|
214 |
|
215 | if (typescript_1.default.isCallExpression(element) &&
|
216 | typescript_1.default.isIdentifier(element.expression) &&
|
217 | element.expression.text === importProvidersName) {
|
218 | return element;
|
219 | }
|
220 | }
|
221 | }
|
222 | return null;
|
223 | }
|
224 |
|
225 | function findProvidersLiteral(config) {
|
226 | for (const prop of config.properties) {
|
227 | if (typescript_1.default.isPropertyAssignment(prop) &&
|
228 | typescript_1.default.isIdentifier(prop.name) &&
|
229 | prop.name.text === 'providers' &&
|
230 | typescript_1.default.isArrayLiteralExpression(prop.initializer)) {
|
231 | return prop.initializer;
|
232 | }
|
233 | }
|
234 | return null;
|
235 | }
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 | function findAppConfig(bootstrapCall, tree, filePath) {
|
243 | if (bootstrapCall.arguments.length > 1) {
|
244 | const config = bootstrapCall.arguments[1];
|
245 | if (typescript_1.default.isObjectLiteralExpression(config)) {
|
246 | return { filePath, node: config };
|
247 | }
|
248 | if (typescript_1.default.isIdentifier(config)) {
|
249 | return resolveAppConfigFromIdentifier(config, tree, filePath);
|
250 | }
|
251 | }
|
252 | return null;
|
253 | }
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 | function resolveAppConfigFromIdentifier(identifier, tree, bootstapFilePath) {
|
261 | const sourceFile = identifier.getSourceFile();
|
262 | for (const node of sourceFile.statements) {
|
263 |
|
264 |
|
265 |
|
266 | if (!typescript_1.default.isImportDeclaration(node) ||
|
267 | !node.importClause?.namedBindings ||
|
268 | !typescript_1.default.isNamedImports(node.importClause.namedBindings) ||
|
269 | !typescript_1.default.isStringLiteralLike(node.moduleSpecifier) ||
|
270 | !node.moduleSpecifier.text.startsWith('.')) {
|
271 | continue;
|
272 | }
|
273 | for (const specifier of node.importClause.namedBindings.elements) {
|
274 | if (specifier.name.text !== identifier.text) {
|
275 | continue;
|
276 | }
|
277 |
|
278 |
|
279 |
|
280 | const filePath = (0, path_1.join)((0, path_1.dirname)(bootstapFilePath), node.moduleSpecifier.text + '.ts');
|
281 | const importedSourceFile = createSourceFile(tree, filePath);
|
282 | const resolvedVariable = findAppConfigFromVariableName(importedSourceFile, (specifier.propertyName || specifier.name).text);
|
283 | if (resolvedVariable) {
|
284 | return { filePath, node: resolvedVariable };
|
285 | }
|
286 | }
|
287 | }
|
288 | const variableInSameFile = findAppConfigFromVariableName(sourceFile, identifier.text);
|
289 | return variableInSameFile ? { filePath: bootstapFilePath, node: variableInSameFile } : null;
|
290 | }
|
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 | function findAppConfigFromVariableName(sourceFile, variableName) {
|
297 | for (const node of sourceFile.statements) {
|
298 | if (typescript_1.default.isVariableStatement(node)) {
|
299 | for (const decl of node.declarationList.declarations) {
|
300 | if (typescript_1.default.isIdentifier(decl.name) &&
|
301 | decl.name.text === variableName &&
|
302 | decl.initializer &&
|
303 | typescript_1.default.isObjectLiteralExpression(decl.initializer)) {
|
304 | return decl.initializer;
|
305 | }
|
306 | }
|
307 | }
|
308 | }
|
309 | return null;
|
310 | }
|
311 |
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 | function findImportLocalName(sourceFile, name, moduleName) {
|
318 | for (const node of sourceFile.statements) {
|
319 |
|
320 | if (!typescript_1.default.isImportDeclaration(node) ||
|
321 | !typescript_1.default.isStringLiteral(node.moduleSpecifier) ||
|
322 | node.moduleSpecifier.text !== moduleName) {
|
323 | continue;
|
324 | }
|
325 |
|
326 | if (!node.importClause ||
|
327 | !node.importClause.namedBindings ||
|
328 | !typescript_1.default.isNamedImports(node.importClause.namedBindings)) {
|
329 | continue;
|
330 | }
|
331 |
|
332 | for (const element of node.importClause.namedBindings.elements) {
|
333 | if ((element.propertyName || element.name).text === name) {
|
334 |
|
335 | return element.name.text;
|
336 | }
|
337 | }
|
338 | }
|
339 | return null;
|
340 | }
|
341 |
|
342 | function createSourceFile(tree, filePath) {
|
343 | return typescript_1.default.createSourceFile(filePath, tree.readText(filePath), typescript_1.default.ScriptTarget.Latest, true);
|
344 | }
|
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 | function addNewAppConfigToCall(call, expression, recorder) {
|
352 | const newCall = typescript_1.default.factory.updateCallExpression(call, call.expression, call.typeArguments, [
|
353 | ...call.arguments,
|
354 | typescript_1.default.factory.createObjectLiteralExpression([
|
355 | typescript_1.default.factory.createPropertyAssignment('providers', typescript_1.default.factory.createArrayLiteralExpression([expression])),
|
356 | ], true),
|
357 | ]);
|
358 | recorder.remove(call.getStart(), call.getWidth());
|
359 | recorder.insertRight(call.getStart(), typescript_1.default.createPrinter().printNode(typescript_1.default.EmitHint.Unspecified, newCall, call.getSourceFile()));
|
360 | }
|
361 |
|
362 |
|
363 |
|
364 |
|
365 |
|
366 |
|
367 | function addElementToArray(node, element, recorder) {
|
368 | const newLiteral = typescript_1.default.factory.updateArrayLiteralExpression(node, [...node.elements, element]);
|
369 | recorder.remove(node.getStart(), node.getWidth());
|
370 | recorder.insertRight(node.getStart(), typescript_1.default.createPrinter().printNode(typescript_1.default.EmitHint.Unspecified, newLiteral, node.getSourceFile()));
|
371 | }
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 | function addProvidersToObjectLiteral(node, expression, recorder) {
|
379 | const newOptionsLiteral = typescript_1.default.factory.updateObjectLiteralExpression(node, [
|
380 | ...node.properties,
|
381 | typescript_1.default.factory.createPropertyAssignment('providers', typescript_1.default.factory.createArrayLiteralExpression([expression])),
|
382 | ]);
|
383 | recorder.remove(node.getStart(), node.getWidth());
|
384 | recorder.insertRight(node.getStart(), typescript_1.default.createPrinter().printNode(typescript_1.default.EmitHint.Unspecified, newOptionsLiteral, node.getSourceFile()));
|
385 | }
|
386 |
|
387 | function isMergeAppConfigCall(node) {
|
388 | if (!typescript_1.default.isCallExpression(node)) {
|
389 | return false;
|
390 | }
|
391 | const localName = findImportLocalName(node.getSourceFile(), 'mergeApplicationConfig', '@angular/core');
|
392 | return !!localName && typescript_1.default.isIdentifier(node.expression) && node.expression.text === localName;
|
393 | }
|