UNPKG

21.6 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.applyWithSkipExisting = exports.renameDirSyncInTree = exports.renameSyncInTree = exports.replaceNodeValue = exports.insertImport = exports.createOrUpdate = exports.getProjectConfig = exports.updatePackageJsonDependencies = exports.addDepsToPackageJson = exports.readWorkspace = exports.updateNxJsonInTree = exports.appsDir = exports.libsDir = exports.readNxJsonInTree = exports.updateWorkspaceInTree = exports.updateJsonInTree = exports.allFilesInDirInHost = exports.getFileDataInHost = exports.readJsonInTree = exports.insert = exports.addGlobal = exports.getImport = exports.addIncludeToTsConfig = exports.offset = exports.findClass = exports.addMethod = exports.addParameterToConstructor = exports.ReplaceChange = exports.RemoveChange = exports.InsertChange = exports.NoopChange = exports.getSourceNodes = exports.findNodes = exports.sortObjectByKeys = void 0;
4/**
5 * @license
6 * Copyright Google Inc. All Rights Reserved.
7 *
8 * Use of this source code is governed by an MIT- style license that can be
9 * found in the LICENSE file at https://angular.io/license
10 */
11const schematics_1 = require("@angular-devkit/schematics");
12const ts = require("typescript");
13const devkit_1 = require("@nrwl/devkit");
14const cli_config_utils_1 = require("./cli-config-utils");
15const core_1 = require("@angular-devkit/core");
16const add_install_task_1 = require("./rules/add-install-task");
17const find_nodes_1 = require("../utilities/typescript/find-nodes");
18const get_source_nodes_1 = require("../utilities/typescript/get-source-nodes");
19function nodesByPosition(first, second) {
20 return first.getStart() - second.getStart();
21}
22function insertAfterLastOccurrence(nodes, toInsert, file, fallbackPos, syntaxKind) {
23 // sort() has a side effect, so make a copy so that we won't overwrite the parent's object.
24 let lastItem = [...nodes].sort(nodesByPosition).pop();
25 if (!lastItem) {
26 throw new Error();
27 }
28 if (syntaxKind) {
29 lastItem = (0, find_nodes_1.findNodes)(lastItem, syntaxKind).sort(nodesByPosition).pop();
30 }
31 if (!lastItem && fallbackPos == undefined) {
32 throw new Error(`tried to insert ${toInsert} as first occurrence with no fallback position`);
33 }
34 const lastItemPosition = lastItem ? lastItem.getEnd() : fallbackPos;
35 return new InsertChange(file, lastItemPosition, toInsert);
36}
37function sortObjectByKeys(obj) {
38 return Object.keys(obj)
39 .sort()
40 .reduce((result, key) => {
41 return Object.assign(Object.assign({}, result), { [key]: obj[key] });
42 }, {});
43}
44exports.sortObjectByKeys = sortObjectByKeys;
45var find_nodes_2 = require("../utilities/typescript/find-nodes");
46Object.defineProperty(exports, "findNodes", { enumerable: true, get: function () { return find_nodes_2.findNodes; } });
47var get_source_nodes_2 = require("../utilities/typescript/get-source-nodes");
48Object.defineProperty(exports, "getSourceNodes", { enumerable: true, get: function () { return get_source_nodes_2.getSourceNodes; } });
49class NoopChange {
50 constructor() {
51 this.type = 'noop';
52 this.description = 'No operation.';
53 this.order = Infinity;
54 this.path = null;
55 }
56 apply() {
57 return Promise.resolve();
58 }
59}
60exports.NoopChange = NoopChange;
61class InsertChange {
62 constructor(path, pos, toAdd) {
63 this.path = path;
64 this.pos = pos;
65 this.toAdd = toAdd;
66 this.type = 'insert';
67 if (pos < 0) {
68 throw new Error('Negative positions are invalid');
69 }
70 this.description = `Inserted ${toAdd} into position ${pos} of ${path}`;
71 this.order = pos;
72 }
73 apply(host) {
74 return host.read(this.path).then((content) => {
75 const prefix = content.substring(0, this.pos);
76 const suffix = content.substring(this.pos);
77 return host.write(this.path, `${prefix}${this.toAdd}${suffix}`);
78 });
79 }
80}
81exports.InsertChange = InsertChange;
82class RemoveChange {
83 constructor(path, pos, toRemove) {
84 this.path = path;
85 this.pos = pos;
86 this.toRemove = toRemove;
87 this.type = 'remove';
88 if (pos < 0) {
89 throw new Error('Negative positions are invalid');
90 }
91 this.description = `Removed ${toRemove} into position ${pos} of ${path}`;
92 this.order = pos;
93 }
94 apply(host) {
95 return host.read(this.path).then((content) => {
96 const prefix = content.substring(0, this.pos);
97 const suffix = content.substring(this.pos + this.toRemove.length);
98 return host.write(this.path, `${prefix}${suffix}`);
99 });
100 }
101}
102exports.RemoveChange = RemoveChange;
103class ReplaceChange {
104 constructor(path, pos, oldText, newText) {
105 this.path = path;
106 this.pos = pos;
107 this.oldText = oldText;
108 this.newText = newText;
109 this.type = 'replace';
110 if (pos < 0) {
111 throw new Error('Negative positions are invalid');
112 }
113 this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`;
114 this.order = pos;
115 }
116 apply(host) {
117 return host.read(this.path).then((content) => {
118 const prefix = content.substring(0, this.pos);
119 const suffix = content.substring(this.pos + this.oldText.length);
120 const text = content.substring(this.pos, this.pos + this.oldText.length);
121 if (text !== this.oldText) {
122 return Promise.reject(new Error(`Invalid replace: "${text}" != "${this.oldText}".`));
123 }
124 return host.write(this.path, `${prefix}${this.newText}${suffix}`);
125 });
126 }
127}
128exports.ReplaceChange = ReplaceChange;
129function addParameterToConstructor(source, modulePath, opts) {
130 const clazz = findClass(source, opts.className);
131 const constructor = clazz.members.filter((m) => m.kind === ts.SyntaxKind.Constructor)[0];
132 if (constructor) {
133 throw new Error('Should be tested');
134 }
135 else {
136 const methodHeader = `constructor(${opts.param})`;
137 return addMethod(source, modulePath, {
138 className: opts.className,
139 methodHeader,
140 body: null,
141 });
142 }
143}
144exports.addParameterToConstructor = addParameterToConstructor;
145function addMethod(source, modulePath, opts) {
146 const clazz = findClass(source, opts.className);
147 const body = opts.body
148 ? `
149${opts.methodHeader} {
150${offset(opts.body, 1, false)}
151}
152`
153 : `
154${opts.methodHeader} {}
155`;
156 return [new InsertChange(modulePath, clazz.end - 1, offset(body, 1, true))];
157}
158exports.addMethod = addMethod;
159function findClass(source, className, silent = false) {
160 const nodes = (0, get_source_nodes_1.getSourceNodes)(source);
161 const clazz = (nodes.filter((n) => n.kind === ts.SyntaxKind.ClassDeclaration &&
162 n.name.text === className)[0]);
163 if (!clazz && !silent) {
164 throw new Error(`Cannot find class '${className}'`);
165 }
166 return clazz;
167}
168exports.findClass = findClass;
169function offset(text, numberOfTabs, wrap) {
170 const lines = text
171 .trim()
172 .split('\n')
173 .map((line) => {
174 let tabs = '';
175 for (let c = 0; c < numberOfTabs; ++c) {
176 tabs += ' ';
177 }
178 return `${tabs}${line}`;
179 })
180 .join('\n');
181 return wrap ? `\n${lines}\n` : lines;
182}
183exports.offset = offset;
184function addIncludeToTsConfig(tsConfigPath, source, include) {
185 const includeKeywordPos = source.text.indexOf('"include":');
186 if (includeKeywordPos > -1) {
187 const includeArrayEndPos = source.text.indexOf(']', includeKeywordPos);
188 return [new InsertChange(tsConfigPath, includeArrayEndPos, include)];
189 }
190 else {
191 return [];
192 }
193}
194exports.addIncludeToTsConfig = addIncludeToTsConfig;
195function getImport(source, predicate) {
196 const allImports = (0, find_nodes_1.findNodes)(source, ts.SyntaxKind.ImportDeclaration);
197 const matching = allImports.filter((i) => predicate(i.moduleSpecifier.getText()));
198 return matching.map((i) => {
199 const moduleSpec = i.moduleSpecifier
200 .getText()
201 .substring(1, i.moduleSpecifier.getText().length - 1);
202 const t = i.importClause.namedBindings.getText();
203 const bindings = t
204 .replace('{', '')
205 .replace('}', '')
206 .split(',')
207 .map((q) => q.trim());
208 return { moduleSpec, bindings };
209 });
210}
211exports.getImport = getImport;
212function addGlobal(source, modulePath, statement) {
213 const allImports = (0, find_nodes_1.findNodes)(source, ts.SyntaxKind.ImportDeclaration);
214 if (allImports.length > 0) {
215 const lastImport = allImports[allImports.length - 1];
216 return [
217 new InsertChange(modulePath, lastImport.end + 1, `\n${statement}\n`),
218 ];
219 }
220 else {
221 return [new InsertChange(modulePath, 0, `${statement}\n`)];
222 }
223}
224exports.addGlobal = addGlobal;
225function insert(host, modulePath, changes) {
226 if (changes.length < 1) {
227 return;
228 }
229 // sort changes so that the highest pos goes first
230 const orderedChanges = changes.sort((a, b) => b.order - a.order);
231 const recorder = host.beginUpdate(modulePath);
232 for (const change of orderedChanges) {
233 if (change.type == 'insert') {
234 recorder.insertLeft(change.pos, change.toAdd);
235 }
236 else if (change.type == 'remove') {
237 recorder.remove(change.pos - 1, change.toRemove.length + 1);
238 }
239 else if (change.type == 'replace') {
240 recorder.remove(change.pos, change.oldText.length);
241 recorder.insertLeft(change.pos, change.newText);
242 }
243 else if (change.type === 'noop') {
244 // do nothing
245 }
246 else {
247 throw new Error(`Unexpected Change '${change.constructor.name}'`);
248 }
249 }
250 host.commitUpdate(recorder);
251}
252exports.insert = insert;
253/**
254 * This method is specifically for reading JSON files in a Tree
255 * @param host The host tree
256 * @param path The path to the JSON file
257 * @returns The JSON data in the file.
258 */
259function readJsonInTree(host, path) {
260 if (!host.exists(path)) {
261 throw new Error(`Cannot find ${path}`);
262 }
263 try {
264 return (0, devkit_1.parseJson)(host.read(path).toString('utf-8'));
265 }
266 catch (e) {
267 throw new Error(`Cannot parse ${path}: ${e.message}`);
268 }
269}
270exports.readJsonInTree = readJsonInTree;
271// TODO(v13): remove this deprecated method
272/**
273 * @deprecated This method is deprecated
274 */
275function getFileDataInHost(host, path) {
276 return {
277 file: path,
278 ext: (0, core_1.extname)((0, core_1.normalize)(path)),
279 hash: '',
280 };
281}
282exports.getFileDataInHost = getFileDataInHost;
283function allFilesInDirInHost(host, path, options = { recursive: true }) {
284 const dir = host.getDir(path);
285 const res = [];
286 dir.subfiles.forEach((p) => {
287 res.push((0, core_1.join)(path, p));
288 });
289 if (!options.recursive) {
290 return res;
291 }
292 dir.subdirs.forEach((p) => {
293 res.push(...allFilesInDirInHost(host, (0, core_1.join)(path, p)));
294 });
295 return res;
296}
297exports.allFilesInDirInHost = allFilesInDirInHost;
298/**
299 * This method is specifically for updating JSON in a Tree
300 * @param path Path of JSON file in the Tree
301 * @param callback Manipulation of the JSON data
302 * @returns A rule which updates a JSON file file in a Tree
303 */
304function updateJsonInTree(path, callback) {
305 return (host, context) => {
306 if (!host.exists(path)) {
307 host.create(path, (0, devkit_1.serializeJson)(callback({}, context)));
308 return host;
309 }
310 host.overwrite(path, (0, devkit_1.serializeJson)(callback(readJsonInTree(host, path), context)));
311 return host;
312 };
313}
314exports.updateJsonInTree = updateJsonInTree;
315function updateWorkspaceInTree(callback) {
316 return (host, context = undefined) => {
317 const path = (0, cli_config_utils_1.getWorkspacePath)(host);
318 host.overwrite(path, (0, devkit_1.serializeJson)(callback(readJsonInTree(host, path), context, host)));
319 return host;
320 };
321}
322exports.updateWorkspaceInTree = updateWorkspaceInTree;
323function readNxJsonInTree(host) {
324 return readJsonInTree(host, 'nx.json');
325}
326exports.readNxJsonInTree = readNxJsonInTree;
327function libsDir(host) {
328 var _a, _b;
329 const json = readJsonInTree(host, 'nx.json');
330 return (_b = (_a = json === null || json === void 0 ? void 0 : json.workspaceLayout) === null || _a === void 0 ? void 0 : _a.libsDir) !== null && _b !== void 0 ? _b : 'libs';
331}
332exports.libsDir = libsDir;
333function appsDir(host) {
334 var _a, _b;
335 const json = readJsonInTree(host, 'nx.json');
336 return (_b = (_a = json === null || json === void 0 ? void 0 : json.workspaceLayout) === null || _a === void 0 ? void 0 : _a.appsDir) !== null && _b !== void 0 ? _b : 'apps';
337}
338exports.appsDir = appsDir;
339function updateNxJsonInTree(callback) {
340 return (host, context) => {
341 host.overwrite('nx.json', (0, devkit_1.serializeJson)(callback(readJsonInTree(host, 'nx.json'), context)));
342 return host;
343 };
344}
345exports.updateNxJsonInTree = updateNxJsonInTree;
346function readWorkspace(host) {
347 const path = (0, cli_config_utils_1.getWorkspacePath)(host);
348 return readJsonInTree(host, path);
349}
350exports.readWorkspace = readWorkspace;
351/**
352 * verifies whether the given packageJson dependencies require an update
353 * given the deps & devDeps passed in
354 */
355function requiresAddingOfPackages(packageJsonFile, deps, devDeps) {
356 let needsDepsUpdate = false;
357 let needsDevDepsUpdate = false;
358 packageJsonFile.dependencies = packageJsonFile.dependencies || {};
359 packageJsonFile.devDependencies = packageJsonFile.devDependencies || {};
360 if (Object.keys(deps).length > 0) {
361 needsDepsUpdate = Object.keys(deps).some((entry) => !packageJsonFile.dependencies[entry]);
362 }
363 if (Object.keys(devDeps).length > 0) {
364 needsDevDepsUpdate = Object.keys(devDeps).some((entry) => !packageJsonFile.devDependencies[entry]);
365 }
366 return needsDepsUpdate || needsDevDepsUpdate;
367}
368/**
369 * Updates the package.json given the passed deps and/or devDeps. Only updates
370 * if the packages are not yet present
371 *
372 * @param deps the package.json dependencies to add
373 * @param devDeps the package.json devDependencies to add
374 * @param addInstall default `true`; set to false to avoid installs
375 */
376function addDepsToPackageJson(deps, devDeps, addInstall = true) {
377 return (host, context) => {
378 const currentPackageJson = readJsonInTree(host, 'package.json');
379 if (requiresAddingOfPackages(currentPackageJson, deps, devDeps)) {
380 return (0, schematics_1.chain)([
381 updateJsonInTree('package.json', (json, context) => {
382 json.dependencies = Object.assign(Object.assign(Object.assign({}, (json.dependencies || {})), deps), (json.dependencies || {}));
383 json.devDependencies = Object.assign(Object.assign(Object.assign({}, (json.devDependencies || {})), devDeps), (json.devDependencies || {}));
384 json.dependencies = sortObjectByKeys(json.dependencies);
385 json.devDependencies = sortObjectByKeys(json.devDependencies);
386 return json;
387 }),
388 (0, add_install_task_1.addInstallTask)({
389 skipInstall: !addInstall,
390 }),
391 ]);
392 }
393 else {
394 return (0, schematics_1.noop)();
395 }
396 };
397}
398exports.addDepsToPackageJson = addDepsToPackageJson;
399function updatePackageJsonDependencies(deps, devDeps, addInstall = true) {
400 return (0, schematics_1.chain)([
401 updateJsonInTree('package.json', (json, context) => {
402 json.dependencies = Object.assign(Object.assign({}, (json.dependencies || {})), deps);
403 json.devDependencies = Object.assign(Object.assign({}, (json.devDependencies || {})), devDeps);
404 json.dependencies = sortObjectByKeys(json.dependencies);
405 json.devDependencies = sortObjectByKeys(json.devDependencies);
406 return json;
407 }),
408 (0, add_install_task_1.addInstallTask)({
409 skipInstall: !addInstall,
410 }),
411 ]);
412}
413exports.updatePackageJsonDependencies = updatePackageJsonDependencies;
414function getProjectConfig(host, name) {
415 const workspaceJson = readJsonInTree(host, (0, cli_config_utils_1.getWorkspacePath)(host));
416 const projectConfig = workspaceJson.projects[name];
417 if (!projectConfig) {
418 throw new Error(`Cannot find project '${name}'`);
419 }
420 else if (typeof projectConfig === 'string') {
421 return readJsonInTree(host, projectConfig);
422 }
423 else {
424 return projectConfig;
425 }
426}
427exports.getProjectConfig = getProjectConfig;
428function createOrUpdate(host, path, content) {
429 if (host.exists(path)) {
430 host.overwrite(path, content);
431 }
432 else {
433 host.create(path, content);
434 }
435}
436exports.createOrUpdate = createOrUpdate;
437function insertImport(source, fileToEdit, symbolName, fileName, isDefault = false) {
438 const rootNode = source;
439 const allImports = (0, find_nodes_1.findNodes)(rootNode, ts.SyntaxKind.ImportDeclaration);
440 // get nodes that map to import statements from the file fileName
441 const relevantImports = allImports.filter((node) => {
442 // StringLiteral of the ImportDeclaration is the import file (fileName in this case).
443 const importFiles = node
444 .getChildren()
445 .filter((child) => child.kind === ts.SyntaxKind.StringLiteral)
446 .map((n) => n.text);
447 return importFiles.filter((file) => file === fileName).length === 1;
448 });
449 if (relevantImports.length > 0) {
450 let importsAsterisk = false;
451 // imports from import file
452 const imports = [];
453 relevantImports.forEach((n) => {
454 Array.prototype.push.apply(imports, (0, find_nodes_1.findNodes)(n, ts.SyntaxKind.Identifier));
455 if ((0, find_nodes_1.findNodes)(n, ts.SyntaxKind.AsteriskToken).length > 0) {
456 importsAsterisk = true;
457 }
458 });
459 // if imports * from fileName, don't add symbolName
460 if (importsAsterisk) {
461 return new NoopChange();
462 }
463 const importTextNodes = imports.filter((n) => n.text === symbolName);
464 // insert import if it's not there
465 if (importTextNodes.length === 0) {
466 const fallbackPos = (0, find_nodes_1.findNodes)(relevantImports[0], ts.SyntaxKind.CloseBraceToken)[0].getStart() ||
467 (0, find_nodes_1.findNodes)(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart();
468 return insertAfterLastOccurrence(imports, `, ${symbolName}`, fileToEdit, fallbackPos);
469 }
470 return new NoopChange();
471 }
472 // no such import declaration exists
473 const useStrict = (0, find_nodes_1.findNodes)(rootNode, ts.SyntaxKind.StringLiteral).filter((n) => n.text === 'use strict');
474 let fallbackPos = 0;
475 if (useStrict.length > 0) {
476 fallbackPos = useStrict[0].end;
477 }
478 const open = isDefault ? '' : '{ ';
479 const close = isDefault ? '' : ' }';
480 // if there are no imports or 'use strict' statement, insert import at beginning of file
481 const insertAtBeginning = allImports.length === 0 && useStrict.length === 0;
482 const separator = insertAtBeginning ? '' : ';\n';
483 const toInsert = `${separator}import ${open}${symbolName}${close}` +
484 ` from '${fileName}'${insertAtBeginning ? ';\n' : ''}`;
485 return insertAfterLastOccurrence(allImports, toInsert, fileToEdit, fallbackPos, ts.SyntaxKind.StringLiteral);
486}
487exports.insertImport = insertImport;
488function replaceNodeValue(host, modulePath, node, content) {
489 insert(host, modulePath, [
490 new ReplaceChange(modulePath, node.getStart(node.getSourceFile()), node.getFullText(), content),
491 ]);
492}
493exports.replaceNodeValue = replaceNodeValue;
494function renameSyncInTree(tree, from, to, cb) {
495 if (!tree.exists(from)) {
496 cb(`Path: ${from} does not exist`);
497 }
498 else if (tree.exists(to)) {
499 cb(`Path: ${to} already exists`);
500 }
501 else {
502 renameFile(tree, from, to);
503 cb(null);
504 }
505}
506exports.renameSyncInTree = renameSyncInTree;
507function renameDirSyncInTree(tree, from, to, cb) {
508 const dir = tree.getDir(from);
509 if (!dirExists(dir)) {
510 cb(`Path: ${from} does not exist`);
511 return;
512 }
513 dir.visit((path) => {
514 const destination = path.replace(from, to);
515 renameFile(tree, path, destination);
516 });
517 cb(null);
518}
519exports.renameDirSyncInTree = renameDirSyncInTree;
520function dirExists(dir) {
521 return dir.subdirs.length + dir.subfiles.length !== 0;
522}
523function renameFile(tree, from, to) {
524 const buffer = tree.read(from);
525 if (!buffer) {
526 return;
527 }
528 tree.create(to, buffer);
529 tree.delete(from);
530}
531/**
532 * Applies a template merge but skips for already existing entries
533 */
534function applyWithSkipExisting(source, rules) {
535 return (tree, _context) => {
536 const rule = (0, schematics_1.mergeWith)((0, schematics_1.apply)(source, [
537 ...rules,
538 (0, schematics_1.forEach)((fileEntry) => {
539 if (tree.exists(fileEntry.path)) {
540 return null;
541 }
542 return fileEntry;
543 }),
544 ]));
545 return rule(tree, _context);
546 };
547}
548exports.applyWithSkipExisting = applyWithSkipExisting;
549//# sourceMappingURL=ast-utils.js.map
\No newline at end of file