From 5e108336f883824fd8cc534e68c63569db7cde07 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 21 Oct 2019 18:12:54 +0200 Subject: [PATCH] fix(schematics): secondary entry point migration not working against v9 (#17452) Currently the secondary entry point migration uses the type checker to figure out which Material module a symbol belongs to. This won't work in v9, because we removed the top-level `@angular/material`. These changes fall back to looking up the name of the symbol in a list of known symbols and their module names. Fixes #17433. --- scripts/generate-schematic-imports-map.ts | 53 ++ .../{misc => v8}/material-imports.spec.ts | 2 +- .../material-imports_expected_output.ts | 0 .../{misc => v8}/material-imports_input.ts | 0 .../test-cases/v9/material-imports.spec.ts | 19 + .../v9/material-imports_expected_output.ts | 14 + .../test-cases/v9/material-imports_input.ts | 14 + .../package-imports-v8/material-symbols.json | 481 ++++++++++++++++++ .../secondary-entry-points-rule.ts | 90 ++-- 9 files changed, 634 insertions(+), 39 deletions(-) create mode 100644 scripts/generate-schematic-imports-map.ts rename src/material/schematics/ng-update/test-cases/{misc => v8}/material-imports.spec.ts (93%) rename src/material/schematics/ng-update/test-cases/{misc => v8}/material-imports_expected_output.ts (100%) rename src/material/schematics/ng-update/test-cases/{misc => v8}/material-imports_input.ts (100%) create mode 100644 src/material/schematics/ng-update/test-cases/v9/material-imports.spec.ts create mode 100644 src/material/schematics/ng-update/test-cases/v9/material-imports_expected_output.ts create mode 100644 src/material/schematics/ng-update/test-cases/v9/material-imports_input.ts create mode 100644 src/material/schematics/ng-update/upgrade-rules/package-imports-v8/material-symbols.json diff --git a/scripts/generate-schematic-imports-map.ts b/scripts/generate-schematic-imports-map.ts new file mode 100644 index 000000000000..0eeba7695473 --- /dev/null +++ b/scripts/generate-schematic-imports-map.ts @@ -0,0 +1,53 @@ +import {sync as glob} from 'glob'; +import {readFileSync, writeFileSync} from 'fs'; +import {join, basename} from 'path'; +import * as ts from 'typescript'; + +// Script that generates mappings from our publicly-exported symbols to their entry points. The +// mappings are intended to be used by the secondary entry points schematic and should be committed +// next to the relevant schematic file. +// Can be run using `ts-node --project scripts scripts/generate-schematic-imports-map.ts`. +const mappings: {[symbolName: string]: string} = {}; +const outputPath = join(__dirname, '../temp-entry-points-mapping.json'); + +glob('**/*.d.ts', { + absolute: true, + cwd: join(__dirname, '../tools/public_api_guard/material') +}).forEach(fileName => { + const content = readFileSync(fileName, 'utf8'); + const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.ES5); + const moduleName = basename(fileName, '.d.ts'); + + // We only care about the top-level symbols. + sourceFile.forEachChild((node: ts.Node & {name?: ts.Identifier}) => { + // Most of the exports are named nodes (e.g. classes, types, interfaces) so we can use the + // `name` property to extract the name. The one exception are variable declarations for + // which we need to loop through the list of declarations. + if (node.name) { + addMapping(moduleName, node.name.text); + } else if (ts.isVariableStatement(node)) { + node.declarationList.declarations.forEach(declaration => { + if (ts.isIdentifier(declaration.name)) { + addMapping(moduleName, declaration.name.text); + } else { + throw Error('Unsupported variable kind.'); + } + }); + } else if (node.kind !== ts.SyntaxKind.EndOfFileToken) { + throw Error(`Unhandled node kind ${node.kind} in ${fileName}.`); + } + }); +}); + +/** Adds a symbol to the mappings. */ +function addMapping(moduleName: string, symbolName: string) { + if (mappings[symbolName] && mappings[symbolName] !== moduleName) { + throw Error(`Duplicate symbol name ${symbolName}.`); + } + + mappings[symbolName] = moduleName; +} + +writeFileSync(outputPath, JSON.stringify(mappings, null, 2)); +console.log(`Generated mappings to ${outputPath}. You should move the file to the ` + + `proper place yourself.`); diff --git a/src/material/schematics/ng-update/test-cases/misc/material-imports.spec.ts b/src/material/schematics/ng-update/test-cases/v8/material-imports.spec.ts similarity index 93% rename from src/material/schematics/ng-update/test-cases/misc/material-imports.spec.ts rename to src/material/schematics/ng-update/test-cases/v8/material-imports.spec.ts index fa1ae3d14c24..2452cba0c8f4 100644 --- a/src/material/schematics/ng-update/test-cases/misc/material-imports.spec.ts +++ b/src/material/schematics/ng-update/test-cases/v8/material-imports.spec.ts @@ -2,7 +2,7 @@ import {createTestCaseSetup, readFileContent} from '@angular/cdk/schematics/test import {migrationCollection} from '../index.spec'; describe('v8 material imports', () => { - it('should report imports for deleted animation constants', async () => { + it('should re-map top-level material imports to the proper entry points', async () => { const {runFixers, appTree, writeFile, removeTempDir} = await createTestCaseSetup( 'migration-v8', migrationCollection, [require.resolve('./material-imports_input.ts')]); const materialPath = '/node_modules/@angular/material'; diff --git a/src/material/schematics/ng-update/test-cases/misc/material-imports_expected_output.ts b/src/material/schematics/ng-update/test-cases/v8/material-imports_expected_output.ts similarity index 100% rename from src/material/schematics/ng-update/test-cases/misc/material-imports_expected_output.ts rename to src/material/schematics/ng-update/test-cases/v8/material-imports_expected_output.ts diff --git a/src/material/schematics/ng-update/test-cases/misc/material-imports_input.ts b/src/material/schematics/ng-update/test-cases/v8/material-imports_input.ts similarity index 100% rename from src/material/schematics/ng-update/test-cases/misc/material-imports_input.ts rename to src/material/schematics/ng-update/test-cases/v8/material-imports_input.ts diff --git a/src/material/schematics/ng-update/test-cases/v9/material-imports.spec.ts b/src/material/schematics/ng-update/test-cases/v9/material-imports.spec.ts new file mode 100644 index 000000000000..4434f4db561c --- /dev/null +++ b/src/material/schematics/ng-update/test-cases/v9/material-imports.spec.ts @@ -0,0 +1,19 @@ +import {createTestCaseSetup, readFileContent} from '@angular/cdk/schematics/testing'; +import {migrationCollection} from '../index.spec'; + +describe('v9 material imports', () => { + it('should re-map top-level material imports to the proper entry points when top-level ' + + '@angular/material package does not exist', async () => { + const {runFixers, appTree, removeTempDir} = await createTestCaseSetup( + 'migration-v9', migrationCollection, [require.resolve('./material-imports_input.ts')]); + + // Note: don't create a fake @angular/material package here, because + // we're testing what would happen if it doesn't exist anymore. + await runFixers(); + + expect(appTree.readContent('/projects/cdk-testing/src/test-cases/material-imports_input.ts')) + .toBe(readFileContent(require.resolve('./material-imports_expected_output.ts'))); + + removeTempDir(); + }); +}); diff --git a/src/material/schematics/ng-update/test-cases/v9/material-imports_expected_output.ts b/src/material/schematics/ng-update/test-cases/v9/material-imports_expected_output.ts new file mode 100644 index 000000000000..12d52e0641d4 --- /dev/null +++ b/src/material/schematics/ng-update/test-cases/v9/material-imports_expected_output.ts @@ -0,0 +1,14 @@ +import { MatDialogContainer as DialogContainer } from '@angular/material/dialog'; +import { MatAccordion, MatExpansionPanel, MatExpansionPanelHeader } from '@angular/material/expansion'; +import { MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS as SPINNER_DEFAULTS } from '@angular/material/progress-spinner'; + +// unsorted +import { MatTreeNodeToggle, MatTreeNodeDef, MatTree } from '@angular/material/tree'; + +import { /* comment */ MatTooltip as Tooltip } from '@angular/material/tooltip'; + +// primary entry-point export +import { VERSION } from '@angular/material/core'; + +// type import +import { MatMenuPanel } from "@angular/material/menu"; diff --git a/src/material/schematics/ng-update/test-cases/v9/material-imports_input.ts b/src/material/schematics/ng-update/test-cases/v9/material-imports_input.ts new file mode 100644 index 000000000000..3eb44cf6f94b --- /dev/null +++ b/src/material/schematics/ng-update/test-cases/v9/material-imports_input.ts @@ -0,0 +1,14 @@ +import {MatDialogContainer as DialogContainer} from '@angular/material'; +import {MatAccordion, MatExpansionPanel, MatExpansionPanelHeader} from '@angular/material'; +import {MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS as SPINNER_DEFAULTS} from '@angular/material'; + +// unsorted +import {MatTreeNodeToggle, MatTreeNodeDef, MatTree} from '@angular/material'; + +import {/* comment */ MatTooltip as Tooltip} from '@angular/material'; + +// primary entry-point export +import {VERSION} from '@angular/material'; + +// type import +import {MatMenuPanel} from "@angular/material"; diff --git a/src/material/schematics/ng-update/upgrade-rules/package-imports-v8/material-symbols.json b/src/material/schematics/ng-update/upgrade-rules/package-imports-v8/material-symbols.json new file mode 100644 index 000000000000..69942583bfd0 --- /dev/null +++ b/src/material/schematics/ng-update/upgrade-rules/package-imports-v8/material-symbols.json @@ -0,0 +1,481 @@ +{ + "AUTOCOMPLETE_OPTION_HEIGHT": "autocomplete", + "AUTOCOMPLETE_PANEL_HEIGHT": "autocomplete", + "getMatAutocompleteMissingPanelError": "autocomplete", + "MAT_AUTOCOMPLETE_DEFAULT_OPTIONS": "autocomplete", + "MAT_AUTOCOMPLETE_DEFAULT_OPTIONS_FACTORY": "autocomplete", + "MAT_AUTOCOMPLETE_SCROLL_STRATEGY": "autocomplete", + "MAT_AUTOCOMPLETE_SCROLL_STRATEGY_FACTORY": "autocomplete", + "MAT_AUTOCOMPLETE_SCROLL_STRATEGY_FACTORY_PROVIDER": "autocomplete", + "MAT_AUTOCOMPLETE_VALUE_ACCESSOR": "autocomplete", + "MatAutocomplete": "autocomplete", + "MatAutocompleteDefaultOptions": "autocomplete", + "MatAutocompleteModule": "autocomplete", + "MatAutocompleteOrigin": "autocomplete", + "MatAutocompleteSelectedEvent": "autocomplete", + "MatAutocompleteTrigger": "autocomplete", + "MatBadge": "badge", + "MatBadgeModule": "badge", + "MatBadgePosition": "badge", + "MatBadgeSize": "badge", + "MAT_BOTTOM_SHEET_DATA": "bottom-sheet", + "MAT_BOTTOM_SHEET_DEFAULT_OPTIONS": "bottom-sheet", + "MatBottomSheet": "bottom-sheet", + "matBottomSheetAnimations": "bottom-sheet", + "MatBottomSheetConfig": "bottom-sheet", + "MatBottomSheetContainer": "bottom-sheet", + "MatBottomSheetModule": "bottom-sheet", + "MatBottomSheetRef": "bottom-sheet", + "MAT_BUTTON_TOGGLE_DEFAULT_OPTIONS": "button-toggle", + "MAT_BUTTON_TOGGLE_GROUP_VALUE_ACCESSOR": "button-toggle", + "MatButtonToggle": "button-toggle", + "MatButtonToggleAppearance": "button-toggle", + "MatButtonToggleChange": "button-toggle", + "MatButtonToggleDefaultOptions": "button-toggle", + "MatButtonToggleGroup": "button-toggle", + "MatButtonToggleGroupMultiple": "button-toggle", + "MatButtonToggleModule": "button-toggle", + "ToggleType": "button-toggle", + "MatAnchor": "button", + "MatButton": "button", + "MatButtonModule": "button", + "MatCard": "card", + "MatCardActions": "card", + "MatCardAvatar": "card", + "MatCardContent": "card", + "MatCardFooter": "card", + "MatCardHeader": "card", + "MatCardImage": "card", + "MatCardLgImage": "card", + "MatCardMdImage": "card", + "MatCardModule": "card", + "MatCardSmImage": "card", + "MatCardSubtitle": "card", + "MatCardTitle": "card", + "MatCardTitleGroup": "card", + "MatCardXlImage": "card", + "_MatCheckboxRequiredValidatorModule": "checkbox", + "MAT_CHECKBOX_CLICK_ACTION": "checkbox", + "MAT_CHECKBOX_CONTROL_VALUE_ACCESSOR": "checkbox", + "MAT_CHECKBOX_REQUIRED_VALIDATOR": "checkbox", + "MatCheckbox": "checkbox", + "MatCheckboxChange": "checkbox", + "MatCheckboxClickAction": "checkbox", + "MatCheckboxModule": "checkbox", + "MatCheckboxRequiredValidator": "checkbox", + "TransitionCheckState": "checkbox", + "MAT_CHIPS_DEFAULT_OPTIONS": "chips", + "MatChip": "chips", + "MatChipAvatar": "chips", + "MatChipEvent": "chips", + "MatChipInput": "chips", + "MatChipInputEvent": "chips", + "MatChipList": "chips", + "MatChipListChange": "chips", + "MatChipRemove": "chips", + "MatChipsDefaultOptions": "chips", + "MatChipSelectionChange": "chips", + "MatChipsModule": "chips", + "MatChipTrailingIcon": "chips", + "_countGroupLabelsBeforeOption": "core", + "_getOptionScrollPosition": "core", + "AnimationCurves": "core", + "AnimationDurations": "core", + "JAN": "core", + "FEB": "core", + "MAR": "core", + "APR": "core", + "MAY": "core", + "JUN": "core", + "JUL": "core", + "AUG": "core", + "SEP": "core", + "OCT": "core", + "NOV": "core", + "DEC": "core", + "CanColor": "core", + "CanColorCtor": "core", + "CanDisable": "core", + "CanDisableCtor": "core", + "CanDisableRipple": "core", + "CanDisableRippleCtor": "core", + "CanUpdateErrorState": "core", + "CanUpdateErrorStateCtor": "core", + "DateAdapter": "core", + "defaultRippleAnimationConfig": "core", + "ErrorStateMatcher": "core", + "FloatLabelType": "core", + "GestureConfig": "core", + "HammerInput": "core", + "HammerInstance": "core", + "HammerManager": "core", + "HammerOptions": "core", + "HammerStatic": "core", + "HasInitialized": "core", + "HasInitializedCtor": "core", + "HasTabIndex": "core", + "HasTabIndexCtor": "core", + "LabelOptions": "core", + "MAT_DATE_FORMATS": "core", + "MAT_DATE_LOCALE": "core", + "MAT_DATE_LOCALE_FACTORY": "core", + "MAT_DATE_LOCALE_PROVIDER": "core", + "MAT_HAMMER_OPTIONS": "core", + "MAT_LABEL_GLOBAL_OPTIONS": "core", + "MAT_NATIVE_DATE_FORMATS": "core", + "MAT_OPTION_PARENT_COMPONENT": "core", + "MAT_RIPPLE_GLOBAL_OPTIONS": "core", + "MatCommonModule": "core", + "MatDateFormats": "core", + "MATERIAL_SANITY_CHECKS": "core", + "MatLine": "core", + "MatLineModule": "core", + "MatLineSetter": "core", + "MatNativeDateModule": "core", + "MatOptgroup": "core", + "MatOption": "core", + "MatOptionModule": "core", + "MatOptionParentComponent": "core", + "MatOptionSelectionChange": "core", + "MatPseudoCheckbox": "core", + "MatPseudoCheckboxModule": "core", + "MatPseudoCheckboxState": "core", + "MatRipple": "core", + "MatRippleModule": "core", + "mixinColor": "core", + "mixinDisabled": "core", + "mixinDisableRipple": "core", + "mixinErrorState": "core", + "mixinInitialized": "core", + "mixinTabIndex": "core", + "NativeDateAdapter": "core", + "NativeDateModule": "core", + "Recognizer": "core", + "RecognizerStatic": "core", + "RippleAnimationConfig": "core", + "RippleConfig": "core", + "RippleGlobalOptions": "core", + "RippleRef": "core", + "RippleRenderer": "core", + "RippleState": "core", + "RippleTarget": "core", + "setLines": "core", + "ShowOnDirtyErrorStateMatcher": "core", + "ThemePalette": "core", + "VERSION": "core", + "MAT_DATEPICKER_SCROLL_STRATEGY": "datepicker", + "MAT_DATEPICKER_SCROLL_STRATEGY_FACTORY": "datepicker", + "MAT_DATEPICKER_SCROLL_STRATEGY_FACTORY_PROVIDER": "datepicker", + "MAT_DATEPICKER_VALIDATORS": "datepicker", + "MAT_DATEPICKER_VALUE_ACCESSOR": "datepicker", + "MatCalendar": "datepicker", + "MatCalendarBody": "datepicker", + "MatCalendarCell": "datepicker", + "MatCalendarCellCssClasses": "datepicker", + "MatCalendarHeader": "datepicker", + "MatCalendarView": "datepicker", + "MatDatepicker": "datepicker", + "matDatepickerAnimations": "datepicker", + "MatDatepickerContent": "datepicker", + "MatDatepickerInput": "datepicker", + "MatDatepickerInputEvent": "datepicker", + "MatDatepickerIntl": "datepicker", + "MatDatepickerModule": "datepicker", + "MatDatepickerToggle": "datepicker", + "MatDatepickerToggleIcon": "datepicker", + "MatMonthView": "datepicker", + "MatMultiYearView": "datepicker", + "MatYearView": "datepicker", + "yearsPerPage": "datepicker", + "yearsPerRow": "datepicker", + "DialogPosition": "dialog", + "DialogRole": "dialog", + "MAT_DIALOG_DATA": "dialog", + "MAT_DIALOG_DEFAULT_OPTIONS": "dialog", + "MAT_DIALOG_SCROLL_STRATEGY": "dialog", + "MAT_DIALOG_SCROLL_STRATEGY_FACTORY": "dialog", + "MAT_DIALOG_SCROLL_STRATEGY_PROVIDER": "dialog", + "MAT_DIALOG_SCROLL_STRATEGY_PROVIDER_FACTORY": "dialog", + "MatDialog": "dialog", + "MatDialogActions": "dialog", + "matDialogAnimations": "dialog", + "MatDialogClose": "dialog", + "MatDialogConfig": "dialog", + "MatDialogContainer": "dialog", + "MatDialogContent": "dialog", + "MatDialogModule": "dialog", + "MatDialogRef": "dialog", + "MatDialogState": "dialog", + "MatDialogTitle": "dialog", + "throwMatDialogContentAlreadyAttachedError": "dialog", + "MatDivider": "divider", + "MatDividerModule": "divider", + "EXPANSION_PANEL_ANIMATION_TIMING": "expansion", + "MAT_ACCORDION": "expansion", + "MAT_EXPANSION_PANEL_DEFAULT_OPTIONS": "expansion", + "MatAccordion": "expansion", + "MatAccordionBase": "expansion", + "MatAccordionDisplayMode": "expansion", + "MatAccordionTogglePosition": "expansion", + "matExpansionAnimations": "expansion", + "MatExpansionModule": "expansion", + "MatExpansionPanel": "expansion", + "MatExpansionPanelActionRow": "expansion", + "MatExpansionPanelContent": "expansion", + "MatExpansionPanelDefaultOptions": "expansion", + "MatExpansionPanelDescription": "expansion", + "MatExpansionPanelHeader": "expansion", + "MatExpansionPanelState": "expansion", + "MatExpansionPanelTitle": "expansion", + "getMatFormFieldDuplicatedHintError": "form-field", + "getMatFormFieldMissingControlError": "form-field", + "getMatFormFieldPlaceholderConflictError": "form-field", + "MAT_FORM_FIELD_DEFAULT_OPTIONS": "form-field", + "MatError": "form-field", + "MatFormField": "form-field", + "matFormFieldAnimations": "form-field", + "MatFormFieldAppearance": "form-field", + "MatFormFieldControl": "form-field", + "MatFormFieldDefaultOptions": "form-field", + "MatFormFieldModule": "form-field", + "MatHint": "form-field", + "MatLabel": "form-field", + "MatPlaceholder": "form-field", + "MatPrefix": "form-field", + "MatSuffix": "form-field", + "MatGridAvatarCssMatStyler": "grid-list", + "MatGridList": "grid-list", + "MatGridListModule": "grid-list", + "MatGridTile": "grid-list", + "MatGridTileFooterCssMatStyler": "grid-list", + "MatGridTileHeaderCssMatStyler": "grid-list", + "MatGridTileText": "grid-list", + "getMatIconFailedToSanitizeLiteralError": "icon", + "getMatIconFailedToSanitizeUrlError": "icon", + "getMatIconNameNotFoundError": "icon", + "getMatIconNoHttpProviderError": "icon", + "ICON_REGISTRY_PROVIDER": "icon", + "ICON_REGISTRY_PROVIDER_FACTORY": "icon", + "IconOptions": "icon", + "MAT_ICON_LOCATION": "icon", + "MAT_ICON_LOCATION_FACTORY": "icon", + "MatIcon": "icon", + "MatIconLocation": "icon", + "MatIconModule": "icon", + "MatIconRegistry": "icon", + "getMatInputUnsupportedTypeError": "input", + "MAT_INPUT_VALUE_ACCESSOR": "input", + "MatInput": "input", + "MatInputModule": "input", + "MatTextareaAutosize": "input", + "MAT_SELECTION_LIST_VALUE_ACCESSOR": "list", + "MatList": "list", + "MatListAvatarCssMatStyler": "list", + "MatListIconCssMatStyler": "list", + "MatListItem": "list", + "MatListModule": "list", + "MatListOption": "list", + "MatListSubheaderCssMatStyler": "list", + "MatNavList": "list", + "MatSelectionList": "list", + "MatSelectionListChange": "list", + "_MatMenu": "menu", + "_MatMenuBase": "menu", + "_MatMenuDirectivesModule": "menu", + "fadeInItems": "menu", + "MAT_MENU_DEFAULT_OPTIONS": "menu", + "MAT_MENU_PANEL": "menu", + "MAT_MENU_SCROLL_STRATEGY": "menu", + "MatMenu": "menu", + "matMenuAnimations": "menu", + "MatMenuContent": "menu", + "MatMenuDefaultOptions": "menu", + "MatMenuItem": "menu", + "MatMenuModule": "menu", + "MatMenuPanel": "menu", + "MatMenuTrigger": "menu", + "MenuPositionX": "menu", + "MenuPositionY": "menu", + "transformMenu": "menu", + "MAT_PAGINATOR_INTL_PROVIDER": "paginator", + "MAT_PAGINATOR_INTL_PROVIDER_FACTORY": "paginator", + "MatPaginator": "paginator", + "MatPaginatorIntl": "paginator", + "MatPaginatorModule": "paginator", + "PageEvent": "paginator", + "MAT_PROGRESS_BAR_LOCATION": "progress-bar", + "MAT_PROGRESS_BAR_LOCATION_FACTORY": "progress-bar", + "MatProgressBar": "progress-bar", + "MatProgressBarLocation": "progress-bar", + "MatProgressBarModule": "progress-bar", + "ProgressAnimationEnd": "progress-bar", + "MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS": "progress-spinner", + "MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS_FACTORY": "progress-spinner", + "MatProgressSpinner": "progress-spinner", + "MatProgressSpinnerDefaultOptions": "progress-spinner", + "MatSpinner": "progress-spinner", + "ProgressSpinnerMode": "progress-spinner", + "MAT_RADIO_DEFAULT_OPTIONS": "radio", + "MAT_RADIO_DEFAULT_OPTIONS_FACTORY": "radio", + "MAT_RADIO_GROUP_CONTROL_VALUE_ACCESSOR": "radio", + "MatRadioButton": "radio", + "MatRadioChange": "radio", + "MatRadioDefaultOptions": "radio", + "MatRadioGroup": "radio", + "MatRadioModule": "radio", + "fadeInContent": "select", + "MAT_SELECT_SCROLL_STRATEGY": "select", + "MAT_SELECT_SCROLL_STRATEGY_PROVIDER": "select", + "MAT_SELECT_SCROLL_STRATEGY_PROVIDER_FACTORY": "select", + "MatSelect": "select", + "matSelectAnimations": "select", + "MatSelectChange": "select", + "MatSelectModule": "select", + "MatSelectTrigger": "select", + "SELECT_ITEM_HEIGHT_EM": "select", + "SELECT_MULTIPLE_PANEL_PADDING_X": "select", + "SELECT_PANEL_INDENT_PADDING_X": "select", + "SELECT_PANEL_MAX_HEIGHT": "select", + "SELECT_PANEL_PADDING_X": "select", + "SELECT_PANEL_VIEWPORT_PADDING": "select", + "transformPanel": "select", + "MAT_DRAWER_DEFAULT_AUTOSIZE": "sidenav", + "MAT_DRAWER_DEFAULT_AUTOSIZE_FACTORY": "sidenav", + "MatDrawer": "sidenav", + "matDrawerAnimations": "sidenav", + "MatDrawerContainer": "sidenav", + "MatDrawerContent": "sidenav", + "MatDrawerToggleResult": "sidenav", + "MatSidenav": "sidenav", + "MatSidenavContainer": "sidenav", + "MatSidenavContent": "sidenav", + "MatSidenavModule": "sidenav", + "throwMatDuplicatedDrawerError": "sidenav", + "_MatSlideToggleRequiredValidatorModule": "slide-toggle", + "MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS": "slide-toggle", + "MAT_SLIDE_TOGGLE_REQUIRED_VALIDATOR": "slide-toggle", + "MAT_SLIDE_TOGGLE_VALUE_ACCESSOR": "slide-toggle", + "MatSlideToggle": "slide-toggle", + "MatSlideToggleChange": "slide-toggle", + "MatSlideToggleDefaultOptions": "slide-toggle", + "MatSlideToggleModule": "slide-toggle", + "MatSlideToggleRequiredValidator": "slide-toggle", + "MAT_SLIDER_VALUE_ACCESSOR": "slider", + "MatSlider": "slider", + "MatSliderChange": "slider", + "MatSliderModule": "slider", + "MAT_SNACK_BAR_DATA": "snack-bar", + "MAT_SNACK_BAR_DEFAULT_OPTIONS": "snack-bar", + "MAT_SNACK_BAR_DEFAULT_OPTIONS_FACTORY": "snack-bar", + "MatSnackBar": "snack-bar", + "matSnackBarAnimations": "snack-bar", + "MatSnackBarConfig": "snack-bar", + "MatSnackBarContainer": "snack-bar", + "MatSnackBarDismiss": "snack-bar", + "MatSnackBarHorizontalPosition": "snack-bar", + "MatSnackBarModule": "snack-bar", + "MatSnackBarRef": "snack-bar", + "MatSnackBarVerticalPosition": "snack-bar", + "SimpleSnackBar": "snack-bar", + "ArrowViewState": "sort", + "ArrowViewStateTransition": "sort", + "MAT_SORT_HEADER_INTL_PROVIDER": "sort", + "MAT_SORT_HEADER_INTL_PROVIDER_FACTORY": "sort", + "MatSort": "sort", + "MatSortable": "sort", + "matSortAnimations": "sort", + "MatSortHeader": "sort", + "MatSortHeaderIntl": "sort", + "MatSortModule": "sort", + "Sort": "sort", + "SortDirection": "sort", + "MAT_STEPPER_INTL_PROVIDER": "stepper", + "MAT_STEPPER_INTL_PROVIDER_FACTORY": "stepper", + "MatHorizontalStepper": "stepper", + "MatStep": "stepper", + "MatStepHeader": "stepper", + "MatStepLabel": "stepper", + "MatStepper": "stepper", + "matStepperAnimations": "stepper", + "MatStepperIcon": "stepper", + "MatStepperIconContext": "stepper", + "MatStepperIntl": "stepper", + "MatStepperModule": "stepper", + "MatStepperNext": "stepper", + "MatStepperPrevious": "stepper", + "MatVerticalStepper": "stepper", + "MatCell": "table", + "MatCellDef": "table", + "MatColumnDef": "table", + "MatFooterCell": "table", + "MatFooterCellDef": "table", + "MatFooterRow": "table", + "MatFooterRowDef": "table", + "MatHeaderCell": "table", + "MatHeaderCellDef": "table", + "MatHeaderRow": "table", + "MatHeaderRowDef": "table", + "MatRow": "table", + "MatRowDef": "table", + "MatTable": "table", + "MatTableDataSource": "table", + "MatTableModule": "table", + "MatTextColumn": "table", + "_MAT_INK_BAR_POSITIONER": "tabs", + "_MatInkBarPositioner": "tabs", + "_MatTabBodyBase": "tabs", + "_MatTabGroupBase": "tabs", + "_MatTabHeaderBase": "tabs", + "_MatTabLinkBase": "tabs", + "_MatTabNavBase": "tabs", + "MAT_TABS_CONFIG": "tabs", + "MatInkBar": "tabs", + "MatTab": "tabs", + "MatTabBody": "tabs", + "MatTabBodyOriginState": "tabs", + "MatTabBodyPortal": "tabs", + "MatTabBodyPositionState": "tabs", + "MatTabChangeEvent": "tabs", + "MatTabContent": "tabs", + "MatTabGroup": "tabs", + "MatTabHeader": "tabs", + "MatTabHeaderPosition": "tabs", + "MatTabLabel": "tabs", + "MatTabLabelWrapper": "tabs", + "MatTabLink": "tabs", + "MatTabNav": "tabs", + "matTabsAnimations": "tabs", + "MatTabsConfig": "tabs", + "MatTabsModule": "tabs", + "ScrollDirection": "tabs", + "MatToolbar": "toolbar", + "MatToolbarModule": "toolbar", + "MatToolbarRow": "toolbar", + "throwToolbarMixedModesError": "toolbar", + "getMatTooltipInvalidPositionError": "tooltip", + "MAT_TOOLTIP_DEFAULT_OPTIONS": "tooltip", + "MAT_TOOLTIP_DEFAULT_OPTIONS_FACTORY": "tooltip", + "MAT_TOOLTIP_SCROLL_STRATEGY": "tooltip", + "MAT_TOOLTIP_SCROLL_STRATEGY_FACTORY": "tooltip", + "MAT_TOOLTIP_SCROLL_STRATEGY_FACTORY_PROVIDER": "tooltip", + "MatTooltip": "tooltip", + "matTooltipAnimations": "tooltip", + "MatTooltipDefaultOptions": "tooltip", + "MatTooltipModule": "tooltip", + "SCROLL_THROTTLE_MS": "tooltip", + "TOOLTIP_PANEL_CLASS": "tooltip", + "TooltipComponent": "tooltip", + "TooltipPosition": "tooltip", + "TooltipVisibility": "tooltip", + "MatNestedTreeNode": "tree", + "MatTree": "tree", + "MatTreeFlatDataSource": "tree", + "MatTreeFlattener": "tree", + "MatTreeModule": "tree", + "MatTreeNestedDataSource": "tree", + "MatTreeNode": "tree", + "MatTreeNodeDef": "tree", + "MatTreeNodeOutlet": "tree", + "MatTreeNodePadding": "tree", + "MatTreeNodeToggle": "tree" +} diff --git a/src/material/schematics/ng-update/upgrade-rules/package-imports-v8/secondary-entry-points-rule.ts b/src/material/schematics/ng-update/upgrade-rules/package-imports-v8/secondary-entry-points-rule.ts index cfa4c1092ac8..e9f91fe9100d 100644 --- a/src/material/schematics/ng-update/upgrade-rules/package-imports-v8/secondary-entry-points-rule.ts +++ b/src/material/schematics/ng-update/upgrade-rules/package-imports-v8/secondary-entry-points-rule.ts @@ -22,6 +22,12 @@ const NO_IMPORT_NAMED_SYMBOLS_FAILURE_STR = `Imports from Angular Material shoul */ const ANGULAR_MATERIAL_FILEPATH_REGEX = new RegExp(`${materialModuleSpecifier}/(.*?)/`); +/** + * Mapping of Material symbol names to their module names. Used as a fallback if + * we didn't manage to resolve the module name of a symbol using the type checker. + */ +const ENTRY_POINT_MAPPINGS: {[name: string]: string} = require('./material-symbols.json'); + /** * 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). @@ -31,7 +37,7 @@ export class SecondaryEntryPointsRule extends MigrationRule { // Only enable this rule if the migration targets version 8. The primary // entry-point of Material has been marked as deprecated in version 8. - ruleEnabled = this.targetVersion === TargetVersion.V8; + ruleEnabled = this.targetVersion === TargetVersion.V8 || this.targetVersion === TargetVersion.V9; visitNode(declaration: ts.Node): void { // Only look at import declarations. @@ -41,7 +47,7 @@ export class SecondaryEntryPointsRule extends MigrationRule { } const importLocation = declaration.moduleSpecifier.text; - // If the import module is not @angular/material, skip check. + // If the import module is not @angular/material, skip the check. if (importLocation !== materialModuleSpecifier) { return; } @@ -78,47 +84,26 @@ export class SecondaryEntryPointsRule extends MigrationRule { 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); + // 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 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))) { + if (!moduleName) { this.createFailureAtNode( - element, `"${element.getText()}" was not found in the Material library.`); + 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: ts.Node = symbol.valueDeclaration || symbol.declarations[0]; - const sourceFile: string = 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]); - } + // 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 @@ -179,3 +164,32 @@ function getDeclarationSymbolOfNode(node: ts.Node, checker: ts.TypeChecker): ts. } return symbol; } + + +/** Tries to resolve the name of the Material module that a node is imported from. */ +function resolveModuleName(node: ts.Identifier, typeChecker: ts.TypeChecker) { + // 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(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))) { + 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 || 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); + return matches ? matches[1] : null; +}