UNPKG

@ptsecurity/mosaic

Version:
155 lines 8.47 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SecondaryEntryPointsMigration = void 0; // tslint:disable const schematics_1 = require("@angular/cdk/schematics"); const ts = require("typescript"); const ONLY_SUBPACKAGE_FAILURE_STR = `Importing from "@ptsecurity/mosaic" is deprecated. ` + `Instead import from the entry-point the symbol belongs to.`; const NO_IMPORT_NAMED_SYMBOLS_FAILURE_STR = `Imports from Mosaic should import ` + `specific symbols rather than importing the entire library.`; const legacyMosaicModuleSpecifier = '@ptsecurity/mosaic'; const currentMosaicModuleSpecifier = '@ptsecurity/mosaic'; const MOSAIC_AC_FILEPATH_REGEX = new RegExp(`${legacyMosaicModuleSpecifier}/(.*?)/`); // tslint:disable-next-line:no-var-requires const ENTRY_POINT_MAPPINGS = require('./mosaic-symbols.json'); // tslint:disable-next-line:no-null-keyword class SecondaryEntryPointsMigration extends schematics_1.Migration { constructor() { super(...arguments); this.printer = ts.createPrinter(); // Only enable this rule if the migration targets version 8. this.enabled = this.targetVersion === schematics_1.TargetVersion.V8 || this.targetVersion === schematics_1.TargetVersion.V9; } // tslint:disable-next-line:max-func-body-length visitNode(declaration) { if (!ts.isImportDeclaration(declaration) || !ts.isStringLiteralLike(declaration.moduleSpecifier)) { return; } const importLocation = declaration.moduleSpecifier.text; // skip check - if the import module is not @ptsecurity/mosaic if (importLocation !== legacyMosaicModuleSpecifier) { return; } // If no import clause is found, or nothing is named as a binding in the // import, add failure saying to import symbols in clause. if (!declaration.importClause || !declaration.importClause.namedBindings) { this.createFailureAtNode(declaration, NO_IMPORT_NAMED_SYMBOLS_FAILURE_STR); return; } // All named bindings in import clauses must be named symbols, otherwise add // failure saying to import symbols in clause. if (!ts.isNamedImports(declaration.importClause.namedBindings)) { this.createFailureAtNode(declaration, NO_IMPORT_NAMED_SYMBOLS_FAILURE_STR); return; } // If no symbols are in the named bindings then add failure saying to // import symbols in clause. if (!declaration.importClause.namedBindings.elements.length) { this.createFailureAtNode(declaration, NO_IMPORT_NAMED_SYMBOLS_FAILURE_STR); return; } // Map which consists of secondary entry-points and import specifiers which are used // within the current import declaration. const importMap = new Map(); // Determine the subpackage each symbol in the namedBinding comes from. for (const element of declaration.importClause.namedBindings.elements) { const elementName = element.propertyName ? element.propertyName : element.name; const moduleName = resolveModuleName(elementName, this.typeChecker) || // tslint:disable-next-line:no-null-keyword ENTRY_POINT_MAPPINGS[elementName.text] || null; if (!moduleName) { this.createFailureAtNode(element, `"${element.getText()}" was not found in the Mosaic library.`); return; } // The module name where the symbol is defined e.g. card, dialog. The // first capture group is contains the module name. if (importMap.has(moduleName)) { importMap.get(moduleName).push(element); } else { importMap.set(moduleName, [element]); } } const singleQuoteImport = declaration.moduleSpecifier.getText()[0] === `'`; // Transforms the import declaration into multiple import declarations that import // the given symbols from the individual secondary entry-points. For example: // import { McCardModule } from '@ptsecurity/mosaic/card'; // import { McRadioModule } from '@ptsecurity/mosaic/radio'; const newImportStatements = Array.from(importMap.entries()) .sort() .map(([name, elements]) => { const newImport = ts.createImportDeclaration(undefined, undefined, ts.createImportClause(undefined, ts.createNamedImports(elements)), createStringLiteral(`${currentMosaicModuleSpecifier}/${name}`, singleQuoteImport)); return this.printer.printNode(ts.EmitHint.Unspecified, newImport, declaration.getSourceFile()); }) .join('\n'); // Without any import statements that were generated, we can assume that this was an empty // import declaration. We still want to add a failure in order to make developers aware that // importing from "@ptsecurity/mosaic" is deprecated. if (!newImportStatements) { this.createFailureAtNode(declaration.moduleSpecifier, ONLY_SUBPACKAGE_FAILURE_STR); return; } const filePath = this.fileSystem.resolve(declaration.moduleSpecifier.getSourceFile().fileName); const recorder = this.fileSystem.edit(filePath); // Perform the replacement that switches the primary entry-point import to // the individual secondary entry-point imports. recorder.remove(declaration.getStart(), declaration.getWidth()); recorder.insertRight(declaration.getStart(), newImportStatements); } } exports.SecondaryEntryPointsMigration = SecondaryEntryPointsMigration; /** * Creates a string literal from the specified text. * @param text Text of the string literal. * @param singleQuotes Whether single quotes should be used when printing the literal node. */ function createStringLiteral(text, singleQuotes) { const literal = ts.createStringLiteral(text); // See: https://github.com/microsoft/TypeScript/blob/master/src/compiler/utilities.ts#L584-L590 // tslint:disable-next-line: no-string-literal literal['singleQuote'] = singleQuotes; return literal; } function getDeclarationSymbolOfNode(node, checker) { // tslint:disable-next-line:no-reserved-keywords const symbol = checker.getSymbolAtLocation(node); // Symbols can be aliases of the declaration symbol. e.g. in named import specifiers. // We need to resolve the aliased symbol back to the declaration symbol. // tslint:disable-next-line:no-bitwise if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) { return checker.getAliasedSymbol(symbol); } return symbol; } function resolveModuleName(node, typeChecker) { var _a; // Get the symbol for the named binding element. Note that we cannot determine the // value declaration based on the type of the element as types are not necessarily // specific to a given secondary entry-point (e.g. exports with the type of "string") // would resolve to the module types provided by TypeScript itself. // tslint:disable-next-line:no-reserved-keywords const symbol = getDeclarationSymbolOfNode(node, typeChecker); // If the symbol can't be found, or no declaration could be found within // the symbol, add failure to report that the given symbol can't be found. if (!symbol || !(symbol.valueDeclaration || (symbol.declarations && symbol.declarations.length !== 0))) { // tslint:disable-next-line:no-null-keyword return null; } // The filename for the source file of the node that contains the // first declaration of the symbol. All symbol declarations must be // part of a defining node, so parent can be asserted to be defined. const resolvedNode = symbol.valueDeclaration || ((_a = symbol.declarations) === null || _a === void 0 ? void 0 : _a[0]); if (!resolvedNode) { return null; } const sourceFile = resolvedNode.getSourceFile().fileName; // File the module the symbol belongs to from a regex match of the // filename. This will always match since only "@ptsecurity/mosaic" // elements are analyzed. const matches = sourceFile.match(MOSAIC_AC_FILEPATH_REGEX); // tslint:disable-next-line:no-null-keyword return matches ? matches[1] : null; } //# sourceMappingURL=secondary-entry-points-migration.js.map