in src/material/schematics/ng-update/migrations/package-imports-v8/secondary-entry-points-migration.ts [44:153]
override visitNode(declaration: ts.Node): void {
// 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 the check.
if (importLocation !== 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<string, ts.ImportSpecifier[]>();
// 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;
// Try to resolve the module name via the type checker, and if it fails, fall back to
// resolving it from our list of symbol to entry point mappings. Using the type checker is
// more accurate and doesn't require us to keep a list of symbols, but it won't work if
// the symbols don't exist anymore (e.g. after we remove the top-level @angular/material).
const moduleName =
resolveModuleName(elementName, this.typeChecker) ||
ENTRY_POINT_MAPPINGS[elementName.text] ||
null;
if (!moduleName) {
this.createFailureAtNode(
element,
`"${element.getText()}" was not found in the Material 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]);
}
}
// 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(`${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 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);
}