From 8b2530a34826b7ad1cdf66e45d81997eb58b7923 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 12 Oct 2021 15:50:58 +0200 Subject: [PATCH] fix(cdk/schematics): drop tilde imports when updating to v13 Tilde imports have been deprecated in the Sass loader for a long time and they won't work with the new package format in v13. These changes add a migration that will drop the tilde. --- src/cdk/schematics/ng-update/index.ts | 31 +++-- .../tilde-import-migration.ts | 36 +++++ .../v13/misc/tilde-import-v13.spec.ts | 128 ++++++++++++++++++ 3 files changed, 186 insertions(+), 9 deletions(-) create mode 100644 src/cdk/schematics/ng-update/migrations/tilde-import-v13/tilde-import-migration.ts create mode 100644 src/cdk/schematics/ng-update/test-cases/v13/misc/tilde-import-v13.spec.ts diff --git a/src/cdk/schematics/ng-update/index.ts b/src/cdk/schematics/ng-update/index.ts index 5f935d8dd2cc..5e7b2f0f7841 100644 --- a/src/cdk/schematics/ng-update/index.ts +++ b/src/cdk/schematics/ng-update/index.ts @@ -9,46 +9,59 @@ import {Rule, SchematicContext} from '@angular-devkit/schematics'; import {TargetVersion} from '../update-tool/target-version'; import {cdkUpgradeData} from './upgrade-data'; -import {createMigrationSchematicRule} from './devkit-migration-rule'; +import {createMigrationSchematicRule, NullableDevkitMigration} from './devkit-migration-rule'; +import {TildeImportMigration} from './migrations/tilde-import-v13/tilde-import-migration'; + +const cdkMigrations: NullableDevkitMigration[] = [ + TildeImportMigration, +]; /** Entry point for the migration schematics with target of Angular CDK 6.0.0 */ export function updateToV6(): Rule { - return createMigrationSchematicRule(TargetVersion.V6, [], cdkUpgradeData, onMigrationComplete); + return createMigrationSchematicRule( + TargetVersion.V6, cdkMigrations, cdkUpgradeData, onMigrationComplete); } /** Entry point for the migration schematics with target of Angular CDK 7.0.0 */ export function updateToV7(): Rule { - return createMigrationSchematicRule(TargetVersion.V7, [], cdkUpgradeData, onMigrationComplete); + return createMigrationSchematicRule( + TargetVersion.V7, cdkMigrations, cdkUpgradeData, onMigrationComplete); } /** Entry point for the migration schematics with target of Angular CDK 8.0.0 */ export function updateToV8(): Rule { - return createMigrationSchematicRule(TargetVersion.V8, [], cdkUpgradeData, onMigrationComplete); + return createMigrationSchematicRule( + TargetVersion.V8, cdkMigrations, cdkUpgradeData, onMigrationComplete); } /** Entry point for the migration schematics with target of Angular CDK 9.0.0 */ export function updateToV9(): Rule { - return createMigrationSchematicRule(TargetVersion.V9, [], cdkUpgradeData, onMigrationComplete); + return createMigrationSchematicRule( + TargetVersion.V9, cdkMigrations, cdkUpgradeData, onMigrationComplete); } /** Entry point for the migration schematics with target of Angular CDK 10.0.0 */ export function updateToV10(): Rule { - return createMigrationSchematicRule(TargetVersion.V10, [], cdkUpgradeData, onMigrationComplete); + return createMigrationSchematicRule( + TargetVersion.V10, cdkMigrations, cdkUpgradeData, onMigrationComplete); } /** Entry point for the migration schematics with target of Angular CDK 11.0.0 */ export function updateToV11(): Rule { - return createMigrationSchematicRule(TargetVersion.V11, [], cdkUpgradeData, onMigrationComplete); + return createMigrationSchematicRule( + TargetVersion.V11, cdkMigrations, cdkUpgradeData, onMigrationComplete); } /** Entry point for the migration schematics with target of Angular CDK 12.0.0 */ export function updateToV12(): Rule { - return createMigrationSchematicRule(TargetVersion.V12, [], cdkUpgradeData, onMigrationComplete); + return createMigrationSchematicRule( + TargetVersion.V12, cdkMigrations, cdkUpgradeData, onMigrationComplete); } /** Entry point for the migration schematics with target of Angular CDK 13.0.0 */ export function updateToV13(): Rule { - return createMigrationSchematicRule(TargetVersion.V13, [], cdkUpgradeData, onMigrationComplete); + return createMigrationSchematicRule( + TargetVersion.V13, cdkMigrations, cdkUpgradeData, onMigrationComplete); } /** Function that will be called when the migration completed. */ diff --git a/src/cdk/schematics/ng-update/migrations/tilde-import-v13/tilde-import-migration.ts b/src/cdk/schematics/ng-update/migrations/tilde-import-v13/tilde-import-migration.ts new file mode 100644 index 000000000000..54f5bc1403f4 --- /dev/null +++ b/src/cdk/schematics/ng-update/migrations/tilde-import-v13/tilde-import-migration.ts @@ -0,0 +1,36 @@ +/** + * @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 {extname} from '@angular-devkit/core'; +import {ResolvedResource} from '../../../update-tool/component-resource-collector'; +import {TargetVersion} from '../../../update-tool/target-version'; +import {DevkitMigration} from '../../devkit-migration'; + +/** Migration that removes tilde symbols from imports. */ +export class TildeImportMigration extends DevkitMigration { + enabled = this.targetVersion === TargetVersion.V13; + + override visitStylesheet(stylesheet: ResolvedResource): void { + const extension = extname(stylesheet.filePath); + + if (extension === '.scss' || extension === '.css') { + const content = stylesheet.content; + const migratedContent = content.replace(/@(?:import|use) +['"]~@angular\/.*['"].*;?/g, + (match) => { + const index = match.indexOf('~@angular'); + return match.slice(0, index) + match.slice(index + 1); + }); + + if (migratedContent && migratedContent !== content) { + this.fileSystem.edit(stylesheet.filePath) + .remove(0, stylesheet.content.length) + .insertLeft(0, migratedContent); + } + } + } +} diff --git a/src/cdk/schematics/ng-update/test-cases/v13/misc/tilde-import-v13.spec.ts b/src/cdk/schematics/ng-update/test-cases/v13/misc/tilde-import-v13.spec.ts new file mode 100644 index 000000000000..536f5fe1e068 --- /dev/null +++ b/src/cdk/schematics/ng-update/test-cases/v13/misc/tilde-import-v13.spec.ts @@ -0,0 +1,128 @@ +import {UnitTestTree} from '@angular-devkit/schematics/testing'; +import {createTestCaseSetup} from '../../../../testing'; +import {join} from 'path'; +import {MIGRATION_PATH} from '../../../../paths'; + +describe('v13 tilde import migration', () => { + const PROJECT_PATH = '/projects/cdk-testing'; + const TEST_PATH = join(PROJECT_PATH, 'src/test.scss'); + let tree: UnitTestTree; + let _writeFile: (filePath: string, text: string) => void; + let runMigration: () => Promise<{logOutput: string}>; + + beforeEach(async () => { + const testSetup = await createTestCaseSetup('migration-v13', MIGRATION_PATH, []); + tree = testSetup.appTree; + runMigration = testSetup.runFixers; + _writeFile = testSetup.writeFile; + }); + + /** Writes an array of lines as a single file. */ + function writeLines(path: string, lines: string[]): void { + _writeFile(path, lines.join('\n')); + } + + /** Reads a file and split it into an array where each item is a new line. */ + function splitFile(path: string): string[] { + return tree.readContent(path).split('\n'); + } + + it('should remove the tilde from angular imports', async () => { + writeLines(TEST_PATH, [ + `@use '~@angular/material' as mat;`, + `@import '~@angular/material/theming';`, + `@import '~@angular/cdk/overlay-prebuilt.css';`, + + `@include mat.button-theme();`, + `@include mat-core();`, + ]); + + await runMigration(); + + expect(splitFile(TEST_PATH)).toEqual([ + `@use '@angular/material' as mat;`, + `@import '@angular/material/theming';`, + `@import '@angular/cdk/overlay-prebuilt.css';`, + + `@include mat.button-theme();`, + `@include mat-core();`, + ]); + }); + + it('should handle an arbitrary amount of whitespace', async () => { + writeLines(TEST_PATH, [ + `@use '~@angular/material' as mat;`, + + `@include mat.core();`, + ]); + + await runMigration(); + + expect(splitFile(TEST_PATH)).toEqual([ + `@use '@angular/material' as mat;`, + + `@include mat.core();`, + ]); + }); + + it('should preserve tilde after the start', async () => { + writeLines(TEST_PATH, [ + `@use '~@angular/~material' as mat;`, + `@import '@angular/cdk/~overlay-prebuilt.css';`, + + `@include mat.core();`, + ]); + + await runMigration(); + + expect(splitFile(TEST_PATH)).toEqual([ + `@use '@angular/~material' as mat;`, + `@import '@angular/cdk/~overlay-prebuilt.css';`, + + `@include mat.core();`, + ]); + }); + + it('should handle different types of quotes', async () => { + writeLines(TEST_PATH, [ + `@use "~@angular/material" as mat;`, + `@import '~@angular/cdk/overlay-prebuilt.css';`, + + `@include mat.button-theme();`, + `@include mat-core();`, + ]); + + await runMigration(); + + expect(splitFile(TEST_PATH)).toEqual([ + `@use "@angular/material" as mat;`, + `@import '@angular/cdk/overlay-prebuilt.css';`, + + `@include mat.button-theme();`, + `@include mat-core();`, + ]); + }); + + it('should preserve the tilde in non-angular imports', async () => { + writeLines(TEST_PATH, [ + `@use '~@angular-momentum/material' as mat;`, + `@import '~@angular-momentum/material/theming';`, + `@import '@angular/cdk/overlay-prebuilt.css';`, + + `@include mat.button-theme();`, + `@include mat-core();`, + ]); + + await runMigration(); + + expect(splitFile(TEST_PATH)).toEqual([ + `@use '~@angular-momentum/material' as mat;`, + `@import '~@angular-momentum/material/theming';`, + `@import '@angular/cdk/overlay-prebuilt.css';`, + + `@include mat.button-theme();`, + `@include mat-core();`, + ]); + }); + +});