diff --git a/src/cdk/schematics/migration.json b/src/cdk/schematics/migration.json index 009aa97b3ff4..4a95ab2a103f 100644 --- a/src/cdk/schematics/migration.json +++ b/src/cdk/schematics/migration.json @@ -1,10 +1,10 @@ { "$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { - "migration-v15": { - "version": "15.0.0-0", - "description": "Updates the Angular CDK to v15", - "factory": "./ng-update/index#updateToV15" + "migration-v16": { + "version": "16.0.0-0", + "description": "Updates the Angular CDK to v16", + "factory": "./ng-update/index#updateToV16" }, "ng-post-update": { "description": "Prints out results after ng-update.", diff --git a/src/cdk/schematics/ng-update/index.ts b/src/cdk/schematics/ng-update/index.ts index 975c350d1a0c..3019186fd9d4 100644 --- a/src/cdk/schematics/ng-update/index.ts +++ b/src/cdk/schematics/ng-update/index.ts @@ -13,10 +13,10 @@ import {createMigrationSchematicRule, NullableDevkitMigration} from './devkit-mi const cdkMigrations: NullableDevkitMigration[] = []; -/** Entry point for the migration schematics with target of Angular CDK 15.0.0 */ -export function updateToV15(): Rule { +/** Entry point for the migration schematics with target of Angular CDK 16.0.0 */ +export function updateToV16(): Rule { return createMigrationSchematicRule( - TargetVersion.V15, + TargetVersion.V16, cdkMigrations, cdkUpgradeData, onMigrationComplete, diff --git a/src/cdk/schematics/ng-update/migrations/misc-template.ts b/src/cdk/schematics/ng-update/migrations/misc-template.ts index f9ff3acc266e..e275ad010889 100644 --- a/src/cdk/schematics/ng-update/migrations/misc-template.ts +++ b/src/cdk/schematics/ng-update/migrations/misc-template.ts @@ -6,10 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {TargetVersion} from '../../update-tool/target-version'; import {ResolvedResource} from '../../update-tool/component-resource-collector'; import {Migration} from '../../update-tool/migration'; -import {findAllSubstringIndices} from '../typescript/literal'; import {UpgradeData} from '../upgrade-data'; /** @@ -17,20 +15,8 @@ import {UpgradeData} from '../upgrade-data'; * instances of outdated Angular CDK API that can't be migrated automatically. */ export class MiscTemplateMigration extends Migration { - // Only enable this rule if the migration targets version 6. The rule - // currently only includes migrations for V6 deprecations. - enabled = this.targetVersion !== TargetVersion.V15; + // There are currently no migrations for V16 deprecations. + enabled = false; - override visitTemplate(template: ResolvedResource): void { - // Migration for https://github.com/angular/components/pull/10325 (v6) - findAllSubstringIndices(template.content, 'cdk-focus-trap').forEach(offset => { - this.failures.push({ - filePath: template.filePath, - position: template.getCharacterAndLineOfPosition(template.start + offset), - message: - `Found deprecated element selector "cdk-focus-trap" which has been ` + - `changed to an attribute selector "[cdkTrapFocus]".`, - }); - }); - } + override visitTemplate(template: ResolvedResource): void {} } diff --git a/src/cdk/schematics/update-tool/target-version.ts b/src/cdk/schematics/update-tool/target-version.ts index 65af5d7f4e76..f9a57478207b 100644 --- a/src/cdk/schematics/update-tool/target-version.ts +++ b/src/cdk/schematics/update-tool/target-version.ts @@ -10,7 +10,7 @@ // Used in an `Object.keys` call below so it can't be `const enum`. // tslint:disable-next-line:prefer-const-enum export enum TargetVersion { - V15 = 'version 15', + V16 = 'version 16', } /** diff --git a/src/material/schematics/migration.json b/src/material/schematics/migration.json index 44e2c43d7c0a..d12830a5789f 100644 --- a/src/material/schematics/migration.json +++ b/src/material/schematics/migration.json @@ -1,10 +1,10 @@ { "$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { - "migration-v15": { - "version": "15.0.0-0", - "description": "Updates the Angular Material to v15", - "factory": "./ng-update/index_bundled#updateToV15" + "migration-v16": { + "version": "16.0.0-0", + "description": "Updates the Angular Material to v16", + "factory": "./ng-update/index#updateToV16" } } } diff --git a/src/material/schematics/ng-update/index.ts b/src/material/schematics/ng-update/index.ts index 0ac400288743..7e20d5e4df7d 100644 --- a/src/material/schematics/ng-update/index.ts +++ b/src/material/schematics/ng-update/index.ts @@ -12,16 +12,15 @@ import { NullableDevkitMigration, TargetVersion, } from '@angular/cdk/schematics'; -import {LegacyComponentsMigration} from './migrations/legacy-components-v15'; import {materialUpgradeData} from './upgrade-data'; -const materialMigrations: NullableDevkitMigration[] = [LegacyComponentsMigration]; +const materialMigrations: NullableDevkitMigration[] = []; -/** Entry point for the migration schematics with target of Angular Material v15 */ -export function updateToV15(): Rule { +/** Entry point for the migration schematics with target of Angular Material v16 */ +export function updateToV16(): Rule { return createMigrationSchematicRule( - TargetVersion.V15, + TargetVersion.V16, materialMigrations, materialUpgradeData, onMigrationComplete, diff --git a/src/material/schematics/ng-update/migrations/legacy-components-v15/constants.ts b/src/material/schematics/ng-update/migrations/legacy-components-v15/constants.ts deleted file mode 100644 index e050d6a62878..000000000000 --- a/src/material/schematics/ng-update/migrations/legacy-components-v15/constants.ts +++ /dev/null @@ -1,129 +0,0 @@ -/** - * @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 - */ - -export const COMPONENTS = [ - 'autocomplete', - 'button', - 'core', - 'card', - 'checkbox', - 'chips', - 'dialog', - 'form-field', - 'input', - 'list', - 'menu', - 'paginator', - 'progress-bar', - 'progress-spinner', - 'radio', - 'select', - 'slide-toggle', - 'snack-bar', - 'slider', - 'table', - 'tabs', - 'tooltip', -]; - -export const MAT_IMPORT_CHANGES = COMPONENTS.flatMap(component => [ - { - old: `@angular/material/${component}`, - new: `@angular/material/legacy-${component}`, - }, - { - old: `@angular/material/${component}/testing`, - new: `@angular/material/legacy-${component}/testing`, - }, -]); - -export const MDC_IMPORT_CHANGES = COMPONENTS.flatMap(component => [ - { - old: `@angular/material-experimental/mdc-${component}`, - new: `@angular/material/${component}`, - }, - { - old: `@angular/material-experimental/mdc-${component}/testing`, - new: `@angular/material/${component}/testing`, - }, -]); - -export const CUSTOM_TS_SYMBOL_RENAMINGS = [ - {old: 'getMatAutocompleteMissingPanelError', new: 'getMatLegacyAutocompleteMissingPanelError'}, - {old: 'TransitionCheckState', new: 'LegacyTransitionCheckState'}, - {old: 'MatTestDialogOpener', new: 'MatTestLegacyDialogOpener'}, - {old: 'AutoFocusTarget', new: 'LegacyAutoFocusTarget'}, - {old: 'DialogRole', new: 'LegacyDialogRole'}, - {old: 'DialogPosition', new: 'LegacyDialogPosition'}, - {old: '_closeDialogVia', new: '_closeLegacyDialogVia'}, - {old: 'FormFieldControlHarness', new: 'LegacyFormFieldControlHarness'}, - {old: 'FloatLabelType', new: 'LegacyFloatLabelType'}, - {old: 'getMatFormFieldDuplicatedHintError', new: 'getMatLegacyFormFieldDuplicatedHintError'}, - {old: 'getMatFormFieldMissingControlError', new: 'getMatLegacyFormFieldMissingControlError'}, - { - old: 'getMatFormFieldPlaceholderConflictError', - new: 'getMatLegacyFormFieldPlaceholderConflictError', - }, - {old: 'getMatInputUnsupportedTypeError', new: 'getMatLegacyInputUnsupportedTypeError'}, - {old: 'fadeInItems', new: 'fadeInLegacyItems'}, - {old: 'MenuPositionX', new: 'LegacyMenuPositionX'}, - {old: 'MenuPositionY', new: 'LegacyMenuPositionY'}, - {old: 'transformMenu', new: 'transformLegacyMenu'}, - {old: 'PageEvent', new: 'LegacyPageEvent'}, - {old: 'ProgressAnimationEnd', new: 'LegacyProgressAnimationEnd'}, - {old: 'ProgressBarMode', new: 'LegacyProgressBarMode'}, - {old: 'ProgressSpinnerMode', new: 'LegacyProgressSpinnerMode'}, - {old: 'SimpleSnackBar', new: 'LegacySimpleSnackBar'}, - {old: 'TextOnlySnackBar', new: 'LegacyTextOnlySnackBar'}, - {old: 'ScrollDirection', new: 'LegacyScrollDirection'}, - {old: 'TooltipComponent', new: 'LegacyTooltipComponent'}, - {old: 'getMatTooltipInvalidPositionError', new: 'getMatLegacyTooltipInvalidPositionError'}, - {old: 'TooltipPosition', new: 'LegacyTooltipPosition'}, - {old: 'TooltipTouchGestures', new: 'LegacyTooltipTouchGestures'}, - {old: 'TooltipVisibility', new: 'LegacyTooltipVisibility'}, - {old: 'SCROLL_THROTTLE_MS', new: 'LEGACY_SCROLL_THROTTLE_MS'}, -]; - -export const COMPONENT_THEME_MIXINS = COMPONENTS.concat(['option', 'optgroup']).flatMap( - component => [ - `${component}-theme`, - `${component}-color`, - `${component}-density`, - `${component}-typography`, - ], -); - -export const CUSTOM_SASS_MIXIN_RENAMINGS: {[key: string]: string} = { - 'all-component-themes': 'all-legacy-component-themes', - 'all-component-colors': 'all-legacy-component-colors', - 'all-component-typographies': 'all-legacy-component-typographies', - 'private-all-component-densities': 'private-all-legacy-component-densities', - 'typography-hierarchy': 'legacy-typography-hierarchy', -}; - -export const CUSTOM_SASS_FUNCTION_RENAMINGS: {[key: string]: string} = { - 'define-typography-config': 'define-legacy-typography-config', -}; - -export const MIGRATED_CORE_SYMBOLS: {[key: string]: string} = { - 'MAT_OPTGROUP': 'MAT_LEGACY_OPTGROUP', - 'MatOptionSelectionChange': 'MatLegacyOptionSelectionChange', - 'MatOptionParentComponent': 'MatLegacyOptionParentComponent', - 'MAT_OPTION_PARENT_COMPONENT': 'MAT_LEGACY_OPTION_PARENT_COMPONENT', - '_countGroupLabelsBeforeOption': '_countGroupLabelsBeforeLegacyOption', - '_getOptionScrollPosition': '_getLegacyOptionScrollPosition', - '_MatOptionBase': '_MatLegacyOptionBase', - '_MatOptgroupBase': '_MatLegacyOptgroupBase', - 'MatOptionModule': 'MatLegacyOptionModule', - 'MatOption': 'MatLegacyOption', - 'MatOptgroup': 'MatLegacyOptgroup', - 'MatOptionHarness': 'MatLegacyOptionHarness', - 'OptionHarnessFilters': 'LegacyOptionHarnessFilters', - 'MatOptgroupHarness': 'MatLegacyOptgroupHarness', - 'OptgroupHarnessFilters': 'LegacyOptgroupHarnessFilters', -}; diff --git a/src/material/schematics/ng-update/migrations/legacy-components-v15/index.ts b/src/material/schematics/ng-update/migrations/legacy-components-v15/index.ts deleted file mode 100644 index 780ceab27080..000000000000 --- a/src/material/schematics/ng-update/migrations/legacy-components-v15/index.ts +++ /dev/null @@ -1,446 +0,0 @@ -/** - * @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 - */ - -import * as ts from 'typescript'; -import * as postcss from 'postcss'; -import * as scss from 'postcss-scss'; -import { - CUSTOM_TS_SYMBOL_RENAMINGS, - MAT_IMPORT_CHANGES, - MDC_IMPORT_CHANGES, - COMPONENT_THEME_MIXINS, - CUSTOM_SASS_MIXIN_RENAMINGS, - CUSTOM_SASS_FUNCTION_RENAMINGS, - MIGRATED_CORE_SYMBOLS, -} from './constants'; -import {Migration, ResolvedResource, TargetVersion, WorkspacePath} from '@angular/cdk/schematics'; -import {extname} from 'path'; - -export class LegacyComponentsMigration extends Migration { - enabled = this.targetVersion === TargetVersion.V15; - private _updates: {offset: number; update: () => void}[] | undefined; - - override visitStylesheet(stylesheet: ResolvedResource): void { - const extension = extname(stylesheet.filePath).toLowerCase(); - - if (!stylesheet.inline && extension && extension !== '.css' && extension !== '.scss') { - return; - } - - let namespace: string | undefined = undefined; - const processor = new postcss.Processor([ - { - postcssPlugin: 'legacy-components-v15-plugin', - AtRule: { - use: node => { - namespace = namespace ?? this._parseSassNamespace(node); - }, - include: node => this._handleAtInclude(node, stylesheet.filePath, namespace), - }, - RootExit: root => this._handleRootNode(root, stylesheet.filePath, namespace), - }, - ]); - try { - processor.process(stylesheet.content, {syntax: scss}).sync(); - } catch (e) { - this.logger.error(`${e}`); - this.logger.warn(`Failed to process stylesheet: ${stylesheet.filePath} (see error above).`); - } - } - - /** Returns the namespace of the given at-rule if it is importing from @angular/material. */ - private _parseSassNamespace(node: postcss.AtRule): string | undefined { - if (node.params.startsWith('@angular/material', 1)) { - return node.params.split(/\s+/).pop(); - } - return; - } - - /** Handles updating the at-include rules of legacy component mixins. */ - private _handleAtInclude( - node: postcss.AtRule, - filePath: WorkspacePath, - namespace?: string, - ): void { - if (!namespace || !node.source?.start) { - return; - } - const original = node.toString(); - const [atInclude, delim, mixinName, ...rest] = original.split(/([.(;])/); - if (mixinName === 'core') { - // The parameters may include function calls that need to be renamed. - const updatedFunctionsRest = Object.keys(CUSTOM_SASS_FUNCTION_RENAMINGS).reduce( - (s, r) => s.replace(new RegExp(r, 'g'), CUSTOM_SASS_FUNCTION_RENAMINGS[r]), - rest.join(''), - ); - // The parameters are moved from the core include to the typography include. - const includeTypography = [atInclude, delim, mixinName, updatedFunctionsRest] - .join('') - .replace(`${namespace}.core`, `${namespace}.all-legacy-component-typographies`); - // If there are no params, pass the default typography config. - const hierarchyParams = - updatedFunctionsRest.replace(/[()\s]/g, '') === '' - ? `(${namespace}.define-legacy-typography-config())` - : updatedFunctionsRest; - const includeHierarchy = [atInclude, delim, mixinName, hierarchyParams] - .join('') - .replace(`${namespace}.core`, `${namespace}.legacy-typography-hierarchy`); - const indent = original.match(/^\s*/)?.[0] || ''; - // Replace the whole original with a comment, typography include, and legacy-core include. - this._replaceAt(filePath, node.source.start.offset, { - old: original, - new: [ - `// TODO(v15): As of v15 ${namespace}.legacy-core no longer includes default typography styles.`, - `// The following line adds:`, - `// 1. Default typography styles for all components`, - `// 2. Styles for typography hierarchy classes (e.g. .mat-headline-1)`, - `// If you specify typography styles for the components you use elsewhere, you should delete this line.`, - `// If you don't need the default component typographies but still want the hierarchy styles,`, - `// you can delete this line and instead use:`, - `// \`${includeHierarchy};\``, - `${includeTypography};`, - `${indent}@include ${namespace}.legacy-core()`, - ].join('\n'), - }); - } else if (CUSTOM_SASS_MIXIN_RENAMINGS[mixinName]) { - this._replaceAt(filePath, node.source.start.offset, { - old: `${namespace}.${mixinName}`, - new: `${namespace}.${CUSTOM_SASS_MIXIN_RENAMINGS[mixinName]}`, - }); - } else if (this._isLegacyMixin(node, namespace)) { - this._replaceAt(filePath, node.source.start.offset, { - old: `${namespace}.`, - new: `${namespace}.legacy-`, - }); - } - } - - /** Handles updating the root node. */ - private _handleRootNode(root: postcss.Root, file: any, namespace?: string) { - if (!namespace) { - return; - } - // @functions could be referenced anywhere, so we need to just walk everything from the root - // and replace all instances that are not in comments. - root.walk(node => { - if (node.source?.start != null && node.type !== 'comment') { - const srcString = node.toString(); - for (const old in CUSTOM_SASS_FUNCTION_RENAMINGS) { - if (srcString.includes(`${namespace}.${old}`)) { - this._replaceAt(file, node.source.start.offset, { - old: `${namespace}.${old}`, - new: `${namespace}.${CUSTOM_SASS_FUNCTION_RENAMINGS[old]}`, - }); - } - } - } - }); - } - - /** Returns true if the given at-include rule is a use of a legacy component mixin. */ - private _isLegacyMixin(node: postcss.AtRule, namespace: string): boolean { - if (!node.params.startsWith(`${namespace}.`)) { - return false; - } - for (let i = 0; i < COMPONENT_THEME_MIXINS.length; i++) { - if (node.params.startsWith(`${namespace}.${COMPONENT_THEME_MIXINS[i]}`)) { - return true; - } - } - return false; - } - - override visitNode(node: ts.Node): void { - if (ts.isImportDeclaration(node)) { - this._handleImportDeclaration(node); - return; - } - if (this._isDestructuredAsyncLegacyImport(node)) { - this._handleDestructuredAsyncImport(node); - return; - } - if (this._isImportCallExpression(node)) { - this._handleImportExpression(node); - return; - } - } - - /** - * Handles updating the module specifier of - * @angular/material and @angular/material-experimental imports. - * - * Also updates the named import bindings of @angular/material imports. - */ - private _handleImportDeclaration(node: ts.ImportDeclaration): void { - const moduleSpecifier = node.moduleSpecifier as ts.StringLiteral; - const matImportChange = this._findMatImportChange(moduleSpecifier); - const mdcImportChange = this._findMdcImportChange(moduleSpecifier); - - if (this._isCoreImport(moduleSpecifier.text)) { - this._handleCoreImportDeclaration(node); - } else if (matImportChange) { - this._tsReplaceAt(node, matImportChange); - - if (node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings)) { - this._handleNamedImportBindings(node.importClause.namedBindings); - } - } else if (mdcImportChange) { - this._tsReplaceAt(node, mdcImportChange); - } - } - - private _isCoreImport(importPath: string) { - return ['@angular/material/core', '@angular/material/core/testing'].includes(importPath); - } - - private _handleCoreImportDeclaration(node: ts.ImportDeclaration) { - const moduleSpecifier = node.moduleSpecifier as ts.StringLiteral; - - if (node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings)) { - this._splitCoreImport(node, node.importClause.namedBindings); - } else { - this._tsReplaceAt(node, { - old: moduleSpecifier.text, - new: moduleSpecifier.text.replace( - '@angular/material/core', - '@angular/material/legacy-core', - ), - }); - } - } - - private _splitCoreImport( - node: ts.Node, - namedBindings: ts.NamedImports | ts.ObjectBindingPattern, - ) { - const migratedSymbols = []; - const unmigratedSymbols = []; - for (const element of namedBindings.elements) { - if (this._isMigratedCoreSymbol(element)) { - migratedSymbols.push(element); - } else { - unmigratedSymbols.push(element); - } - } - const unmigratedImportDeclaration = unmigratedSymbols.length - ? [this._stripImports(node.getText(), migratedSymbols)] - : []; - const migratedImportDeclaration = migratedSymbols.length - ? [ - this._updateImportedCoreSymbols( - this._stripImports(node.getText(), unmigratedSymbols).replace( - '@angular/material/core', - '@angular/material/legacy-core', - ), - migratedSymbols, - ), - ] - : []; - this._tsReplaceAt(node, { - old: node.getText(), - new: [...unmigratedImportDeclaration, ...migratedImportDeclaration].join('\n'), - }); - } - - private _isMigratedCoreSymbol(node: ts.ImportSpecifier | ts.BindingElement): boolean { - const name = node.propertyName ? node.propertyName : node.name; - if (!ts.isIdentifier(name)) { - return false; - } - - return !!MIGRATED_CORE_SYMBOLS[name.escapedText.toString()]; - } - - private _stripImports(importString: string, remove: (ts.ImportSpecifier | ts.BindingElement)[]) { - for (const symbol of remove) { - importString = importString - .replace(new RegExp(`,\\s*${symbol.getText()}`), '') - .replace(new RegExp(`${symbol.getText()},\\s*`), '') - .replace(symbol.getText(), ''); - } - return importString; - } - - private _updateImportedCoreSymbols( - importString: string, - rename: (ts.ImportSpecifier | ts.BindingElement)[], - ) { - return rename.reduce((result, symbol) => { - const oldName = symbol.propertyName ? symbol.propertyName.getText() : symbol.name.getText(); - const newName = MIGRATED_CORE_SYMBOLS[oldName]; - const aliasedName = symbol.propertyName ? symbol.name.getText() : oldName; - const separator = ts.isImportSpecifier(symbol) ? ' as ' : ': '; - return result.replace(symbol.getText(), `${newName}${separator}${aliasedName}`); - }, importString); - } - - /** - * Handles updating the module specifier of - * @angular/material and @angular/material-experimental import expressions. - */ - private _handleImportExpression(node: ts.CallExpression): void { - const moduleSpecifier = node.arguments[0] as ts.StringLiteral; - - const matImportChange = this._findMatImportChange(moduleSpecifier); - if (matImportChange) { - this._tsReplaceAt(node, matImportChange); - return; - } - - const mdcImportChange = this._findMdcImportChange(moduleSpecifier); - if (mdcImportChange) { - this._tsReplaceAt(node, mdcImportChange); - } - } - - /** Handles updating the named bindings of awaited @angular/material import expressions. */ - private _handleDestructuredAsyncImport( - node: ts.VariableDeclaration & {name: ts.ObjectBindingPattern}, - ): void { - const importPath = (node!.initializer as any).expression.arguments[0].text; - if (ts.isVariableStatement(node.parent.parent) && this._isCoreImport(importPath)) { - this._splitCoreImport(node.parent.parent, node.name); - } else { - for (let i = 0; i < node.name.elements.length; i++) { - this._handleNamedBindings(node.name.elements[i]); - } - } - } - - /** Handles updating the named bindings of @angular/material imports. */ - private _handleNamedImportBindings(node: ts.NamedImports): void { - for (let i = 0; i < node.elements.length; i++) { - this._handleNamedBindings(node.elements[i]); - } - } - - /** Handles updating the named bindings of @angular/material imports and import expressions. */ - private _handleNamedBindings(node: ts.ImportSpecifier | ts.BindingElement): void { - const name = node.propertyName ? node.propertyName : node.name; - if (!ts.isIdentifier(name)) { - return; - } - - const separator = ts.isImportSpecifier(node) ? ' as ' : ': '; - const oldExport = name.escapedText.toString(); - - // Handle TS Symbols that have non-standard renamings. - const customMapping = CUSTOM_TS_SYMBOL_RENAMINGS.find(v => v.old === oldExport); - if (customMapping) { - const replacement = node.propertyName - ? customMapping.new - : `${customMapping.new}${separator}${customMapping.old}`; - this._tsReplaceAt(name, {old: oldExport, new: replacement}); - return; - } - - // Handle TS Symbols that have standard renamings. - const newExport = this._parseMatSymbol(oldExport); - if (newExport) { - const replacement = node.propertyName ? newExport : `${newExport}${separator}${oldExport}`; - this._tsReplaceAt(name, {old: oldExport, new: replacement}); - return; - } - } - - /** Returns the new symbol to be used for a given standard mat symbol. */ - private _parseMatSymbol(symbol: string): string | undefined { - if (symbol.startsWith('Mat')) { - return `MatLegacy${symbol.slice('Mat'.length)}`; - } - if (symbol.startsWith('mat')) { - return `matLegacy${symbol.slice('mat'.length)}`; - } - if (symbol.startsWith('_Mat')) { - return `_MatLegacy${symbol.slice('_Mat'.length)}`; - } - if (symbol.startsWith('MAT_')) { - return `MAT_LEGACY_${symbol.slice('MAT_'.length)}`; - } - if (symbol.startsWith('_MAT_')) { - return `_MAT_LEGACY_${symbol.slice('_MAT_'.length)}`; - } - if (symbol.endsWith('HarnessFilters')) { - return `Legacy${symbol}`; - } - return; - } - - /** - * Returns true if the given node is a variable declaration - * assigns the awaited result of an @angular/material import - * expression using an object binding. - */ - private _isDestructuredAsyncLegacyImport(node: ts.Node): node is ts.VariableDeclaration & { - name: ts.ObjectBindingPattern; - initializer: ts.AwaitExpression & {expression: ts.CallExpression} & { - arguments: [ts.StringLiteralLike]; - }; - } { - return ( - ts.isVariableDeclaration(node) && - !!node.initializer && - ts.isAwaitExpression(node.initializer) && - this._isImportCallExpression(node.initializer.expression) && - ts.isStringLiteral(node.initializer.expression.arguments[0]) && - !!this._findMatImportChange(node.initializer.expression.arguments[0]) && - ts.isObjectBindingPattern(node.name) - ); - } - - /** Gets whether the specified node is an import expression. */ - private _isImportCallExpression( - node: ts.Node, - ): node is ts.CallExpression & {arguments: [ts.StringLiteralLike]} { - return ( - ts.isCallExpression(node) && - node.expression.kind === ts.SyntaxKind.ImportKeyword && - node.arguments.length === 1 && - ts.isStringLiteralLike(node.arguments[0]) - ); - } - - private _findMatImportChange( - moduleSpecifier: ts.StringLiteral, - ): {old: string; new: string} | undefined { - return MAT_IMPORT_CHANGES.find(change => change.old === moduleSpecifier.text); - } - - private _findMdcImportChange( - moduleSpecifier: ts.StringLiteral, - ): {old: string; new: string} | undefined { - return MDC_IMPORT_CHANGES.find(change => change.old === moduleSpecifier.text); - } - - /** Updates the source file of the given ts node with the given replacements. */ - private _tsReplaceAt(node: ts.Node, str: {old: string; new: string}): void { - const filePath = this.fileSystem.resolve(node.getSourceFile().fileName); - this._replaceAt(filePath, node.pos, str); - } - - /** Updates the source file with the given replacements. */ - private _replaceAt( - filePath: WorkspacePath, - offset: number, - str: {old: string; new: string}, - ): void { - const index = this.fileSystem.read(filePath)!.indexOf(str.old, offset); - this._updates ??= []; - this._updates.push({ - offset: index, - update: () => - this.fileSystem.edit(filePath).remove(index, str.old.length).insertRight(index, str.new), - }); - } - - override postAnalysis(): void { - // Apply the updates in reverse offset order this ensures that there are no - // overlapping changes which would caused a change in offsets. - this._updates?.sort((a, b) => b.offset - a.offset).forEach(({update}) => update()); - } -} diff --git a/src/material/schematics/ng-update/test-cases/v15/legacy-components-v15.spec.ts b/src/material/schematics/ng-update/test-cases/v15/legacy-components-v15.spec.ts deleted file mode 100644 index 8cd76140d035..000000000000 --- a/src/material/schematics/ng-update/test-cases/v15/legacy-components-v15.spec.ts +++ /dev/null @@ -1,318 +0,0 @@ -import {UnitTestTree} from '@angular-devkit/schematics/testing'; -import {createTestCaseSetup} from '@angular/cdk/schematics/testing'; -import {join} from 'path'; -import {COMPONENTS} from '../../migrations/legacy-components-v15/constants'; -import {MIGRATION_PATH} from '../../../paths'; - -const PROJECT_ROOT_DIR = '/projects/cdk-testing'; -const THEME_FILE_PATH = join(PROJECT_ROOT_DIR, 'src/theme.scss'); -const TS_FILE_PATH = join(PROJECT_ROOT_DIR, 'src/app/app.component.ts'); - -describe('v15 legacy components migration', () => { - let tree: UnitTestTree; - - /** Writes an single line file. */ - let writeLine: (path: string, line: string) => void; - - /** Writes multiple lines to a file. */ - let writeLines: (path: string, lines: string[]) => void; - - /** Reads a single line file. */ - let readLine: (path: string) => string; - - /** Reads multiple lines from a file. */ - let readLines: (path: string) => string[]; - - /** Runs the v15 migration on the test application. */ - let runMigration: () => Promise<{logOutput: string}>; - - beforeEach(async () => { - const testSetup = await createTestCaseSetup('migration-v15', MIGRATION_PATH, []); - tree = testSetup.appTree; - runMigration = testSetup.runFixers; - readLine = (path: string) => tree.readContent(path); - readLines = (path: string) => tree.readContent(path).split('\n'); - writeLine = (path: string, lines: string) => testSetup.writeFile(path, lines); - writeLines = (path: string, lines: string[]) => testSetup.writeFile(path, lines.join('\n')); - }); - - describe('typescript migrations', () => { - async function runTypeScriptMigrationTest(ctx: string, opts: {old: string; new: string}) { - writeLine(TS_FILE_PATH, opts.old); - await runMigration(); - expect(readLine(TS_FILE_PATH)).withContext(ctx).toEqual(opts.new); - } - - async function runMultilineTypeScriptMigrationTest( - ctx: string, - opts: {old: string[]; new: string[]}, - ) { - writeLines(TS_FILE_PATH, opts.old); - await runMigration(); - expect(readLines(TS_FILE_PATH)).withContext(ctx).toEqual(opts.new); - } - - describe('material --> legacy', () => { - it('updates import declarations', async () => { - await runTypeScriptMigrationTest('named binding', { - old: `import {MatButton} from '@angular/material/button';`, - new: `import {MatLegacyButton as MatButton} from '@angular/material/legacy-button';`, - }); - await runTypeScriptMigrationTest('named binding w/ alias', { - old: `import {MatButton as Button} from '@angular/material/button';`, - new: `import {MatLegacyButton as Button} from '@angular/material/legacy-button';`, - }); - await runTypeScriptMigrationTest('multiple named bindings', { - old: `import {MatButton, MatButtonModule} from '@angular/material/button';`, - new: `import {MatLegacyButton as MatButton, MatLegacyButtonModule as MatButtonModule} from '@angular/material/legacy-button';`, - }); - await runTypeScriptMigrationTest('multiple named bindings w/ alias', { - old: `import {MatButton, MatButtonModule as ButtonModule} from '@angular/material/button';`, - new: `import {MatLegacyButton as MatButton, MatLegacyButtonModule as ButtonModule} from '@angular/material/legacy-button';`, - }); - await runMultilineTypeScriptMigrationTest('specific cases', { - old: [ - `import {ProgressAnimationEnd, ProgressBarMode} from '@angular/material/progress-bar';`, - `import {ProgressSpinnerMode} from '@angular/material/progress-spinner';`, - `import {AutoFocusTarget, DialogRole, DialogPosition, _closeDialogVia, MatTestDialogOpener} from '@angular/material/dialog';`, - `import {SimpleSnackBar, TextOnlySnackBar} from '@angular/material/snack-bar';`, - ], - new: [ - `import {LegacyProgressAnimationEnd as ProgressAnimationEnd, LegacyProgressBarMode as ProgressBarMode} from '@angular/material/legacy-progress-bar';`, - `import {LegacyProgressSpinnerMode as ProgressSpinnerMode} from '@angular/material/legacy-progress-spinner';`, - `import {LegacyAutoFocusTarget as AutoFocusTarget, LegacyDialogRole as DialogRole, LegacyDialogPosition as DialogPosition, _closeLegacyDialogVia as _closeDialogVia, MatTestLegacyDialogOpener as MatTestDialogOpener} from '@angular/material/legacy-dialog';`, - `import {LegacySimpleSnackBar as SimpleSnackBar, LegacyTextOnlySnackBar as TextOnlySnackBar} from '@angular/material/legacy-snack-bar';`, - ], - }); - await runTypeScriptMigrationTest('specific case w/ alias', { - old: `import {ProgressBarMode as MatProgressBarMode} from '@angular/material/progress-bar';`, - new: `import {LegacyProgressBarMode as MatProgressBarMode} from '@angular/material/legacy-progress-bar';`, - }); - await runTypeScriptMigrationTest('test code', { - old: `import {MatButtonHarness, ButtonHarnessFilters} from '@angular/material/button/testing';`, - new: `import {MatLegacyButtonHarness as MatButtonHarness, LegacyButtonHarnessFilters as ButtonHarnessFilters} from '@angular/material/legacy-button/testing';`, - }); - }); - - it('updates import expressions', async () => { - await runTypeScriptMigrationTest('destructured & awaited', { - old: `const {MatButton} = await import('@angular/material/button');`, - new: `const {MatLegacyButton: MatButton} = await import('@angular/material/legacy-button');`, - }); - await runTypeScriptMigrationTest('destructured & awaited w/ alias', { - old: `const {MatButton: Button} = await import('@angular/material/button');`, - new: `const {MatLegacyButton: Button} = await import('@angular/material/legacy-button');`, - }); - await runTypeScriptMigrationTest('promise', { - old: `const promise = import('@angular/material/button');`, - new: `const promise = import('@angular/material/legacy-button');`, - }); - await runTypeScriptMigrationTest('.then', { - old: `import('@angular/material/button').then(() => {});`, - new: `import('@angular/material/legacy-button').then(() => {});`, - }); - }); - - it('does not update non-legacy imports', async () => { - await runTypeScriptMigrationTest('non-legacy component', { - old: `import {MatButtonToggleModule} from '@angular/material/button-toggle';`, - new: `import {MatButtonToggleModule} from '@angular/material/button-toggle';`, - }); - }); - - it('splits @angular/material/core imports', async () => { - await runMultilineTypeScriptMigrationTest('core imports', { - old: [ - `import {VERSION, MatOption} from '@angular/material/core';`, - `import {CanDisable} from '@angular/material/core';`, - `import {MatOptionHarness} from '@angular/material/core/testing';`, - `import {VERSION as a, MatOption as b} from '@angular/material/core';`, - `const {mixinDisable, MatOptgroup} = await import('@angular/material/core');`, - `const {mixinDisable: c, MatOptgroup: d} = await import('@angular/material/core');`, - ], - new: [ - `import {VERSION} from '@angular/material/core';`, - `import {MatLegacyOption as MatOption} from '@angular/material/legacy-core';`, - `import {CanDisable} from '@angular/material/core';`, - `import {MatLegacyOptionHarness as MatOptionHarness} from '@angular/material/legacy-core/testing';`, - `import {VERSION as a} from '@angular/material/core';`, - `import {MatLegacyOption as b} from '@angular/material/legacy-core';`, - `const {mixinDisable} = await import('@angular/material/core');`, - `const {MatLegacyOptgroup: MatOptgroup} = await import('@angular/material/legacy-core');`, - `const {mixinDisable: c} = await import('@angular/material/core');`, - `const {MatLegacyOptgroup: d} = await import('@angular/material/legacy-core');`, - ], - }); - }); - }); - - describe('material-experimental --> material', () => { - it('updates import declarations', async () => { - await runTypeScriptMigrationTest('named binding', { - old: `import {MatButton} from '@angular/material-experimental/mdc-button';`, - new: `import {MatButton} from '@angular/material/button';`, - }); - await runTypeScriptMigrationTest('named binding w/ alias', { - old: `import {MatButton as Button} from '@angular/material-experimental/mdc-button';`, - new: `import {MatButton as Button} from '@angular/material/button';`, - }); - await runTypeScriptMigrationTest('multiple named bindings', { - old: `import {MatButton, MatButtonModule} from '@angular/material-experimental/mdc-button';`, - new: `import {MatButton, MatButtonModule} from '@angular/material/button';`, - }); - await runTypeScriptMigrationTest('multiple named bindings w/ alias', { - old: `import {MatButton, MatButtonModule as ButtonModule} from '@angular/material-experimental/mdc-button';`, - new: `import {MatButton, MatButtonModule as ButtonModule} from '@angular/material/button';`, - }); - await runTypeScriptMigrationTest('test code', { - old: `import {MatButtonHarness, ButtonHarnessFilters} from '@angular/material-experimental/mdc-button/testing';`, - new: `import {MatButtonHarness, ButtonHarnessFilters} from '@angular/material/button/testing';`, - }); - }); - - it('updates import expressions', async () => { - await runTypeScriptMigrationTest('destructured & awaited', { - old: `const {MatButton} = await import('@angular/material-experimental/mdc-button');`, - new: `const {MatButton} = await import('@angular/material/button');`, - }); - await runTypeScriptMigrationTest('destructured & awaited w/ alias', { - old: `const {MatButton: Button} = await import('@angular/material-experimental/mdc-button');`, - new: `const {MatButton: Button} = await import('@angular/material/button');`, - }); - await runTypeScriptMigrationTest('promise', { - old: `const promise = import('@angular/material-experimental/mdc-button');`, - new: `const promise = import('@angular/material/button');`, - }); - await runTypeScriptMigrationTest('.then', { - old: `import('@angular/material-experimental/mdc-button').then(() => {});`, - new: `import('@angular/material/button').then(() => {});`, - }); - }); - }); - }); - - describe('style migrations', () => { - async function runSassMigrationTest(ctx: string, opts: {old: string[]; new: string[]}) { - writeLines(THEME_FILE_PATH, opts.old); - await runMigration(); - expect(readLines(THEME_FILE_PATH)).withContext(ctx).toEqual(opts.new); - } - - it('updates all mixins', async () => { - const oldFile: string[] = [ - `@use '@angular/material' as mat;`, - `@include mat.all-component-themes($theme);`, - `@include mat.all-component-colors($theme);`, - `@include mat.private-all-component-densities($theme);`, - `@include mat.all-component-typographies($theme);`, - ]; - const newFile: string[] = [ - `@use '@angular/material' as mat;`, - `@include mat.all-legacy-component-themes($theme);`, - `@include mat.all-legacy-component-colors($theme);`, - `@include mat.private-all-legacy-component-densities($theme);`, - `@include mat.all-legacy-component-typographies($theme);`, - ]; - for (let i = 0; i < COMPONENTS.length; i++) { - oldFile.push( - ...[ - `@include mat.${COMPONENTS[i]}-theme($theme);`, - `@include mat.${COMPONENTS[i]}-color($theme);`, - `@include mat.${COMPONENTS[i]}-density($theme);`, - `@include mat.${COMPONENTS[i]}-typography($theme);`, - ], - ); - newFile.push( - ...[ - `@include mat.legacy-${COMPONENTS[i]}-theme($theme);`, - `@include mat.legacy-${COMPONENTS[i]}-color($theme);`, - `@include mat.legacy-${COMPONENTS[i]}-density($theme);`, - `@include mat.legacy-${COMPONENTS[i]}-typography($theme);`, - ], - ); - } - await runSassMigrationTest('all components', { - old: oldFile, - new: newFile, - }); - await runSassMigrationTest('w/ unique namespaces', { - old: [`@use '@angular/material' as material;`, `@include material.button-theme($theme);`], - new: [ - `@use '@angular/material' as material;`, - `@include material.legacy-button-theme($theme);`, - ], - }); - await runSassMigrationTest('w/ unique whitespace', { - old: [ - ` @use '@angular/material' as material ; `, - ` @include material.button-theme( $theme ) ; `, - ], - new: [ - ` @use '@angular/material' as material ; `, - ` @include material.legacy-button-theme( $theme ) ; `, - ], - }); - }); - - it('does not update non-mdc component mixins', async () => { - await runSassMigrationTest('datepicker', { - old: [`@use '@angular/material' as mat;`, `@include mat.datepicker-theme($theme);`], - new: [`@use '@angular/material' as mat;`, `@include mat.datepicker-theme($theme);`], - }); - await runSassMigrationTest('button-toggle', { - old: [`@use '@angular/material' as mat;`, `@include mat.button-toggle-theme($theme);`], - new: [`@use '@angular/material' as mat;`, `@include mat.button-toggle-theme($theme);`], - }); - }); - - it('updates sass functions', async () => { - await runSassMigrationTest('variable assignment', { - old: [ - `@use '@angular/material' as mat;`, - `$typography: mat.define-typography-config();`, - `@include mat.all-component-typographies(mat.define-typography-config());`, - ], - new: [ - `@use '@angular/material' as mat;`, - `$typography: mat.define-legacy-typography-config();`, - `@include mat.all-legacy-component-typographies(mat.define-legacy-typography-config());`, - ], - }); - }); - - it('updates mat.core mixin', async () => { - await runSassMigrationTest('mat.core mixin', { - old: [ - `@use '@angular/material' as mat;`, - `@include mat.core();`, - `@include mat.core(mat.define-typography-config(()));`, - `@include mat.core-theme(())`, - ], - new: [ - `@use '@angular/material' as mat;`, - `// TODO(v15): As of v15 mat.legacy-core no longer includes default typography styles.`, - `// The following line adds:`, - `// 1. Default typography styles for all components`, - `// 2. Styles for typography hierarchy classes (e.g. .mat-headline-1)`, - `// If you specify typography styles for the components you use elsewhere, you should delete this line.`, - `// If you don't need the default component typographies but still want the hierarchy styles,`, - `// you can delete this line and instead use:`, - `// \`@include mat.legacy-typography-hierarchy(mat.define-legacy-typography-config());\``, - `@include mat.all-legacy-component-typographies();`, - `@include mat.legacy-core();`, - `// TODO(v15): As of v15 mat.legacy-core no longer includes default typography styles.`, - `// The following line adds:`, - `// 1. Default typography styles for all components`, - `// 2. Styles for typography hierarchy classes (e.g. .mat-headline-1)`, - `// If you specify typography styles for the components you use elsewhere, you should delete this line.`, - `// If you don't need the default component typographies but still want the hierarchy styles,`, - `// you can delete this line and instead use:`, - `// \`@include mat.legacy-typography-hierarchy(mat.define-legacy-typography-config(()));\``, - `@include mat.all-legacy-component-typographies(mat.define-legacy-typography-config(()));`, - `@include mat.legacy-core();`, - `@include mat.legacy-core-theme(())`, - ], - }); - }); - }); -});