UNPKG

@lightweightform/theme-common

Version:

Common utilities for Lightweightform themes

1,079 lines (1,060 loc) 91.9 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var change = require('@schematics/angular/utility/change'); var schematics = require('@angular-devkit/schematics'); var ts = require('@schematics/angular/third_party/github.com/Microsoft/TypeScript/lib/typescript'); var astUtils = require('@schematics/angular/utility/ast-utils'); var core = require('@angular-devkit/core'); var findModule = require('@schematics/angular/utility/find-module'); var workspace = require('@schematics/angular/utility/workspace'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var ts__default = /*#__PURE__*/_interopDefaultLegacy(ts); /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } function __values(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); } /** * Apply changes to a file. * @param host Source tree. * @param path Path on where to apply changes. * @param changes List of changes to apply. */ function applyChanges(host, path, changes) { var e_1, _a; changes = changes.filter(function (change$1) { return !(change$1 instanceof change.NoopChange); }); if (changes.length > 0) { var recorder = host.beginUpdate(path); try { for (var changes_1 = __values(changes), changes_1_1 = changes_1.next(); !changes_1_1.done; changes_1_1 = changes_1.next()) { var change$1 = changes_1_1.value; // FIXME: `pos`, `toRemove`, `oldText`, and `newText` are private in // `@schematics/angular` for some reason, we should implement `Change` and // derivatives ourselves if (change$1 instanceof change.InsertChange) { recorder.insertLeft(change$1.pos, change$1.toAdd); } else if (change$1 instanceof change.RemoveChange) { recorder.remove(change$1.pos, change$1.toRemove.length); } else if (change$1 instanceof change.ReplaceChange) { recorder.remove(change$1.pos, change$1.oldText.length); recorder.insertLeft(change$1.pos, change$1.newText); } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (changes_1_1 && !changes_1_1.done && (_a = changes_1.return)) _a.call(changes_1); } finally { if (e_1) throw e_1.error; } } host.commitUpdate(recorder); } } /** * Removes the placeholder content generated by the Angular CLI (as long as it * is wrapped by the "placeholder comments" generated by the CLI). * @param content HTML file content. * @returns HTML file without the placeholder content. */ function removeHtmlAppComponentPlaceholder(content) { var placeholderStart = "<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->\n<!-- * * * * * * * * * * * The content below * * * * * * * * * * * -->\n<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->\n<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->\n<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->\n<!-- * * * * * * * * * Delete the template below * * * * * * * * * * -->\n<!-- * * * * * * * to get started with your project! * * * * * * * * -->\n<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->"; var placeholderEnd = "<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->\n<!-- * * * * * * * * * * * The content above * * * * * * * * * * * -->\n<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->\n<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->\n<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->\n<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * -->\n<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->"; var startIdx = content.indexOf(placeholderStart); var endIdx = content.lastIndexOf(placeholderEnd); return startIdx !== -1 && endIdx !== -1 ? content.slice(0, startIdx) + content.slice(endIdx + placeholderEnd.length) : content; } /** * Removes the `<router-outlet>` generated by the Angular CLI for the app * component (as long as it is in the expected position: at the end of the * file). * @param content HTML file content. * @returns HTML file content without the CLI generated router outlet. */ function removeHtmlAppComponentRouterOutlet(content) { var outletRegExp = /(^|\n)<router-outlet><\/router-outlet>\s*$/; return content.replace(outletRegExp, ''); } /** * Removes the default CLI generated content in the HTML file of a module. * @param content HTML file content. * @param moduleName Name of the module. * @returns HTML file content without the CLI generated default content. */ function removeHtmlModuleDefaultContent(content, moduleName) { var defaultContent = "<p>" + moduleName + " works!</p>"; return content.replace(defaultContent, ''); } /** * Whether the provided HTML file is an unmodified version of the Angular CLI * generated app component (ignores added spaces and content modified within the * "template" comments). * @param content HTML file content. * @returns Whether the HTML file content is an unmodified version of the CLI * generated app component. */ function isUnmodifiedHtmlAppComponent(content) { return (removeHtmlAppComponentPlaceholder(removeHtmlAppComponentRouterOutlet(content)).trim() === ''); } /** * Whether the provided HTML file is an unmodified version of the Angular CLI * generated module content (ignores added spaces). * @param content HTML file content. * @param moduleName Name of the module. * @returns Whether the HTML file content is an unmodified version of the CLI * generated module content. */ function isUnmodifiedHtmlModuleComponent(content, moduleName) { return removeHtmlModuleDefaultContent(content, moduleName).trim() === ''; } /** * Gets the indentation used in the given content by returning the indentation * of the first indented line. * @param content File content on which to get indentation. * @param dft Default indentation when no indentation was found. * @returns Indentation used in the file. */ function getIndentation(content, dft) { if (dft === void 0) { dft = ' '; } // The `[^*]` prevents obtaining indentation from within a JSDoc return (content.match(/^(( |\t)+)[^*]/m) || [, dft])[1]; } /** * Indents new lines in the given content. * @param content Content with new lines to indend. * @param indent Indentation to use on new lines. * @returns Content with indented new lines. */ function indentNewLines(content, indent) { if (indent === void 0) { indent = ' '; } return content.replace(/(\r?\n)(.)/g, "$1" + indent + "$2"); } /** * RegExp used to determine whether a string is a valid TS identifier. */ var validTsIdRegExp = /^[a-z_$][\w$]*$/i; /** * Symbol representing a "default binding" (import or export). */ var defaultBinding = Symbol('Default binding'); /** * Class used to insert a new import in a TS file. */ var TsImport = /** @class */ (function () { /** * Creates a new TS import. * @param name Name to import (or the default import symbol). `null` to import * no name (e.g. `import 'module';`). * @param location Import location. * @param as Identifier to import as (use also when importing a default). */ function TsImport(name, location, as) { Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: name }); Object.defineProperty(this, "location", { enumerable: true, configurable: true, writable: true, value: location }); Object.defineProperty(this, "as", { enumerable: true, configurable: true, writable: true, value: as }); } return TsImport; }()); /** * Source of a given TypeScript file given its path. * @param host Source tree. * @param path File path for which to fetch source. * @returns TypeScript source file. */ function getTsSourceFile(host, path) { var buffer = host.read(path); if (!buffer) { throw new schematics.SchematicsException("File not found: \"" + path.slice(1) + "\"."); } return ts__default["default"].createSourceFile(path, buffer.toString('utf-8'), ts__default["default"].ScriptTarget.Latest, true); } /** * Location used to import a file relatively to another file in a TS import. * @param sourcePath File path where the import is to be inserted. * @param importPath File being imported. * @returns Relative file path from the source file to the one being imported. */ function relativeTsImportPath(sourcePath, importPath) { var rel = core.relative(core.dirname(sourcePath), importPath).replace(/\.ts$/, ''); return rel.startsWith('../') ? rel : "./" + rel; } /** * Creates a change that inserts a new TS import. * @param source TS source. * @param path Path of the TS file on which to import. * @param toImport Import object representing import to add. * @returns Change to apply. */ function insertTsImport(source, path, toImport) { var isDefault = toImport.name === defaultBinding; var name = isDefault ? toImport.as : toImport.name; // XXX: `insertImport` is not prepared to deal with import aliases so we fake // an import without the alias to see if there would be a change, and if so we // set the symbol as a string with the alias if (!isDefault && toImport.as && name !== toImport.as) { var mockImportChange = astUtils.insertImport(source, path, name, toImport.location); if (mockImportChange instanceof change.NoopChange) { return mockImportChange; } name = toImport.name + " as " + toImport.as; } return astUtils.insertImport(source, path, name, toImport.location, isDefault); } /** * Creates a change that removes a TS import. * @param source TS source. * @param path Path of the TS file on which to remove the import. * @param name Import name (or the default import symbol). * @param location Import location. */ function removeTsImport(source, path, name, location) { var e_1, _a; var importStats = getLocationImports(source, location); try { for (var importStats_1 = __values(importStats), importStats_1_1 = importStats_1.next(); !importStats_1_1.done; importStats_1_1 = importStats_1.next()) { var importStat = importStats_1_1.value; var importClause = importStat.importClause; if (!importClause) { continue; } var importClauseStr = importClause.getText(); if (name === defaultBinding) { // Remove default import var match = matchDefaultImport(importClauseStr); if (match) { if (match[0] === importClauseStr) { // Remove whole statement return new change.RemoveChange(path, importStat.pos, importStat.getText()); } else { // Remove just the default import return new change.RemoveChange(path, importClause.pos + importClauseStr.indexOf(match[0]) + 1, match[0]); } } } else { // Remove named binding var match = matchNamedBindingImport(importClauseStr, name); if (match) { if (importClause.namedBindings.elements.length === 1) { if (match[0] === importClauseStr) { // Remove whole statement return new change.RemoveChange(path, importStat.pos, importClauseStr); } else { // Remove just the named imports group return new change.RemoveChange(path, importClause.pos + importClauseStr.indexOf(match[0]) + 1, match[0]); } } else { // Remove just the named import return new change.RemoveChange(path, importClause.pos + importClauseStr.indexOf(match[1]) + 1, match[1]); } } } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (importStats_1_1 && !importStats_1_1.done && (_a = importStats_1.return)) _a.call(importStats_1); } finally { if (e_1) throw e_1.error; } } return new change.NoopChange(); } /** * Obtains the id with which a certain name was imported in a given file, * `undefined` if the given name hasn't been imported. E.g., let `source` * contain: * ```ts * import {w, x as y} from 'location'; * ``` * Then: * ```ts * getImportId(source, 'w', 'location'); // Returns `'w'` * getImportId(source, 'x', 'location'); // Returns `'y'` * getImportId(source, 'z', 'location'); // Returns `undefined` * ``` * @param source TS source. * @param name Name of the import (or the default import symbol). * @param location Import location. * @returns Id of the imported name or `undefined` if the name hasn't been * imported. */ function getTsImportId(source, name, location) { var e_2, _a; var importStats = getLocationImports(source, location); try { for (var importStats_2 = __values(importStats), importStats_2_1 = importStats_2.next(); !importStats_2_1.done; importStats_2_1 = importStats_2.next()) { var importStat = importStats_2_1.value; var importClause = importStat.importClause; if (!importClause) { continue; } var importClauseStr = importClause.getText(); if (name === defaultBinding) { // Get default import name var match = matchDefaultImport(importClauseStr); if (match) { return match[1]; } } else { // Get named binding var match = matchNamedBindingImport(importClauseStr, name); if (match) { return match[2] || name; } } } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (importStats_2_1 && !importStats_2_1.done && (_a = importStats_2.return)) _a.call(importStats_2); } finally { if (e_2) throw e_2.error; } } } /** * Obtains the declaration exporting the provided name (or default export). * @param source TS source. * @param name Name of the exported declaration (or default export name). * @returns Declaration exported with the provided name (or default export). */ function getTsExportedDeclaration(source, name) { var isDefault = name === defaultBinding; // Find matching function or class declaration var fn = source.statements.find(function (s) { return (ts__default["default"].isFunctionDeclaration(s) || ts__default["default"].isClassDeclaration(s)) && !!s.modifiers && !!s.modifiers.find(function (m) { return m.kind === ts__default["default"].SyntaxKind.ExportKeyword; }) && (!isDefault || !!s.modifiers.find(function (m) { return m.kind === ts__default["default"].SyntaxKind.DefaultKeyword; })) && !!s.name && s.name.getText() === name; }); if (fn) { return fn; } // Find matching variable declaration var decl = source.statements .filter(function (s) { return ts__default["default"].isVariableStatement(s) && !!s.modifiers && !!s.modifiers.find(function (m) { return m.kind === ts__default["default"].SyntaxKind.ExportKeyword; }) && (!isDefault || !!s.modifiers.find(function (m) { return m.kind === ts__default["default"].SyntaxKind.DefaultKeyword; })); }) .flatMap(function (v) { return v.declarationList.declarations; }) .find(function (d) { return d.name.getText() === name; }); return decl; } /** * Get all imports from a given location. * @param source TS source. * @param location Import location. * @returns List of imports from the given location. */ function getLocationImports(source, location) { return source.statements.filter(function (stat) { return ts__default["default"].isImportDeclaration(stat) && ts__default["default"].isStringLiteral(stat.moduleSpecifier) && stat.moduleSpecifier.text === location; }); } /** * Match the default import section of an import statement's clause. Returns a * `RegExp` match where the whole match contains the portion to remove (when the * default import is to be removed). The first capturing groups contains the * name of the imported default id. * @param importClauseStr String representing the import statement's clause. * @returns `RegExp` match or `null` when the import doesn't match. */ function matchDefaultImport(importClauseStr) { return importClauseStr.match(/^([^\s,]+)(?:\s*,\s*)?/); } /** * Match an import named binding from an import statement's clause. Returns a * `RegExp` match where the full match matches the whole named bindings group to * remove (when appropriate); the first capturing group is the portion of the * named binding to remove (when the import name is to be removed); and the * second capturing group contains the "alias" if it exists, e.g. `N` in * `name as N`. * @param importClauseStr String representing the import statement's clause. * @param name Name to match in the import statement. * @returns `RegExp` match or `null` when the import doesn't match. */ function matchNamedBindingImport(importClauseStr, name) { return importClauseStr.match(new RegExp("(?:,\\s*)?{[^]*?(" + name + "(?:\\s+as\\s+([^\\s,}]+))?(?:\\s*,\\s*)?)[^]*}")); } /** * Given a TS node, attempts to find the value represented by said node. I.e., * if the given node **is not** an identifier, the given node is returned back; * if the node **is** an identifier, we try to find a declaration within the * same file matching said identifier and return the declared value; if no such * declaration is found, we check if the identifier has been imported from * somewhere, in which case we return a `TsImport` with the name of the imported * identifier and its location. * @param source Source code where the node has been found. * @param node Node for which to get a non-identifier node. */ function getTsValue(source, node) { var e_3, _a, e_4, _b; if (node && ts__default["default"].isIdentifier(node)) { var name_1 = node.getText(); // Find matching function or class declaration var fn = source.statements.find(function (s) { return (ts__default["default"].isFunctionDeclaration(s) || ts__default["default"].isClassDeclaration(s)) && !!s.name && s.name.getText() === name_1; }); if (fn) { return fn; } // Find matching variable declaration var decl = source.statements .filter(function (s) { return ts__default["default"].isVariableStatement(s); }) .flatMap(function (v) { return v.declarationList.declarations; }) .find(function (d) { return d.name.getText() === name_1; }); if (decl) { return getTsValue(source, decl.initializer); } // Find matching import var imports = source.statements.filter(function (stat) { return ts__default["default"].isImportDeclaration(stat) && stat.importClause; }); try { for (var imports_1 = __values(imports), imports_1_1 = imports_1.next(); !imports_1_1.done; imports_1_1 = imports_1.next()) { var imp = imports_1_1.value; var location_1 = imp.moduleSpecifier.text; // Default import if (imp.importClause.name && imp.importClause.name.getText() === name_1) { return new TsImport(defaultBinding, location_1); } // Named import if (imp.importClause.namedBindings && ts__default["default"].isNamedImports(imp.importClause.namedBindings)) { var bindings = imp.importClause.namedBindings.elements; try { for (var bindings_1 = (e_4 = void 0, __values(bindings)), bindings_1_1 = bindings_1.next(); !bindings_1_1.done; bindings_1_1 = bindings_1.next()) { var el = bindings_1_1.value; if (el.name.getText() === name_1) { return new TsImport(el.propertyName ? el.propertyName.getText() : name_1, location_1); } } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (bindings_1_1 && !bindings_1_1.done && (_b = bindings_1.return)) _b.call(bindings_1); } finally { if (e_4) throw e_4.error; } } } } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (imports_1_1 && !imports_1_1.done && (_a = imports_1.return)) _a.call(imports_1); } finally { if (e_3) throw e_3.error; } } } else { return node; } } /** * Transforms a given string in a string that can be used as the property of * an object. E.g.: * ```ts * stringAsProperty('aaaa'); // Returns `'aaaa'` * stringAsProperty('1xx'); // Returns `'\'1xx\''` * ``` * @param string String to transform into a string that can be used as property. * @returns String that can be used as an object property. */ function stringAsProperty(id) { return validTsIdRegExp.test(id) ? id : "'" + id.replace(/'/g, "\\'") + "'"; } /** * Transforms a given string that represents an object property into the actual * name of the property (removing extra delimiters, etc.). E.g.: * ```ts * propertyAsString('aaaa'); // Returns `'aaaa'` * propertyAsString('\'1xx\''); // Returns `'1xx'` * ``` * @param property Property (as a string) from which to get the name. * @returns Name of the property. */ function propertyAsString(property) { return /^('|").*?\1$/.test(property) ? property.slice(1, property.length - 1).replace(/\\'/g, "'") : property; } /** * Creates a change that inserts a given element (as a string) in a given array * of nodes. * @param filePath Path of file being edited. * @param nodeArr Array of nodes where to insert the new element. * @param nodeStr New node to insert (as a string). * @param separator Separator used between nodes. * @returns Change to apply. */ function insertInNodeArray(filePath, nodeArr, nodeStr, separator) { if (separator === void 0) { separator = ','; } var indent = nodeArr[0] && getIndentation(nodeArr[0].getFullText(), ''); var spacing = indent ? "\n" + indent : ' '; var toInsert = nodeArr.length > 0 ? "" + separator + spacing + nodeStr : nodeStr; return astUtils.insertAfterLastOccurrence(nodeArr, toInsert, filePath, nodeArr.pos); } /** * Gets the first `@Component` class of a given TS source file. * @param source TS source file. * @returns Component's class declaration. */ function getFirstComponent(source) { // Obtain the parent class declaration of a node function getParentClassDeclaration(nd) { if (ts__default["default"].isClassDeclaration(nd)) { return nd; } return nd.parent ? getParentClassDeclaration(nd.parent) : null; } // Find `@Component`s var components = astUtils.getDecoratorMetadata(source, 'Component', '@angular/core'); return components.length === 0 ? null : getParentClassDeclaration(components[0]); } /** * Adds an interface to the first component in a given file. * @param host Source tree. * @param componentPath Path on which to find the component. * @param interfaceName Name of the interface to implement. * @param toImport Identifier of the interface to import. */ function addTsComponentInterface(host, componentPath, interfaceName, toImport) { var source = getTsSourceFile(host, componentPath); var component = getFirstComponent(source); if (!component) { return console.error("No `@Component` found in \"" + componentPath + "\"."); } var interfacesClause = component.heritageClauses && component.heritageClauses.find(function (h) { return ts__default["default"].tokenToString(h.token) === 'implements'; }); var changes = []; if (interfacesClause) { changes.push(insertInNodeArray(componentPath, interfacesClause.types, interfaceName)); } else { var insertAfter = component.heritageClauses ? component.heritageClauses[component.heritageClauses.length - 1].end : component.name.end; var toInsert = " implements " + interfaceName; changes.push(new change.InsertChange(componentPath, insertAfter, toInsert)); } if (toImport) { changes.push(insertTsImport(source, componentPath, toImport)); } applyChanges(host, componentPath, changes); } /** * Adds a member to the first component in a given file. * @param host Source tree. * @param componentPath Path on which to find the component. * @param memberName Name of the member to add. * @param toInsert Member content to insert. * @param memberKind Kind of member to insert. * @param modifiers Member modifiers, e.g. `['private', 'static']`. * @param toImport Identifiers to import. */ function addTsComponentMember(host, componentPath, memberName, toInsert, memberKind, modifiers, toImport) { var _a; if (modifiers === void 0) { modifiers = []; } if (toImport === void 0) { toImport = []; } var source = getTsSourceFile(host, componentPath); var component = getFirstComponent(source); if (!component) { return console.error("No `@Component` found in \"" + componentPath + "\"."); } var members = component.members; // Do nothing if member already exists if (members && members.find(function (m) { return !!m.name && m.name.escapedText === memberName; })) { return; } // Add modifiers and indent new member var modifiersStr = modifiers.length ? modifiers.join(' ') + ' ' : ''; var indent = getIndentation(component.getFullText()); toInsert = "\n\n" + indent + modifiersStr + indentNewLines(toInsert, indent); var ordering = (_a = {}, _a[ts__default["default"].SyntaxKind.PropertyDeclaration] = 0, _a[ts__default["default"].SyntaxKind.Constructor] = 1, _a[ts__default["default"].SyntaxKind.GetAccessor] = 2, _a[ts__default["default"].SyntaxKind.SetAccessor] = 2, _a[ts__default["default"].SyntaxKind.MethodDeclaration] = 3, _a); var memberOrder = ordering[memberKind]; var changes = [ astUtils.insertAfterLastOccurrence(members.filter(function (m) { return ordering[m.kind] <= memberOrder; }), toInsert, componentPath, members.pos), ]; toImport.forEach(function (t) { return changes.push(insertTsImport(source, componentPath, t)); }); applyChanges(host, componentPath, changes); } /** * Adds a property to the first component in a given file. * @param host Source tree. * @param componentPath Path on which to find the component. * @param propertyName Name of the property to add. * @param propertyValue Value of the property to add. * @param propertyType Type of the property to add. * @param modifiers Property modifiers, e.g. `['private', 'static']`. * @param toImport Identifiers to import. */ function addTsComponentProperty(host, componentPath, propertyName, propertyType, propertyValue, modifiers, toImport) { var typ = propertyType ? ": " + propertyType : ''; var assign = propertyValue ? " = " + propertyValue : ''; var toInsert = "" + propertyName + typ + assign + ";"; addTsComponentMember(host, componentPath, propertyName, toInsert, ts__default["default"].SyntaxKind.PropertyDeclaration, modifiers, toImport); } /** * Adds a getter to the first component in a given file. * @param host Source tree. * @param componentPath Path on which to find the component. * @param getterName Name of the getter to add. * @param getterType Type of the getter to add. * @param getterContent Content of the getter to add, including the opening and * closing brackets, e.g.: `'{ return this.prop; }'`. * @param modifiers Getter modifiers, e.g. `['private']`. * @param toImport Identifiers to import. */ function addTsComponentGetter(host, componentPath, getterName, getterType, getterContent, modifiers, toImport) { var typ = getterType ? ": " + getterType : ''; var toInsert = "get " + getterName + "()" + typ + " " + getterContent; addTsComponentMember(host, componentPath, getterName, toInsert, ts__default["default"].SyntaxKind.GetAccessor, modifiers, toImport); } /** * Adds a setter to the first component in a given file. * @param host Source tree. * @param componentPath Path on which to find the component. * @param setterName Name of the setter to add. * @param setterArgName Name of the setter's argument. * @param setterArgType Type of the setter's argument. * @param setterContent Content of the setter to add, , including the opening * and closing brackets, e.g.: `'{ this.prop = arg; }'`. * @param modifiers Setter modifiers, e.g. `['private']`. * @param toImport Identifiers to import. */ function addTsComponentSetter(host, componentPath, setterName, setterArgName, setterArgType, setterContent, modifiers, toImport) { var typ = setterArgType ? ": " + setterArgType : ''; var toInsert = "set " + setterName + "(" + setterArgName + typ + ") " + setterContent; addTsComponentMember(host, componentPath, setterName, toInsert, ts__default["default"].SyntaxKind.SetAccessor, modifiers, toImport); } /** * Adds a method to the first component in a given file. * @param host Source tree. * @param componentPath Path on which to find the component. * @param methodName Name of the method to add. * @param methodContent Content of the method to add, including the method's * signature, but excluding its name, e.g.: * `'(arg1: number): string { return arg1.toString(); }'` * @param modifiers Property modifiers, e.g. `['private', 'static']`. * @param toImport Identifiers to import. */ function addTsComponentMethod(host, componentPath, methodName, methodContent, modifiers, toImport) { var toInsert = "" + methodName + methodContent; addTsComponentMember(host, componentPath, methodName, toInsert, ts__default["default"].SyntaxKind.MethodDeclaration, modifiers, toImport); } /** * Adds an argument to the first constructor of the first component in a given * file. * @param host Source tree. * @param componentPath Path on which to find the component. * @param paramName Name of the parameter to add. * @param paramType Type of the parameter to add. * @param modifiers Property modifiers, e.g. `['private']`. * @param toImport Identifiers to import. */ function addTsComponentConstructorParameter(host, componentPath, paramName, paramType, modifiers, toImport) { if (modifiers === void 0) { modifiers = []; } if (toImport === void 0) { toImport = []; } var source = getTsSourceFile(host, componentPath); var component = getFirstComponent(source); if (!component) { return console.error("No `@Component` found in \"" + componentPath + "\"."); } var members = component.members; var constructor = component.members && component.members.find(function (m) { return ts__default["default"].isConstructorDeclaration(m); }); var modifiersStr = modifiers.length ? modifiers.join(' ') + ' ' : ''; var paramStr = "" + modifiersStr + paramName + ": " + paramType; var changes = []; if (constructor) { // Constructor already exists, simply add param if it doesn't exist var params = constructor.parameters; if (params && params.find(function (p) { return ts__default["default"].isParameter(p) && p.name.getText() === paramName; })) { return; } changes.push(insertInNodeArray(componentPath, params, paramStr)); } else { // Constructor doesn't exist, create it after all properties var indent = getIndentation(component.getFullText()); var toInsert = "\n\n" + indent + "constructor(" + paramStr + ") {}"; changes.push(astUtils.insertAfterLastOccurrence(members.filter(function (m) { return ts__default["default"].isPropertyDeclaration(m); }), toInsert, componentPath, members.pos, ts__default["default"].SyntaxKind.PropertyDeclaration)); } toImport.forEach(function (t) { return changes.push(insertTsImport(source, componentPath, t)); }); applyChanges(host, componentPath, changes); } /** * Set the `inlineTemplate` of the first component of a given file. * @param host Source tree. * @param componentPath Path on which to find the component. * @param template String to add as inline template. */ function setTsComponentInlineTemplate(host, componentPath, template) { var source = getTsSourceFile(host, componentPath); var metadatas = astUtils.getDecoratorMetadata(source, 'Component', '@angular/core'); if (metadatas.length === 0) { return console.error("No `@Component` found in \"" + componentPath + "\"."); } var metadata = metadatas[0]; if (!ts__default["default"].isObjectLiteralExpression(metadata)) { throw new schematics.SchematicsException("Invalid `@Component` metadata in \"" + componentPath + "\"."); } var props = metadata.properties; var templateProp = props.find(function (p) { return ts__default["default"].isPropertyAssignment(p) && p.name.getText() === 'template'; }); var indent = getIndentation(metadata.getText()); var templateStr = "`" + indentNewLines("\n" + template, indent + indent) + "\n" + indent + "`"; var change$1; if (templateProp) { // `template` already exists change$1 = new change.ReplaceChange(componentPath, templateProp.initializer.pos, templateProp.initializer.getFullText(), templateStr); } else { // `template` doesn't exist (create new property) change$1 = insertInNodeArray(componentPath, props, "template: " + templateStr); } applyChanges(host, componentPath, [change$1]); } /** * Removes all comments from an HTML file. * @param content HTML file content. * @returns HTML file content with no comments. */ function htmlRemoveComments(content) { var commentsRegExp = /<!--[^]*?-->/g; return content.replace(commentsRegExp, ''); } /** * Whether a given HTML file contains an HTML element with a given tag. * @param content HTML file content. * @param tag Tag of element to check (inserted in a `RegExp`). * @returns Whether the given HTML file content contains an HTML element with * the given tag. */ function htmlContainsTag(content, tag) { var tagRegExp = new RegExp("<" + tag + "\\b[^>]*>"); return htmlRemoveComments(content).match(tagRegExp) !== null; } /** * Suffix used for the root i18n file. */ var i18nFileRegExp = /\.i18n\.ts$/; /** * Returns the root i18n for a given form. * @param host Source tree. * @param moduleDir Path of the module directory where the root i18n should be * found. * @returns Path of the root i18n. */ function getRootI18nPath(host, moduleDir) { var i18nFile = host .getDir(moduleDir) .subfiles.find(function (file) { return i18nFileRegExp.test(file); }); if (!i18nFile) { throw new schematics.SchematicsException("Could not find the root i18n file at: \"" + moduleDir.slice(1) + "\""); } return core.join(moduleDir, i18nFile); } /** * Adds a form's locale to the root i18n file. * @param host Source tree. * @param rootI18nPath Path of the root i18n. * @param locale Locale being added. * @param localePath Path of the form's locale. * @param localeTsName Exported named of the form's locale. */ function addFormLocaleToRootI18n(host, rootI18nPath, locale, localePath, localeTsName) { var source = getTsSourceFile(host, rootI18nPath); var i18nObj = getExportedI18nObject(source); if (!i18nObj) { throw new schematics.SchematicsException("Could not find an exported i18n object in \"" + rootI18nPath.slice(1) + "\"."); } var relativeLocaleLocation = relativeTsImportPath(rootI18nPath, localePath); var changes = [ insertTsImport(source, rootI18nPath, new TsImport(localeTsName, relativeLocaleLocation)), ]; // Property for the provided locale var props = i18nObj.properties; var relevantProp = props.find(function (p) { return ts__default["default"].isPropertyAssignment(p) && propertyAsString(p.name.getText()) === locale; }); if (relevantProp) { // Locale found var propInit = relevantProp.initializer; var mergeArgs = ts__default["default"].isCallExpression(propInit) && propInit.arguments; if (!mergeArgs) { return console.error("Unable to add the i18n object exported by \"" + localePath.slice(1) + "\" " + ("to \"" + rootI18nPath.slice(1) + "\". Please make sure to add the object ") + 'manually.'); } changes.push(insertInNodeArray(rootI18nPath, mergeArgs, localeTsName)); } else { // Locale not found (create it) var localeProp = stringAsProperty(locale); changes.push(insertInNodeArray(rootI18nPath, props, localeProp + ": LfI18n.mergeTranslations(" + localeTsName + ")"), insertTsImport(source, rootI18nPath, new TsImport('LfI18n', '@lightweightform/core'))); } applyChanges(host, rootI18nPath, changes); } /** * Returns the exported i18n object. * @param source TS source code. * @returns Exported i18n object. */ function getExportedI18nObject(source) { // Exported declarations var decls = source.statements .filter(function (s) { return ts__default["default"].isVariableStatement(s) && s.modifiers && s.modifiers.find(function (m) { return m.kind === ts__default["default"].SyntaxKind.ExportKeyword; }); }) .flatMap(function (v) { return v.declarationList.declarations; }); // Get the first declaration exporting an object. var decl = decls.find(function (d) { return !!d.initializer && ts__default["default"].isObjectLiteralExpression(d.initializer); }); return decl && decl.initializer; } /** * Regex matching the suffix of a module path. */ var moduleSuffixRegExp = /\.module\.ts$/; /** * Regex matching the suffix of a routing module path. */ var routingModuleSuffixRegExp = /-routing\.module\.ts$/; /** * Returns the path of the Angular module file for the given module name. * @param host Source tree. * @param project Project for which to fetch the module path. * @param moduleName Name of the module for which to fetch the module path. * @param routing Whether to fetch the routing module. * @param name Possibly a name provided by a schematic where a component has * been generated for which we want to find its declaring module. * @returns Angular module path. */ function getModulePath(host, project, moduleName, routing, name) { if (routing === void 0) { routing = false; } if (name === void 0) { name = ''; } return findModule.findModuleFromOptions(host, { path: workspace.buildDefaultPath(project), name: name, module: moduleName || 'app', moduleExt: routing ? '-routing.module.ts' : '.module.ts', }); } /** * Returns the name of a module given its Angular module path. * @param modulePath Angular module path. * @param routing Whether the module path belongs to a routing module. * @returns Module name. */ function getModuleNameFromPath(modulePath, routing) { if (routing === void 0) { routing = false; } return core.basename(modulePath).replace(routing ? routingModuleSuffixRegExp : moduleSuffixRegExp, ''); } /** * Adds something to the metadata of an `NgModule`. * @param host Source tree. * @param modulePath Path of the Angular module on which to add the import. * @param metadataField Metadata field on which to add something. * @param expression Expression to add. * @param toImport Identifiers to import. */ function addNgModuleMetadata(host, modulePath, metadataField, expression, toImport) { if (toImport === void 0) { toImport = []; } var source = getTsSourceFile(host, modulePath); var changes = astUtils.addSymbolToNgModuleMetadata(source, modulePath, metadataField, expression); toImport.forEach(function (t) { return changes.push(insertTsImport(source, modulePath, t)); }); applyChanges(host, modulePath, changes); } /** * Adds a declaration into an `NgModule`. * @param host Source tree. * @param modulePath Path of the Angular module on which to add the declaration. * @param declarationName Name of the declaration. * @param declarationLocation Location of declaration (where to import it from). */ function addNgModuleDeclaration(host, modulePath, declarationName, declarationLocation) { addNgModuleMetadata(host, modulePath, 'declarations', declarationName, [ new TsImport(declarationName, declarationLocation), ]); } /** * Adds an import into an `NgModule`. * @param host Source tree. * @param modulePath Path of the Angular module on which to add the import. * @param importName Name of the import. * @param importLocation Location of import (where to import it from). * @param importExpression Expression to use in the module imports array (defaults to `importName`). */ function addNgModuleImport(host, modulePath, importName, importLocation, importExpression) { if (importExpression === void 0) { importExpression = importName; } addNgModuleMetadata(host, modulePath, 'imports', importExpression, [ new TsImport(importName, importLocation), ]); } /** * Adds a simple provider into an `NgModule`. * @param host Source tree. * @param modulePath Path of the Angular module on which to add the provider. * @param providerName Name of the provider. * @param providerLocation Location of provider (where to import it from). */ function addNgModuleProvider(host, modulePath, providerName, providerLocation) { addNgModuleMetadata(host, modulePath, 'providers', providerName, [ new TsImport(providerName, providerLocation), ]); } /** * Adds a provider of type `{provide: providerName, useValue: valueName}` into * an `NgModule`. * @param host Source tree. * @param modulePath Path of the Angular module on which to add the provider. * @param providerName Name of the provider. * @param providerLocation Location of provider (where to import it from). * @param valueName Name of the value. * @param valueLocation Location of value (where to import it from). */ function addNgModuleValueProvider(host, modulePath, providerName, providerLocation, valueName, valueLocation) { var toImport = [new TsImport(providerName, providerLocation)]; if (valueLocation) { toImport.push(new TsImport(valueName, valueLocation)); } addNgModuleMetadata(host, modulePath, 'providers', "{ provide: " + providerName + ", useValue: " + valueName + " }", toImport); } /** * Adds a provider of type `{provide: providerName, useClass: className}` into * an `NgModule`. * @param host Source tree. * @param modulePath Path of the Angular module on which to add the provider. * @param providerName Name of the provider. * @param providerLocation Location of provider (where to import it from). * @param className Name of the class. * @param classLocation Location of class (where to import it from). */ function addNgModuleClassProvider(host, modulePath, providerName, providerLocation, className, classLocation) { var toImport = [new TsImport(providerName, providerLocation)]; if (classLocation) { toImport.push(new TsImport(className, classLocation)); } addNgModuleMetadata(host, modulePath, 'providers', "{ provide: " + providerName + ", useClass: " + className + " }", toImport); } /** * Adds an export into an `NgModule`. * @param host Source tree. * @param modulePath Path of the Angular module on which to add the export. * @param exportName Name of the export.