UNPKG

@angular/material

Version:
155 lines 8.65 kB
"use strict"; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ Object.defineProperty(exports, "__esModule", { value: true }); const schematics_1 = require("@angular/cdk/schematics"); const ts = require("typescript"); const module_specifiers_1 = require("../../../ng-update/typescript/module-specifiers"); const ONLY_SUBPACKAGE_FAILURE_STR = `Importing from "@angular/material" is deprecated. ` + `Instead import from the entry-point the symbol belongs to.`; const NO_IMPORT_NAMED_SYMBOLS_FAILURE_STR = `Imports from Angular Material should import ` + `specific symbols rather than importing the entire library.`; /** * Regex for testing file paths against to determine if the file is from the * Angular Material library. */ const ANGULAR_MATERIAL_FILEPATH_REGEX = new RegExp(`${module_specifiers_1.materialModuleSpecifier}/(.*?)/`); /** * A migration rule that updates imports which refer to the primary Angular Material * entry-point to use the appropriate secondary entry points (e.g. @angular/material/button). */ class SecondaryEntryPointsRule extends schematics_1.MigrationRule { constructor() { super(...arguments); this.printer = ts.createPrinter(); // Only enable this rule if the migration targets version 8. The primary // entry-point of Material has been marked as deprecated in version 8. this.ruleEnabled = this.targetVersion === schematics_1.TargetVersion.V8; } visitNode(declaration) { // Only look at import declarations. if (!ts.isImportDeclaration(declaration) || !ts.isStringLiteralLike(declaration.moduleSpecifier)) { return; } const importLocation = declaration.moduleSpecifier.text; // If the import module is not @angular/material, skip check. if (importLocation !== module_specifiers_1.materialModuleSpecifier) { 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; } // Whether the existing import declaration is using a single quote module specifier. const singleQuoteImport = declaration.moduleSpecifier.getText()[0] === `'`; // 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; // 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. const symbol = getDeclarationSymbolOfNode(elementName, this.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))) { this.createFailureAtNode(element, `"${element.getText()}" was not found in the Material library.`); return; } // 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 || symbol.declarations[0]; 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 "@angular/material" // elements are analyzed. const matches = sourceFile.match(ANGULAR_MATERIAL_FILEPATH_REGEX); if (!matches) { this.createFailureAtNode(element, `"${element.getText()}" was found to be imported ` + `from a file outside the Material library.`); return; } const [, moduleName] = matches; // 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]); } } // Transforms the import declaration into multiple import declarations that import // the given symbols from the individual secondary entry-points. For example: // import {MatCardModule, MatCardTitle} from '@angular/material/card'; // import {MatRadioModule} from '@angular/material/radio'; const newImportStatements = Array.from(importMap.entries()) .sort() .map(([name, elements]) => { const newImport = ts.createImportDeclaration(undefined, undefined, ts.createImportClause(undefined, ts.createNamedImports(elements)), createStringLiteral(`${module_specifiers_1.materialModuleSpecifier}/${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 "@angular/material" is deprecated. if (!newImportStatements) { this.createFailureAtNode(declaration.moduleSpecifier, ONLY_SUBPACKAGE_FAILURE_STR); return; } const recorder = this.getUpdateRecorder(declaration.moduleSpecifier.getSourceFile().fileName); // 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.SecondaryEntryPointsRule = SecondaryEntryPointsRule; /** * 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 literal['singleQuote'] = singleQuotes; return literal; } /** Gets the symbol that contains the value declaration of the given node. */ function getDeclarationSymbolOfNode(node, checker) { 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; } //# sourceMappingURL=secondary-entry-points-rule.js.map