From c3c8755653cc881065150403e2f98df9550d4e06 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Wed, 20 Sep 2023 14:54:47 -0700 Subject: [PATCH 01/29] Add updator to rename mui namespace to unify in component usages --- bin/unify_package_rename.dart | 15 ++++ lib/src/executables/unify_package_rename.dart | 81 +++++++++++++++++++ .../constants.dart | 8 ++ .../constants.dart | 16 ++++ ...ckage_rename_component_usage_migrator.dart | 48 +++++++++++ pubspec.yaml | 1 + test/resolved_file_context.dart | 5 ++ test/test_fixtures/rmui_project/README.md | 5 ++ .../rmui_project/lib/analysis_warmup.dart | 10 +++ test/test_fixtures/rmui_project/pubspec.yaml | 10 +++ .../mui_button_migrator_test.dart | 51 ++++++++++++ 11 files changed, 250 insertions(+) create mode 100644 bin/unify_package_rename.dart create mode 100644 lib/src/executables/unify_package_rename.dart create mode 100644 lib/src/unify_package_rename_suggestors/constants.dart create mode 100644 lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart create mode 100644 test/test_fixtures/rmui_project/README.md create mode 100644 test/test_fixtures/rmui_project/lib/analysis_warmup.dart create mode 100644 test/test_fixtures/rmui_project/pubspec.yaml create mode 100644 test/unify_package_rename_suggestors/mui_button_migrator_test.dart diff --git a/bin/unify_package_rename.dart b/bin/unify_package_rename.dart new file mode 100644 index 00000000..1dad86aa --- /dev/null +++ b/bin/unify_package_rename.dart @@ -0,0 +1,15 @@ +// Copyright 2023 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export 'package:over_react_codemod/src/executables/unify_package_rename.dart'; diff --git a/lib/src/executables/unify_package_rename.dart b/lib/src/executables/unify_package_rename.dart new file mode 100644 index 00000000..3108b6a4 --- /dev/null +++ b/lib/src/executables/unify_package_rename.dart @@ -0,0 +1,81 @@ +// Copyright 2023 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:codemod/codemod.dart'; +import 'package:over_react_codemod/src/ignoreable.dart'; +import 'package:over_react_codemod/src/rmui_bundle_update_suggestors/constants.dart'; +import 'package:over_react_codemod/src/rmui_bundle_update_suggestors/dart_script_updater.dart'; +import 'package:over_react_codemod/src/rmui_bundle_update_suggestors/html_script_updater.dart'; +import 'package:over_react_codemod/src/util.dart'; +import 'package:over_react_codemod/src/util/pubspec_upgrader.dart'; + +const _changesRequiredOutput = """ + To update your code, run the following commands in your repository: + dart pub global activate over_react_codemod + dart pub global run over_react_codemod:unify_package_rename +"""; + +void main(List args) async { + final parser = ArgParser.allowAnything(); + + final parsedArgs = parser.parse(args); + + // todo can we also remove rmui dependency here??? + + exitCode = await runInteractiveCodemod( + pubspecYamlPaths(), + aggregate([ + // todo update version: + PubspecUpgrader('unify_ui', parseVersionRange('^1.89.1'), + hostedUrl: 'https://pub.workiva.org', shouldAddDependencies: false), + ].map((s) => ignoreable(s))), + defaultYes: true, + args: parsedArgs.rest, + additionalHelpOutput: parser.usage, + changesRequiredOutput: _changesRequiredOutput, + ); + + if (exitCode != 0) return; + + // Update RMUI bundle script in all HTML files (and templates) to Unify bundle. + exitCode = await runInteractiveCodemodSequence( + allHtmlPathsIncludingTemplates(), + [ + HtmlScriptUpdater(rmuiBundleDevUpdated, unifyBundleDev), + HtmlScriptUpdater(rmuiBundleProdUpdated, unifyBundleProd), + ], + defaultYes: true, + args: parsedArgs.rest, + additionalHelpOutput: parser.usage, + changesRequiredOutput: _changesRequiredOutput, + ); + + if (exitCode != 0) return; + + // Update RMUI bundle script in all Dart files to Unify bundle. + exitCode = await runInteractiveCodemodSequence( + allDartPathsExceptHidden(), + [ + DartScriptUpdater(rmuiBundleDevUpdated, unifyBundleDev), + DartScriptUpdater(rmuiBundleProdUpdated, unifyBundleProd), + ], + defaultYes: true, + args: parsedArgs.rest, + additionalHelpOutput: parser.usage, + changesRequiredOutput: _changesRequiredOutput, + ); +} diff --git a/lib/src/rmui_bundle_update_suggestors/constants.dart b/lib/src/rmui_bundle_update_suggestors/constants.dart index e3b81060..4a208e89 100644 --- a/lib/src/rmui_bundle_update_suggestors/constants.dart +++ b/lib/src/rmui_bundle_update_suggestors/constants.dart @@ -27,6 +27,14 @@ const rmuiBundleDevUpdated = const rmuiBundleProdUpdated = 'packages/react_material_ui/js/react-material-ui.browser.min.esm.js'; +/// The script for the dev Unify bundle. +const unifyBundleDev = + 'packages/unify_ui/js/unify-ui.browser.dev.esm.js'; + +/// The script for the prod Unify bundle. +const unifyBundleProd = + 'packages/unify_ui/js/unify-ui.browser.min.esm.js'; + /// The type attribute that needs to be added to script tags for the new RMUI bundles. final typeModuleAttribute = 'type="module"'; diff --git a/lib/src/unify_package_rename_suggestors/constants.dart b/lib/src/unify_package_rename_suggestors/constants.dart new file mode 100644 index 00000000..a3a52337 --- /dev/null +++ b/lib/src/unify_package_rename_suggestors/constants.dart @@ -0,0 +1,16 @@ +// Copyright 2023 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// The standard namespace to use when importing unify_ui. +const unifyNs = 'unify'; diff --git a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart new file mode 100644 index 00000000..667c70fd --- /dev/null +++ b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart @@ -0,0 +1,48 @@ +// Copyright 2023 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:over_react_codemod/src/unify_package_rename_suggestors/constants.dart'; +import 'package:over_react_codemod/src/util.dart'; +import 'package:over_react_codemod/src/util/component_usage.dart'; +import 'package:over_react_codemod/src/util/component_usage_migrator.dart'; +import 'package:over_react_codemod/src/util/element_type_helpers.dart'; + +class PackageRenameComponentUsageMigrator extends ComponentUsageMigrator { + @override + String get fixmePrefix => 'FIXME(unify_package_rename)'; + + @override + bool shouldMigrateUsage(FluentComponentUsage usage) => true; + + @override + void migrateUsage(FluentComponentUsage usage) { + super.migrateUsage(usage); + + final factoryElement = usage.factoryTopLevelVariableElement; + if (factoryElement == null) return; + + // Replace 'mui' namespace usage with 'unify'. + if(factoryElement.isDeclaredInPackage('react_material_ui')) { + final prefix = usage.node.function.tryCast()?.function.tryCast()?.prefix; + if(prefix != null && prefix.name == 'mui') { + yieldPatch( + unifyNs, + prefix.offset, + prefix.end, + ); + } + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 19007143..d911cff9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,6 +45,7 @@ executables: rmui_preparation: rmui_bundle_update: intl_message_migration: + unify_package_rename: dependency_validator: ignore: - meta diff --git a/test/resolved_file_context.dart b/test/resolved_file_context.dart index ed0ec301..94008f66 100644 --- a/test/resolved_file_context.dart +++ b/test/resolved_file_context.dart @@ -59,6 +59,11 @@ class SharedAnalysisContext { 'If this fails to resolve in GitHub Actions, make sure your test or' ' test group is tagged with "wsd" so that it\'s only run in Skynet.'); + /// A context root located at `test/test_fixtures/rmui_project` + /// that depends on the `react_material_ui` package (as well as `over_react`). + static final rmui = SharedAnalysisContext(p.join( + findPackageRootFor(p.current), 'test/test_fixtures/rmui_project')); + /// The path to the package root in which test files will be created /// and resolved. final String _path; diff --git a/test/test_fixtures/rmui_project/README.md b/test/test_fixtures/rmui_project/README.md new file mode 100644 index 00000000..0f35e921 --- /dev/null +++ b/test/test_fixtures/rmui_project/README.md @@ -0,0 +1,5 @@ +A package that depends on react_material_ui, and can be used as a context root for tests that require a resolved analysis context with access to react_material_ui APIs. + +This is separate from over_react_project, since react_material_ui is only required for some tests, and fetching it as a dependency requires access to a private Pub server, meaning it can't be run in GitHub Actions. + +To use, see `SharedAnalysisContext.rmui`. diff --git a/test/test_fixtures/rmui_project/lib/analysis_warmup.dart b/test/test_fixtures/rmui_project/lib/analysis_warmup.dart new file mode 100644 index 00000000..6ff6f99c --- /dev/null +++ b/test/test_fixtures/rmui_project/lib/analysis_warmup.dart @@ -0,0 +1,10 @@ +// This file imports RMUI and over_react and can be analyzed to warm up an +// analysis context during testing. + +import 'package:react_material_ui/react_material_ui.dart'; +import 'package:over_react/over_react.dart'; + +main() { + Button()(); + Dom.div()(); +} diff --git a/test/test_fixtures/rmui_project/pubspec.yaml b/test/test_fixtures/rmui_project/pubspec.yaml new file mode 100644 index 00000000..aac85bab --- /dev/null +++ b/test/test_fixtures/rmui_project/pubspec.yaml @@ -0,0 +1,10 @@ +name: rmui_project +environment: + sdk: '>=2.11.0 <3.0.0' +dependencies: + over_react: ^4.2.0 + react_material_ui: + hosted: + name: react_material_ui + url: https://pub.workiva.org + version: ^1.118.0 diff --git a/test/unify_package_rename_suggestors/mui_button_migrator_test.dart b/test/unify_package_rename_suggestors/mui_button_migrator_test.dart new file mode 100644 index 00000000..757331bd --- /dev/null +++ b/test/unify_package_rename_suggestors/mui_button_migrator_test.dart @@ -0,0 +1,51 @@ +// Copyright 2021 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dart:js_util'; + +import 'package:over_react_codemod/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart'; +import 'package:test/test.dart'; + +import '../mui_suggestors/components/shared.dart'; +import '../resolved_file_context.dart'; +import '../util.dart'; + +void main() { + final resolvedContext = SharedAnalysisContext.rmui; + + // Warm up analysis in a setUpAll so that if getting the resolved AST times out + // (which is more common for the WSD context), it fails here instead of failing the first test. + setUpAll(resolvedContext.warmUpAnalysis); + + group('PackageRenameComponentUsageMigrator', () { + final testSuggestor = getSuggestorTester( + PackageRenameComponentUsageMigrator(), + resolvedContext: resolvedContext, + ); + + test( + 'abc', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:over_react/over_react.dart'; + import 'package:react_material_ui/react_material_ui.dart' as mui; + + content() { + mui.Button()(); + } +''', + ); + }); + }); +} From c629b020684a49bf6a66743913cd5ee7296a3032 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Wed, 20 Sep 2023 16:11:41 -0700 Subject: [PATCH 02/29] Add tests --- .../constants.dart | 8 +- ...ckage_rename_component_usage_migrator.dart | 21 ++-- .../mui_button_migrator_test.dart | 51 --------- ..._rename_component_usage_migrator_test.dart | 107 ++++++++++++++++++ 4 files changed, 124 insertions(+), 63 deletions(-) delete mode 100644 test/unify_package_rename_suggestors/mui_button_migrator_test.dart create mode 100644 test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart diff --git a/lib/src/unify_package_rename_suggestors/constants.dart b/lib/src/unify_package_rename_suggestors/constants.dart index a3a52337..ab854afb 100644 --- a/lib/src/unify_package_rename_suggestors/constants.dart +++ b/lib/src/unify_package_rename_suggestors/constants.dart @@ -12,5 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -/// The standard namespace to use when importing unify_ui. -const unifyNs = 'unify'; +/// A map of the standard namespaces for rmui imports to their unify equivalents. +const unifyNamespaceMapping = { + 'mui': 'unify', + 'alpha_mui': 'alpha_unify', + 'mui_alpha': 'unify_alpha', +}; diff --git a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart index 667c70fd..5ee97118 100644 --- a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart +++ b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart @@ -33,16 +33,17 @@ class PackageRenameComponentUsageMigrator extends ComponentUsageMigrator { final factoryElement = usage.factoryTopLevelVariableElement; if (factoryElement == null) return; - // Replace 'mui' namespace usage with 'unify'. - if(factoryElement.isDeclaredInPackage('react_material_ui')) { - final prefix = usage.node.function.tryCast()?.function.tryCast()?.prefix; - if(prefix != null && prefix.name == 'mui') { - yieldPatch( - unifyNs, - prefix.offset, - prefix.end, - ); + // Replace 'mui' namespaces usage with 'unify'. + if (factoryElement.isDeclaredInPackage('react_material_ui')) { + final prefix = usage.node.function + .tryCast() + ?.function + .tryCast() + ?.prefix; + final newPrefixName = unifyNamespaceMapping[prefix?.name]; + if (prefix != null && newPrefixName != null) { + yieldPatch(newPrefixName, prefix.offset, prefix.end); } - } + } } } diff --git a/test/unify_package_rename_suggestors/mui_button_migrator_test.dart b/test/unify_package_rename_suggestors/mui_button_migrator_test.dart deleted file mode 100644 index 757331bd..00000000 --- a/test/unify_package_rename_suggestors/mui_button_migrator_test.dart +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2021 Workiva Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import 'dart:js_util'; - -import 'package:over_react_codemod/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart'; -import 'package:test/test.dart'; - -import '../mui_suggestors/components/shared.dart'; -import '../resolved_file_context.dart'; -import '../util.dart'; - -void main() { - final resolvedContext = SharedAnalysisContext.rmui; - - // Warm up analysis in a setUpAll so that if getting the resolved AST times out - // (which is more common for the WSD context), it fails here instead of failing the first test. - setUpAll(resolvedContext.warmUpAnalysis); - - group('PackageRenameComponentUsageMigrator', () { - final testSuggestor = getSuggestorTester( - PackageRenameComponentUsageMigrator(), - resolvedContext: resolvedContext, - ); - - test( - 'abc', () async { - await testSuggestor( - input: /*language=dart*/ ''' - import 'package:over_react/over_react.dart'; - import 'package:react_material_ui/react_material_ui.dart' as mui; - - content() { - mui.Button()(); - } -''', - ); - }); - }); -} diff --git a/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart b/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart new file mode 100644 index 00000000..7b5279f5 --- /dev/null +++ b/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart @@ -0,0 +1,107 @@ +// Copyright 2023 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:over_react_codemod/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart'; +import 'package:test/test.dart'; + +import '../resolved_file_context.dart'; +import '../util.dart'; + +void main() { + final resolvedContext = SharedAnalysisContext.rmui; + + // Warm up analysis in a setUpAll so that if getting the resolved AST times out + // (which is more common for the WSD context), it fails here instead of failing the first test. + setUpAll(resolvedContext.warmUpAnalysis); + + group('PackageRenameComponentUsageMigrator', () { + final testSuggestor = getSuggestorTester( + PackageRenameComponentUsageMigrator(), + resolvedContext: resolvedContext, + ); + + group('namespace on component usage', () { + test('mui namespace from react_material_ui is migrated to unify', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_mui; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; + + content() { + mui.Button()(); + mui.LinearProgress()(); + alpha_mui.Rating()(); + mui_alpha.Rating()(); + } +''', + expectedOutput: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_mui; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; + + content() { + unify.Button()(); + unify.LinearProgress()(); + alpha_unify.Rating()(); + unify_alpha.Rating()(); + } +''', + ); + }); + + test('mui namespace from a different package is not migrated', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:over_react/over_react.dart' as mui; + import 'package:over_react/over_react.dart' as alpha_mui; + import 'package:over_react/over_react.dart' as mui_alpha; + + content() { + mui.Fragment()(); + alpha_mui.Fragment()(); + mui_alpha.Fragment()(); + } +''', + ); + }); + + test('non-mui namespace on a react_material_ui import', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as abc; + + content() { + abc.Button()(); + abc.LinearProgress()(); + } +''', + ); + }); + + test('no namespace on a react_material_ui import', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart'; + + content() { + Button()(); + LinearProgress()(); + } +''', + ); + }); + }); + }); +} From 351db4f38fd838c4cb809a04fe8374b192f2f584 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Wed, 20 Sep 2023 16:49:50 -0700 Subject: [PATCH 03/29] Add component renames --- .../constants.dart | 10 +++- ...ckage_rename_component_usage_migrator.dart | 22 ++++--- ..._rename_component_usage_migrator_test.dart | 58 +++++++++++++++++++ 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/lib/src/unify_package_rename_suggestors/constants.dart b/lib/src/unify_package_rename_suggestors/constants.dart index ab854afb..582f442a 100644 --- a/lib/src/unify_package_rename_suggestors/constants.dart +++ b/lib/src/unify_package_rename_suggestors/constants.dart @@ -13,8 +13,16 @@ // limitations under the License. /// A map of the standard namespaces for rmui imports to their unify equivalents. -const unifyNamespaceMapping = { +const rmuiToUnifyNamespaces = { 'mui': 'unify', 'alpha_mui': 'alpha_unify', 'mui_alpha': 'unify_alpha', }; + +/// A map of RMUI component names to their new names in unify_ui. +const rmuiToUnifyComponentNames = { + 'Alert': 'WsdAlert', + 'LinkButton': 'WsdLinkButton', + 'MuiList': 'UnifyList', + 'WorkivaMuiThemeProvider': 'UnifyThemeProvider', +}; diff --git a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart index 5ee97118..d7c6fb71 100644 --- a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart +++ b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart @@ -33,17 +33,25 @@ class PackageRenameComponentUsageMigrator extends ComponentUsageMigrator { final factoryElement = usage.factoryTopLevelVariableElement; if (factoryElement == null) return; - // Replace 'mui' namespaces usage with 'unify'. if (factoryElement.isDeclaredInPackage('react_material_ui')) { - final prefix = usage.node.function - .tryCast() - ?.function - .tryCast() - ?.prefix; - final newPrefixName = unifyNamespaceMapping[prefix?.name]; + // Replace 'mui' namespaces usage with 'unify'. + final prefix = usage.factory.tryCast()?.prefix; + final newPrefixName = rmuiToUnifyNamespaces[prefix?.name]; if (prefix != null && newPrefixName != null) { yieldPatch(newPrefixName, prefix.offset, prefix.end); } + + // todo add fixme comment for things that should be updated - Wsd prefixed things + + // todo remove mui prefix for Wsd prefixed components or make new prefix for import to find (unify_wsd) + + // Update components that were renamed in unify_ui. + final identifier = usage.factory.tryCast() ?? + usage.factory.tryCast()?.identifier; + final newComponentName = rmuiToUnifyComponentNames[identifier?.name]; + if (identifier != null && newComponentName != null) { + yieldPatch(newComponentName, identifier.offset, identifier.end); + } } } } diff --git a/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart b/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart index 7b5279f5..98d734af 100644 --- a/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart +++ b/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart @@ -99,6 +99,64 @@ void main() { Button()(); LinearProgress()(); } +''', + ); + }); + }); + + group('rename components', () { + test('from react_material_ui to unify equivalents', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/react_material_ui.dart'; + import 'package:react_material_ui/components/providers/workiva_mui_theme_provider.dart'; + + content() { + mui.Alert()(); + Alert()(); + mui.LinkButton()(); + LinkButton()(); + mui.MuiList()(); + MuiList()(); + WorkivaMuiThemeProvider()(); + } +''', + expectedOutput: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/react_material_ui.dart'; + import 'package:react_material_ui/components/providers/workiva_mui_theme_provider.dart'; + + content() { + unify.WsdAlert()(); + WsdAlert()(); + unify.WsdLinkButton()(); + WsdLinkButton()(); + unify.UnifyList()(); + UnifyList()(); + UnifyThemeProvider()(); + } +''', + ); + }); + + test('except when they are not from react_material_ui', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:over_react/over_react.dart'; + + // Shadows the RMUI factories + UiFactory Alert; + UiFactory LinkButton; + UiFactory MuiList; + UiFactory WorkivaMuiThemeProvider; + + content() { + Alert()(); + LinkButton()(); + MuiList()(); + WorkivaMuiThemeProvider()(); + } ''', ); }); From dab9ea62e484dbc0a66b52c5e0e5dfd4dcb49c99 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Thu, 21 Sep 2023 14:54:59 -0700 Subject: [PATCH 04/29] Add FIXME comments --- ...ckage_rename_component_usage_migrator.dart | 6 ++ ..._rename_component_usage_migrator_test.dart | 58 +++++++++++++++++-- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart index d7c6fb71..6eaf620d 100644 --- a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart +++ b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart @@ -52,6 +52,12 @@ class PackageRenameComponentUsageMigrator extends ComponentUsageMigrator { if (identifier != null && newComponentName != null) { yieldPatch(newComponentName, identifier.offset, identifier.end); } + + // Add comments for components that need manual verification. + if (identifier?.name == 'Badge' || identifier?.name == 'LinearProgress') { + yieldUsageFixmePatch(usage, + 'Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart.'); + } } } } diff --git a/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart b/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart index 98d734af..b3ff9dd6 100644 --- a/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart +++ b/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart @@ -41,7 +41,7 @@ void main() { content() { mui.Button()(); - mui.LinearProgress()(); + mui.Checkbox()(); alpha_mui.Rating()(); mui_alpha.Rating()(); } @@ -53,7 +53,7 @@ void main() { content() { unify.Button()(); - unify.LinearProgress()(); + unify.Checkbox()(); alpha_unify.Rating()(); unify_alpha.Rating()(); } @@ -84,7 +84,7 @@ void main() { content() { abc.Button()(); - abc.LinearProgress()(); + abc.Checkbox()(); } ''', ); @@ -97,7 +97,7 @@ void main() { content() { Button()(); - LinearProgress()(); + Checkbox()(); } ''', ); @@ -157,6 +157,56 @@ void main() { MuiList()(); WorkivaMuiThemeProvider()(); } +''', + ); + }); + }); + + group('fixme comments', () { + test('for specific components that need manual intervention', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/react_material_ui.dart'; + + content() { + mui.Badge()(); + Badge()(); + mui.LinearProgress()(); + LinearProgress()(); + } +''', + expectedOutput: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/react_material_ui.dart'; + + content() { + // FIXME(unify_package_rename) Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart. + unify.Badge()(); + // FIXME(unify_package_rename) Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart. + Badge()(); + // FIXME(unify_package_rename) Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart. + unify.LinearProgress()(); + // FIXME(unify_package_rename) Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart. + LinearProgress()(); + } +''', + ); + }); + + test('except when they are not from react_material_ui', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:over_react/over_react.dart'; + + // Shadows the RMUI factories + UiFactory Badge; + UiFactory LinearProgress; + + content() { + Badge()(); + LinearProgress()(); + } ''', ); }); From 55fce191f97edc210f178fbf7f54c8cd94d58534 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Thu, 21 Sep 2023 17:47:32 -0700 Subject: [PATCH 05/29] Add updated namespace for wsd entrypoint --- .../constants.dart | 3 ++ ...ckage_rename_component_usage_migrator.dart | 29 ++++++++++++------- ..._rename_component_usage_migrator_test.dart | 16 +++++++--- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/lib/src/unify_package_rename_suggestors/constants.dart b/lib/src/unify_package_rename_suggestors/constants.dart index 582f442a..4d41df97 100644 --- a/lib/src/unify_package_rename_suggestors/constants.dart +++ b/lib/src/unify_package_rename_suggestors/constants.dart @@ -26,3 +26,6 @@ const rmuiToUnifyComponentNames = { 'MuiList': 'UnifyList', 'WorkivaMuiThemeProvider': 'UnifyThemeProvider', }; + +/// The namespace that will be used for the `unify_ui/components/wsd.dart` import that is added. +const unifyWsdNamespace = 'unify_wsd'; diff --git a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart index 6eaf620d..8a63f812 100644 --- a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart +++ b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart @@ -34,23 +34,30 @@ class PackageRenameComponentUsageMigrator extends ComponentUsageMigrator { if (factoryElement == null) return; if (factoryElement.isDeclaredInPackage('react_material_ui')) { - // Replace 'mui' namespaces usage with 'unify'. - final prefix = usage.factory.tryCast()?.prefix; - final newPrefixName = rmuiToUnifyNamespaces[prefix?.name]; - if (prefix != null && newPrefixName != null) { - yieldPatch(newPrefixName, prefix.offset, prefix.end); - } - - // todo add fixme comment for things that should be updated - Wsd prefixed things - - // todo remove mui prefix for Wsd prefixed components or make new prefix for import to find (unify_wsd) + // Save the import namespace to later replace with a unify version. + final importNamespace = usage.factory.tryCast()?.prefix; + final newImportNamespace = rmuiToUnifyNamespaces[importNamespace?.name]; // Update components that were renamed in unify_ui. final identifier = usage.factory.tryCast() ?? usage.factory.tryCast()?.identifier; final newComponentName = rmuiToUnifyComponentNames[identifier?.name]; + final isFromWsdEntrypoint = newComponentName?.startsWith('Wsd') ?? false; if (identifier != null && newComponentName != null) { - yieldPatch(newComponentName, identifier.offset, identifier.end); + if (isFromWsdEntrypoint) { + // Overwrite or add import namespace for components that will be imported from the separate + // unify_ui/components/wsd.dart entrypoint so we can keep the namespace of the import + // we add consistent with the components that use it. + final factory = usage.factory.tryCast() ?? identifier; + yieldPatch('$unifyWsdNamespace.$newComponentName', factory.offset, factory.end); + } else { + yieldPatch(newComponentName, identifier.offset, identifier.end); + } + } + + // Replace 'mui' namespaces usage with 'unify'. + if (importNamespace != null && newImportNamespace != null && !isFromWsdEntrypoint) { + yieldPatch(newImportNamespace, importNamespace.offset, importNamespace.end); } // Add comments for components that need manual verification. diff --git a/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart b/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart index b3ff9dd6..413f2219 100644 --- a/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart +++ b/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart @@ -110,30 +110,38 @@ void main() { input: /*language=dart*/ ''' import 'package:react_material_ui/react_material_ui.dart' as mui; import 'package:react_material_ui/react_material_ui.dart'; + import 'package:react_material_ui/react_material_ui.dart' as random_rmui_namespace; import 'package:react_material_ui/components/providers/workiva_mui_theme_provider.dart'; content() { mui.Alert()(); Alert()(); + random_rmui_namespace.Alert()(); mui.LinkButton()(); LinkButton()(); + random_rmui_namespace.LinkButton()(); mui.MuiList()(); MuiList()(); + random_rmui_namespace.MuiList()(); WorkivaMuiThemeProvider()(); } ''', expectedOutput: /*language=dart*/ ''' import 'package:react_material_ui/react_material_ui.dart' as mui; import 'package:react_material_ui/react_material_ui.dart'; + import 'package:react_material_ui/react_material_ui.dart' as random_rmui_namespace; import 'package:react_material_ui/components/providers/workiva_mui_theme_provider.dart'; content() { - unify.WsdAlert()(); - WsdAlert()(); - unify.WsdLinkButton()(); - WsdLinkButton()(); + unify_wsd.WsdAlert()(); + unify_wsd.WsdAlert()(); + unify_wsd.WsdAlert()(); + unify_wsd.WsdLinkButton()(); + unify_wsd.WsdLinkButton()(); + unify_wsd.WsdLinkButton()(); unify.UnifyList()(); UnifyList()(); + random_rmui_namespace.UnifyList()(); UnifyThemeProvider()(); } ''', From e5f1cfcb30e9508ed98caf803d70b00bd694a011 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Thu, 28 Sep 2023 18:09:44 -0700 Subject: [PATCH 06/29] Add ImportRenamer suggestor --- lib/src/executables/mui_migration.dart | 26 ++- lib/src/executables/unify_package_rename.dart | 109 +++++++----- lib/src/mui_suggestors/constants.dart | 3 + .../unused_wsd_import_remover.dart | 54 ------ .../constants.dart | 46 +++++- .../import_renamer.dart | 51 ++++++ ...ckage_rename_component_usage_migrator.dart | 22 ++- .../mui_importer.dart => util/importer.dart} | 95 +++++------ lib/src/util/unused_import_remover.dart | 55 +++++++ .../import_renamer_test.dart | 155 ++++++++++++++++++ ..._rename_component_usage_migrator_test.dart | 3 + .../import_remover_test.dart} | 6 +- .../importer_test.dart} | 34 ++-- 13 files changed, 474 insertions(+), 185 deletions(-) delete mode 100644 lib/src/mui_suggestors/unused_wsd_import_remover.dart create mode 100644 lib/src/unify_package_rename_suggestors/import_renamer.dart rename lib/src/{mui_suggestors/mui_importer.dart => util/importer.dart} (58%) create mode 100644 lib/src/util/unused_import_remover.dart create mode 100644 test/unify_package_rename_suggestors/import_renamer_test.dart rename test/{mui_suggestors/unused_wsd_import_remover_test.dart => util/import_remover_test.dart} (95%) rename test/{mui_suggestors/mui_importer_test.dart => util/importer_test.dart} (92%) diff --git a/lib/src/executables/mui_migration.dart b/lib/src/executables/mui_migration.dart index b53dacbd..3c49454c 100644 --- a/lib/src/executables/mui_migration.dart +++ b/lib/src/executables/mui_migration.dart @@ -21,14 +21,17 @@ import 'package:codemod/codemod.dart'; import 'package:glob/glob.dart'; import 'package:glob/list_local_fs.dart'; import 'package:logging/logging.dart'; +import 'package:over_react_codemod/src/mui_suggestors/constants.dart'; import 'package:over_react_codemod/src/util.dart'; import 'package:over_react_codemod/src/ignoreable.dart'; import 'package:over_react_codemod/src/mui_suggestors/components.dart'; -import 'package:over_react_codemod/src/mui_suggestors/mui_importer.dart'; -import 'package:over_react_codemod/src/mui_suggestors/unused_wsd_import_remover.dart'; import 'package:over_react_codemod/src/util/package_util.dart'; import 'package:over_react_codemod/src/util/pubspec_upgrader.dart'; import 'package:over_react_codemod/src/util/logging.dart'; + +import '../util/importer.dart'; +import '../util/unused_import_remover.dart'; + final _log = Logger('orcm.mui_migration'); const _componentOption = 'component'; @@ -93,8 +96,7 @@ void main(List args) async { final parsedArgs = parser.parse(args); if (parsedArgs['help'] as bool) { - stderr.writeln( - 'Migrates web_skin_dart component usages to react_material_ui.'); + stderr.writeln('Migrates web_skin_dart component usages to react_material_ui.'); stderr.writeln(); stderr.writeln('Usage:'); stderr.writeln(' mui_migration [arguments]'); @@ -111,10 +113,8 @@ void main(List args) async { // // An alternative would be to use `--` and `arguments.rest` to pass along codemod // args, but that's not as convenient to the user and makes showing help a bit more complicated. - final codemodArgs = _allCodemodFlags - .where((name) => parsedArgs[name] as bool) - .map((name) => '--$name') - .toList(); + final codemodArgs = + _allCodemodFlags.where((name) => parsedArgs[name] as bool).map((name) => '--$name').toList(); // codemod sets up a global logging handler that forwards to the console, and // we want that set up before we do other non-codemodd things that might log. @@ -187,8 +187,8 @@ void main(List args) async { // should only be handled by a single migrator, and shouldn't depend on the // output of previous migrators. [aggregate(migratorsToRun)], - [muiImporter], - [unusedWsdImportRemover], + [importerSuggestorBuilder(importUri: rmuiImportUri, importNamespace: muiNs)], + [unusedImportRemoverSuggestorBuilder(packageName: 'web_skin_dart')], ]); if (exitCode != 0) return; @@ -197,8 +197,7 @@ void main(List args) async { exitCode = await runInteractiveCodemod( pubspecYamlPaths(), aggregate([ - PubspecUpgrader('react_material_ui', rmuiVersionRange, - hostedUrl: 'https://pub.workiva.org'), + PubspecUpgrader('react_material_ui', rmuiVersionRange, hostedUrl: 'https://pub.workiva.org'), ].map((s) => ignoreable(s))), defaultYes: true, args: codemodArgs, @@ -229,8 +228,7 @@ void sortPartsLast(List dartPaths) { } Future pubGetForAllPackageRoots(Iterable files) async { - _log.info( - 'Running `pub get` if needed so that all Dart files can be resolved...'); + _log.info('Running `pub get` if needed so that all Dart files can be resolved...'); final packageRoots = files.map(findPackageRootFor).toSet(); for (final packageRoot in packageRoots) { await runPubGetIfNeeded(packageRoot); diff --git a/lib/src/executables/unify_package_rename.dart b/lib/src/executables/unify_package_rename.dart index 3108b6a4..263190e0 100644 --- a/lib/src/executables/unify_package_rename.dart +++ b/lib/src/executables/unify_package_rename.dart @@ -16,66 +16,101 @@ import 'dart:io'; import 'package:args/args.dart'; import 'package:codemod/codemod.dart'; +import 'package:over_react_codemod/src/executables/mui_migration.dart'; import 'package:over_react_codemod/src/ignoreable.dart'; import 'package:over_react_codemod/src/rmui_bundle_update_suggestors/constants.dart'; import 'package:over_react_codemod/src/rmui_bundle_update_suggestors/dart_script_updater.dart'; import 'package:over_react_codemod/src/rmui_bundle_update_suggestors/html_script_updater.dart'; +import 'package:over_react_codemod/src/unify_package_rename_suggestors/constants.dart'; +import 'package:over_react_codemod/src/unify_package_rename_suggestors/import_renamer.dart'; import 'package:over_react_codemod/src/util.dart'; import 'package:over_react_codemod/src/util/pubspec_upgrader.dart'; +import '../unify_package_rename_suggestors/package_rename_component_usage_migrator.dart'; +import '../util/importer.dart'; +import '../util/unused_import_remover.dart'; + const _changesRequiredOutput = """ To update your code, run the following commands in your repository: dart pub global activate over_react_codemod dart pub global run over_react_codemod:unify_package_rename """; +class CodemodInfo { + CodemodInfo({required this.paths, required this.sequence}); + Iterable paths; + Iterable sequence; +} + void main(List args) async { final parser = ArgParser.allowAnything(); final parsedArgs = parser.parse(args); - // todo can we also remove rmui dependency here??? + /// Runs a list of codemods one after the other and returns exit code 0 if any fail. + Future runCodemods( + Iterable codemods, + ) async { + for (final sequence in codemods) { + final exitCode = await runInteractiveCodemodSequence( + sequence.paths, + sequence.sequence, + defaultYes: true, + args: parsedArgs.rest, + additionalHelpOutput: parser.usage, + changesRequiredOutput: _changesRequiredOutput, + ); + if (exitCode != 0) return exitCode; + } - exitCode = await runInteractiveCodemod( - pubspecYamlPaths(), - aggregate([ - // todo update version: - PubspecUpgrader('unify_ui', parseVersionRange('^1.89.1'), - hostedUrl: 'https://pub.workiva.org', shouldAddDependencies: false), - ].map((s) => ignoreable(s))), - defaultYes: true, - args: parsedArgs.rest, - additionalHelpOutput: parser.usage, - changesRequiredOutput: _changesRequiredOutput, - ); + return 0; + } - if (exitCode != 0) return; - - // Update RMUI bundle script in all HTML files (and templates) to Unify bundle. - exitCode = await runInteractiveCodemodSequence( - allHtmlPathsIncludingTemplates(), - [ + exitCode = await runCodemods([ + // todo can we also remove rmui dependency here??? + // Add unify_ui dependency. + CodemodInfo(paths: pubspecYamlPaths(), sequence: [ + aggregate( + [ + // todo update version: + PubspecUpgrader('unify_ui', parseVersionRange('^1.89.1'), + hostedUrl: 'https://pub.workiva.org', shouldAddDependencies: true), + ].map((s) => ignoreable(s)), + ) + ]), + // Update RMUI bundle script in all HTML files (and templates) to Unify bundle. + CodemodInfo(paths: allHtmlPathsIncludingTemplates(), sequence: [ HtmlScriptUpdater(rmuiBundleDevUpdated, unifyBundleDev), HtmlScriptUpdater(rmuiBundleProdUpdated, unifyBundleProd), - ], - defaultYes: true, - args: parsedArgs.rest, - additionalHelpOutput: parser.usage, - changesRequiredOutput: _changesRequiredOutput, - ); + ]), + // Update RMUI bundle script in all Dart files to Unify bundle. + CodemodInfo(paths: allDartPathsExceptHidden(), sequence: [ + DartScriptUpdater(rmuiBundleDevUpdated, unifyBundleDev), + DartScriptUpdater(rmuiBundleProdUpdated, unifyBundleProd), + ]), + ]); if (exitCode != 0) return; - // Update RMUI bundle script in all Dart files to Unify bundle. - exitCode = await runInteractiveCodemodSequence( - allDartPathsExceptHidden(), - [ - DartScriptUpdater(rmuiBundleDevUpdated, unifyBundleDev), - DartScriptUpdater(rmuiBundleProdUpdated, unifyBundleProd), - ], - defaultYes: true, - args: parsedArgs.rest, - additionalHelpOutput: parser.usage, - changesRequiredOutput: _changesRequiredOutput, - ); + final dartPaths = dartFilesToMigrate().toList(); + // Work around parts being unresolved if you resolve them before their libraries. + // TODO - reference analyzer issue for this once it's created + sortPartsLast(dartPaths); + + await pubGetForAllPackageRoots(dartPaths); + exitCode = await runCodemods([ + // todo add comments + CodemodInfo(paths: dartPaths, sequence: [PackageRenameComponentUsageMigrator()]), + CodemodInfo( + paths: dartPaths, + sequence: importsToUpdate.where((import) => import.namespace != null).map((import) => + importerSuggestorBuilder(importUri: import.uri, importNamespace: import.namespace!))), + CodemodInfo( + paths: dartPaths, + sequence: [unusedImportRemoverSuggestorBuilder(packageName: 'react_material_ui')]), + CodemodInfo( + paths: dartPaths, + sequence: [ImportRenamer(oldPackageName: 'react_material_ui', newPackageName: 'unify_ui')]) + ]); + if (exitCode != 0) return; } diff --git a/lib/src/mui_suggestors/constants.dart b/lib/src/mui_suggestors/constants.dart index 19cfd26d..99790475 100644 --- a/lib/src/mui_suggestors/constants.dart +++ b/lib/src/mui_suggestors/constants.dart @@ -18,3 +18,6 @@ /// is what `muiImporter` picks up on to add the react_material_ui import to /// libraries only when it's needed. const muiNs = 'mui'; + +/// The import uri for the main react_material_ui entrypoint. +const rmuiImportUri = 'package:react_material_ui/react_material_ui.dart'; diff --git a/lib/src/mui_suggestors/unused_wsd_import_remover.dart b/lib/src/mui_suggestors/unused_wsd_import_remover.dart deleted file mode 100644 index 3a410453..00000000 --- a/lib/src/mui_suggestors/unused_wsd_import_remover.dart +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2021 Workiva Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/dart/ast/syntactic_entity.dart'; -import 'package:codemod/codemod.dart'; -import 'package:logging/logging.dart'; - -final _log = Logger('unusedWsdImportRemover'); -/// A suggestor that removes unused imports for WSD. -Stream unusedWsdImportRemover(FileContext context) async* { - final unitResult = await context.getResolvedUnit(); - if (unitResult == null) { - // Most likely a part and not a library. - return; - } - final unusedImportErrors = unitResult.errors - .where((error) => error.errorCode.name.toLowerCase() == 'unused_import') - .toList(); - - final allImports = - unitResult.unit.directives.whereType().toList(); - - for (final error in unusedImportErrors) { - final matchingImport = - allImports.singleWhere((import) => import.containsOffset(error.offset)); - final importUri = matchingImport.uriContent; - if (importUri != null && importUri.startsWith('package:web_skin_dart/')) { - final prevTokenEnd = matchingImport.beginToken.previous?.end; - // Try to take the newline before the import, but watch out - // for prevToken's offset/end being -1 if it's this import has the - // first token in the file. - final startOffset = prevTokenEnd != null && prevTokenEnd != -1 - ? prevTokenEnd - : matchingImport.offset; - yield Patch('', startOffset, matchingImport.end); - } - } -} - -extension on SyntacticEntity { - bool containsOffset(int offset) => offset >= this.offset && offset < end; -} diff --git a/lib/src/unify_package_rename_suggestors/constants.dart b/lib/src/unify_package_rename_suggestors/constants.dart index 4d41df97..6f0cee34 100644 --- a/lib/src/unify_package_rename_suggestors/constants.dart +++ b/lib/src/unify_package_rename_suggestors/constants.dart @@ -12,12 +12,46 @@ // See the License for the specific language governing permissions and // limitations under the License. -/// A map of the standard namespaces for rmui imports to their unify equivalents. -const rmuiToUnifyNamespaces = { - 'mui': 'unify', - 'alpha_mui': 'alpha_unify', - 'mui_alpha': 'unify_alpha', -}; +// todo comment +class UnifyImportInfo { + UnifyImportInfo(this.uri, {this.rmuiUri, this.namespace, this.possibleMuiNamespaces}); + String? namespace; + List? possibleMuiNamespaces; + String uri; + String? rmuiUri; +} + +/// A list of the standard imports for unify_ui that should be updated. +/// +/// Only adds namespace information if the import is commonly used with a namespace. +/// Only adds RMUI uri information if it is different from a simple package name swap. +final importsToUpdate = [ + UnifyImportInfo( + 'package:unify_ui/unify_ui.dart', + rmuiUri: 'package:react_material_ui/react_material_ui.dart', + namespace: 'unify', + possibleMuiNamespaces: ['mui'], + ), + UnifyImportInfo( + 'package:unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart', + rmuiUri: + 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart', + namespace: 'alpha_unify', + possibleMuiNamespaces: ['alpha_mui', 'mui_alpha'], + ), + UnifyImportInfo( + 'package:unify_ui/components/wsd.dart', + namespace: unifyWsdNamespace, + ), + UnifyImportInfo( + 'package:unify_ui/components/list.dart', + rmuiUri: 'package:react_material_ui/components/mui_list.dart', + ), + UnifyImportInfo( + 'package:unify_ui/styles/styled.dart', + rmuiUri: 'package:react_material_ui/for_cp_use_only/styled.dart', + ) +]; /// A map of RMUI component names to their new names in unify_ui. const rmuiToUnifyComponentNames = { diff --git a/lib/src/unify_package_rename_suggestors/import_renamer.dart b/lib/src/unify_package_rename_suggestors/import_renamer.dart new file mode 100644 index 00000000..ddb445c0 --- /dev/null +++ b/lib/src/unify_package_rename_suggestors/import_renamer.dart @@ -0,0 +1,51 @@ +// Copyright 2023 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:codemod/codemod.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; + +import 'constants.dart'; + +/// Suggestor that updates imports from [oldPackageName] to [newPackageName]. +class ImportRenamer extends RecursiveAstVisitor with AstVisitingSuggestor { + final String oldPackageName; + final String newPackageName; + + ImportRenamer({required this.oldPackageName, required this.newPackageName}); + + @override + void visitImportDirective(ImportDirective node) { + super.visitImportDirective(node); + + final importUri = node.uri.stringValue; + if (importUri != null && importUri.startsWith('package:$oldPackageName/')) { + final specialCaseRmuiImport = importsToUpdate.where((import) => importUri == import.rmuiUri); + if (specialCaseRmuiImport.isNotEmpty) { + if (specialCaseRmuiImport.single.possibleMuiNamespaces?.contains(node.prefix?.name) ?? + false) { + // Do nothing if the import prefix is the old RMUI prefix - a new import with a new + // namespace should have already been added by [importerSuggestorBuilder] in `unify_package_rename.dart`. + return; + } + yieldPatch('\'${specialCaseRmuiImport.single.uri}\'', node.uri.offset, node.uri.end); + } else { + yieldPatch( + '\'${importUri.replaceFirst('package:$oldPackageName/', 'package:$newPackageName/')}\'', + node.uri.offset, + node.uri.end); + } + } + } +} diff --git a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart index 8a63f812..292c6df4 100644 --- a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart +++ b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart @@ -13,6 +13,7 @@ // limitations under the License. import 'package:analyzer/dart/ast/ast.dart'; +import 'package:collection/collection.dart'; import 'package:over_react_codemod/src/unify_package_rename_suggestors/constants.dart'; import 'package:over_react_codemod/src/util.dart'; import 'package:over_react_codemod/src/util/component_usage.dart'; @@ -26,6 +27,20 @@ class PackageRenameComponentUsageMigrator extends ComponentUsageMigrator { @override bool shouldMigrateUsage(FluentComponentUsage usage) => true; + // todo update to skip all flags + @override + bool get shouldFlagUnsafeMethodCalls => false; + @override + bool get shouldFlagUntypedSingleProp => false; + @override + bool get shouldFlagExtensionMembers => false; + @override + bool get shouldFlagPrefixedProps => false; + @override + bool get shouldFlagRefProp => false; + @override + bool get shouldFlagClassName => false; + @override void migrateUsage(FluentComponentUsage usage) { super.migrateUsage(usage); @@ -36,7 +51,12 @@ class PackageRenameComponentUsageMigrator extends ComponentUsageMigrator { if (factoryElement.isDeclaredInPackage('react_material_ui')) { // Save the import namespace to later replace with a unify version. final importNamespace = usage.factory.tryCast()?.prefix; - final newImportNamespace = rmuiToUnifyNamespaces[importNamespace?.name]; + final newImportNamespace = importsToUpdate + .where((import) => + importNamespace?.name != null && + (import.possibleMuiNamespaces?.contains(importNamespace?.name) ?? false)) + .singleOrNull + ?.namespace; // Update components that were renamed in unify_ui. final identifier = usage.factory.tryCast() ?? diff --git a/lib/src/mui_suggestors/mui_importer.dart b/lib/src/util/importer.dart similarity index 58% rename from lib/src/mui_suggestors/mui_importer.dart rename to lib/src/util/importer.dart index 0903b3fb..7c3eaf55 100644 --- a/lib/src/mui_suggestors/mui_importer.dart +++ b/lib/src/util/importer.dart @@ -18,44 +18,45 @@ import 'package:codemod/codemod.dart'; import 'package:collection/collection.dart'; import 'package:logging/logging.dart'; -import 'constants.dart'; - final _log = Logger('muiImporter'); -/// A suggestor that adds imports in libraries that reference -/// the [muiNs] import namespace (including in parts) but don't yet import it. -Stream muiImporter(FileContext context) async* { - final libraryResult = await context.getResolvedLibrary(); - if (libraryResult == null) { - // Most likely a part and not a library. - return; - } - // Parts that have not been generated can show up as `exists = false` but also `isPart = false`, - // so using the unitResults is a little trickier than using the libraryElement to get it. - final mainLibraryUnitResult = libraryResult.units.singleWhere((unitResult) => - unitResult.unit.declaredElement == - libraryResult.element.definingCompilationUnit); - - // Look for errors in the main compilation unit and its part files. - // Ignore null partContexts and partContexts elements caused by - // resolution issues and parts being excluded in the codemod file list. - final needsMuiImport = libraryResult.units - .expand((unitResult) => unitResult.errors) - .where((error) => error.errorCode.name == 'UNDEFINED_IDENTIFIER') - .any((error) => error.message.contains("Undefined name '$muiNs'")); - - if (!needsMuiImport) return; - - const rmuiImportUri = 'package:react_material_ui/react_material_ui.dart'; - - final insertInfo = _insertionLocationForPackageImport(rmuiImportUri, - mainLibraryUnitResult.unit, mainLibraryUnitResult.lineInfo); - yield Patch( - insertInfo.leadingNewlines + - "import '$rmuiImportUri' as $muiNs;" + - insertInfo.trailingNewlines, - insertInfo.offset, - insertInfo.offset); +/// Creates a suggestor that adds [importUri] imports in libraries that reference +/// the [importNamespace] (including in parts) but don't yet import it. +Suggestor importerSuggestorBuilder({ + required String importUri, + required String importNamespace, +}) { + return (context) async* { + final libraryResult = await context.getResolvedLibrary(); + if (libraryResult == null) { + // Most likely a part and not a library. + return; + } + + // Parts that have not been generated can show up as `exists = false` but also `isPart = false`, + // so using the unitResults is a little trickier than using the libraryElement to get it. + final mainLibraryUnitResult = libraryResult.units.singleWhere((unitResult) => + unitResult.unit.declaredElement == libraryResult.element.definingCompilationUnit); + + // Look for errors in the main compilation unit and its part files. + // Ignore null partContexts and partContexts elements caused by + // resolution issues and parts being excluded in the codemod file list. + final needsMuiImport = libraryResult.units + .expand((unitResult) => unitResult.errors) + .where((error) => error.errorCode.name == 'UNDEFINED_IDENTIFIER') + .any((error) => error.message.contains("Undefined name '$importNamespace'")); + + if (!needsMuiImport) return; + + final insertInfo = _insertionLocationForPackageImport( + importUri, mainLibraryUnitResult.unit, mainLibraryUnitResult.lineInfo); + yield Patch( + insertInfo.leadingNewlines + + "import '$importUri' as $importNamespace;" + + insertInfo.trailingNewlines, + insertInfo.offset, + insertInfo.offset); + }; } class _InsertionLocation { @@ -83,21 +84,16 @@ _InsertionLocation _insertionLocationForPackageImport( final imports = unit.directives.whereType(); final firstImport = imports.firstOrNull; - final dartImports = - imports.where((i) => i.uriContent?.startsWith('dart:') ?? false); + final dartImports = imports.where((i) => i.uriContent?.startsWith('dart:') ?? false); final lastDartImport = dartImports.lastOrNull; - final packageImports = - imports.where((i) => i.uriContent?.startsWith('package:') ?? false); - final firstPackageImportSortedAfterNewImport = packageImports - .where((i) => i.uriContent!.compareTo(importUri) > 0) - .firstOrNull; - final lastPackageImportSortedBeforeNewImport = packageImports - .where((i) => i.uriContent!.compareTo(importUri) < 0) - .lastOrNull; + final packageImports = imports.where((i) => i.uriContent?.startsWith('package:') ?? false); + final firstPackageImportSortedAfterNewImport = + packageImports.where((i) => i.uriContent!.compareTo(importUri) > 0).firstOrNull; + final lastPackageImportSortedBeforeNewImport = + packageImports.where((i) => i.uriContent!.compareTo(importUri) < 0).lastOrNull; - final firstNonImportDirective = - unit.directives.where((d) => d is! ImportDirective).firstOrNull; + final firstNonImportDirective = unit.directives.where((d) => d is! ImportDirective).firstOrNull; final AstNode relativeNode; final bool insertAfter; @@ -128,8 +124,7 @@ _InsertionLocation _insertionLocationForPackageImport( } else { // No directive to insert relative to; insert before the first member or // at the beginning of the file. - return _InsertionLocation(unit.declarations.firstOrNull?.offset ?? 0, - trailingNewlineCount: 2); + return _InsertionLocation(unit.declarations.firstOrNull?.offset ?? 0, trailingNewlineCount: 2); } return _InsertionLocation( diff --git a/lib/src/util/unused_import_remover.dart b/lib/src/util/unused_import_remover.dart new file mode 100644 index 00000000..ac09d6fe --- /dev/null +++ b/lib/src/util/unused_import_remover.dart @@ -0,0 +1,55 @@ +// Copyright 2021 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/syntactic_entity.dart'; +import 'package:codemod/codemod.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('unusedWsdImportRemover'); + +/// Creates a suggestor that removes unused imports for [packageName]. +Suggestor unusedImportRemoverSuggestorBuilder({required String packageName}) { + return (context) async* { + final unitResult = await context.getResolvedUnit(); + if (unitResult == null) { + // Most likely a part and not a library. + return; + } + final unusedImportErrors = unitResult.errors + .where((error) => error.errorCode.name.toLowerCase() == 'unused_import') + .toList(); + + final allImports = unitResult.unit.directives.whereType().toList(); + + for (final error in unusedImportErrors) { + final matchingImport = + allImports.singleWhere((import) => import.containsOffset(error.offset)); + final importUri = matchingImport.uriContent; + if (importUri != null && importUri.startsWith('package:$packageName/')) { + final prevTokenEnd = matchingImport.beginToken.previous?.end; + // Try to take the newline before the import, but watch out + // for prevToken's offset/end being -1 if it's this import has the + // first token in the file. + final startOffset = + prevTokenEnd != null && prevTokenEnd != -1 ? prevTokenEnd : matchingImport.offset; + yield Patch('', startOffset, matchingImport.end); + } + } + }; +} + +extension on SyntacticEntity { + bool containsOffset(int offset) => offset >= this.offset && offset < end; +} diff --git a/test/unify_package_rename_suggestors/import_renamer_test.dart b/test/unify_package_rename_suggestors/import_renamer_test.dart new file mode 100644 index 00000000..d4d8abfb --- /dev/null +++ b/test/unify_package_rename_suggestors/import_renamer_test.dart @@ -0,0 +1,155 @@ +// Copyright 2023 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:over_react_codemod/src/unify_package_rename_suggestors/import_renamer.dart'; +import 'package:test/test.dart'; + +import '../util.dart'; + +void main() { + group('importRenamer', () { + final testSuggestor = getSuggestorTester( + ImportRenamer(oldPackageName: 'react_material_ui', newPackageName: 'unify_ui'), + ); + + test('does nothing when there are no imports', () async { + await testSuggestor(input: ''); + }); + + test('does nothing for non-react_material_ui imports', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:over_react/over_react.dart'; + ''', + ); + }); + + group('updates react_material_ui imports in a file', () { + test('', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:over_react/over_react.dart'; + import 'package:react_material_ui/react_material_ui.dart'; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart'; + import 'package:react_material_ui/components/badge.dart'; + import 'package:react_material_ui/abc.dart'; + + content() => Dom.div()(); + ''', + expectedOutput: /*language=dart*/ ''' + import 'package:over_react/over_react.dart'; + import 'package:unify_ui/unify_ui.dart'; + import 'package:unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart'; + import 'package:unify_ui/components/badge.dart'; + import 'package:unify_ui/abc.dart'; + + content() => Dom.div()(); + ''', + ); + }); + + test('for special cases when the new file path is different from the old one', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart'; + import 'package:over_react/over_react.dart'; + import 'package:web_skin_dart/ui_components.dart'; + import 'package:react_material_ui/for_cp_use_only/styled.dart'; + import 'package:react_material_ui/components/mui_list.dart'; + import 'package:react_material_ui/styles/styled.dart'; + + content() => Dom.div()(); + ''', + expectedOutput: /*language=dart*/ ''' + import 'package:unify_ui/unify_ui.dart'; + import 'package:over_react/over_react.dart'; + import 'package:web_skin_dart/ui_components.dart'; + import 'package:unify_ui/styles/styled.dart'; + import 'package:unify_ui/components/list.dart'; + import 'package:unify_ui/styles/styled.dart'; + + content() => Dom.div()(); + ''', + ); + }); + + test( + 'unless there is a namespace on the main entrypoints that will be updated by a different suggestor', + () async { + await testSuggestor( + input: /*language=dart*/ ''' + library lib; + + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_mui; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; + import 'package:over_react/over_react.dart' as mui; + import 'package:react_material_ui/components/badge.dart' as mui; + + content() => Dom.div()(); + ''', + expectedOutput: /*language=dart*/ ''' + library lib; + + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_mui; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; + import 'package:over_react/over_react.dart' as mui; + import 'package:unify_ui/components/badge.dart' as mui; + + content() => Dom.div()(); + ''', + ); + }); + + test('unless the imports are already updated to the new name', () async { + await testSuggestor( + input: /*language=dart*/ ''' + library lib; + + import 'package:unify_ui/unify_ui.dart' as mui; + import 'package:unify_ui/unify_ui.dart'; + import 'package:unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; + import 'package:unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart'; + import 'package:unify_ui/abc.dart'; + + content() => Dom.div()(); + ''', + ); + }); + + test('also works for other package name inputs', () async { + final testSuggestor = getSuggestorTester( + ImportRenamer(oldPackageName: 'test_old', newPackageName: 'test_new'), + ); + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:test_old/abc.dart' as mui; + import 'package:over_react/over_react.dart'; + import 'package:test_old/components/badge.dart'; + + content() => Dom.div()(); + ''', + expectedOutput: /*language=dart*/ ''' + import 'package:test_new/abc.dart' as mui; + import 'package:over_react/over_react.dart'; + import 'package:test_new/components/badge.dart'; + + content() => Dom.div()(); + ''', + ); + }); + }); + }); +} diff --git a/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart b/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart index 413f2219..c06fb7d4 100644 --- a/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart +++ b/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart @@ -18,6 +18,9 @@ import 'package:test/test.dart'; import '../resolved_file_context.dart'; import '../util.dart'; +// todo also test builders +// todo also test non-component usage of the namespace + void main() { final resolvedContext = SharedAnalysisContext.rmui; diff --git a/test/mui_suggestors/unused_wsd_import_remover_test.dart b/test/util/import_remover_test.dart similarity index 95% rename from test/mui_suggestors/unused_wsd_import_remover_test.dart rename to test/util/import_remover_test.dart index 6e84f340..8bdc9a03 100644 --- a/test/mui_suggestors/unused_wsd_import_remover_test.dart +++ b/test/util/import_remover_test.dart @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:over_react_codemod/src/mui_suggestors/unused_wsd_import_remover.dart'; +import 'package:over_react_codemod/src/util/unused_import_remover.dart'; import 'package:test/test.dart'; import '../resolved_file_context.dart'; import '../util.dart'; void main() { - group('unusedWsdImportRemover', () { + group('unusedImportRemoverSuggestorBuilder', () { final resolvedContext = SharedAnalysisContext.wsd; // Warm up analysis in a setUpAll so that if getting the resolved AST times out @@ -27,7 +27,7 @@ void main() { setUpAll(resolvedContext.warmUpAnalysis); final testSuggestor = getSuggestorTester( - unusedWsdImportRemover, + unusedImportRemoverSuggestorBuilder(packageName: 'web_skin_dart'), resolvedContext: resolvedContext, ); diff --git a/test/mui_suggestors/mui_importer_test.dart b/test/util/importer_test.dart similarity index 92% rename from test/mui_suggestors/mui_importer_test.dart rename to test/util/importer_test.dart index 7a42914c..4d2798f5 100644 --- a/test/mui_suggestors/mui_importer_test.dart +++ b/test/util/importer_test.dart @@ -13,15 +13,17 @@ // limitations under the License. import 'package:analyzer/error/error.dart'; -import 'package:over_react_codemod/src/mui_suggestors/mui_importer.dart'; +import 'package:over_react_codemod/src/mui_suggestors/constants.dart'; +import 'package:over_react_codemod/src/util/importer.dart'; import 'package:test/test.dart'; import '../resolved_file_context.dart'; import '../util.dart'; void main() { - group('muiImporter', () { + group('importerSuggestorBuilder', () { final resolvedContext = SharedAnalysisContext.overReact; + final muiImporter = importerSuggestorBuilder(importUri: rmuiImportUri, importNamespace: muiNs); // Warm up analysis in a setUpAll so that if getting the resolved AST times out // (which is more common for the WSD context), it fails here instead of failing the first test. @@ -34,9 +36,7 @@ void main() { resolvedContext: resolvedContext, ); - group( - 'adds a RMUI import when there is an undefined `mui` identifier in the file', - () { + group('adds a RMUI import when there is an undefined `mui` identifier in the file', () { bool isFakeUriError(AnalysisError error) => error.errorCode.name.toLowerCase() == 'uri_does_not_exist' && error.message.contains('fake'); @@ -294,28 +294,24 @@ void main() { }); }); - test( - 'adds a RMUI import when there is an undefined `mui` identifier in a part file', - () async { + test('adds a RMUI import when there is an undefined `mui` identifier in a part file', () async { // testSuggestor isn't really set up for multiple files, // so the test setup here is a little more manual. const partFilename = 'mui_importer_test_part.dart'; const mainLibraryFilename = 'mui_importer_test_main_library.dart'; - final partFileContext = - await resolvedContext.resolvedFileContextForTest(''' + final partFileContext = await resolvedContext.resolvedFileContextForTest(''' part of '${mainLibraryFilename}'; content() => mui.Button(); ''', - filename: partFilename, - // Don't pre-resolve since this isn't a library. - preResolveLibrary: false, - throwOnAnalysisErrors: false); + filename: partFilename, + // Don't pre-resolve since this isn't a library. + preResolveLibrary: false, + throwOnAnalysisErrors: false); - final mainLibraryFileContext = - await resolvedContext.resolvedFileContextForTest( + final mainLibraryFileContext = await resolvedContext.resolvedFileContextForTest( ''' part '${partFilename}'; ''', @@ -325,8 +321,7 @@ void main() { final mainPatches = await muiImporter(mainLibraryFileContext).toList(); expect(mainPatches, [ - hasPatchText(contains( - "import 'package:react_material_ui/react_material_ui.dart' as mui;")), + hasPatchText(contains("import 'package:react_material_ui/react_material_ui.dart' as mui;")), ]); final partPatches = await muiImporter(partFileContext).toList(); @@ -354,5 +349,4 @@ void main() { }); } -bool isUndefinedMuiError(AnalysisError error) => - error.message.contains("Undefined name 'mui'"); +bool isUndefinedMuiError(AnalysisError error) => error.message.contains("Undefined name 'mui'"); From b8974136e79a3ee1a5a7b55927e0d643685011a8 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Fri, 6 Oct 2023 11:11:30 -0700 Subject: [PATCH 07/29] Namespace updater attempt --- lib/src/executables/unify_package_rename.dart | 24 ++- .../namespace_usage_updater.dart | 68 ++++++ ...ckage_rename_component_usage_migrator.dart | 6 +- .../namespace_usage_updater_test.dart | 203 ++++++++++++++++++ ..._rename_component_usage_migrator_test.dart | 79 +------ 5 files changed, 290 insertions(+), 90 deletions(-) create mode 100644 lib/src/unify_package_rename_suggestors/namespace_usage_updater.dart create mode 100644 test/unify_package_rename_suggestors/namespace_usage_updater_test.dart diff --git a/lib/src/executables/unify_package_rename.dart b/lib/src/executables/unify_package_rename.dart index 263190e0..bb792950 100644 --- a/lib/src/executables/unify_package_rename.dart +++ b/lib/src/executables/unify_package_rename.dart @@ -23,6 +23,7 @@ import 'package:over_react_codemod/src/rmui_bundle_update_suggestors/dart_script import 'package:over_react_codemod/src/rmui_bundle_update_suggestors/html_script_updater.dart'; import 'package:over_react_codemod/src/unify_package_rename_suggestors/constants.dart'; import 'package:over_react_codemod/src/unify_package_rename_suggestors/import_renamer.dart'; +import 'package:over_react_codemod/src/unify_package_rename_suggestors/namespace_usage_updater.dart'; import 'package:over_react_codemod/src/util.dart'; import 'package:over_react_codemod/src/util/pubspec_upgrader.dart'; @@ -100,17 +101,18 @@ void main(List args) async { await pubGetForAllPackageRoots(dartPaths); exitCode = await runCodemods([ // todo add comments - CodemodInfo(paths: dartPaths, sequence: [PackageRenameComponentUsageMigrator()]), - CodemodInfo( - paths: dartPaths, - sequence: importsToUpdate.where((import) => import.namespace != null).map((import) => - importerSuggestorBuilder(importUri: import.uri, importNamespace: import.namespace!))), - CodemodInfo( - paths: dartPaths, - sequence: [unusedImportRemoverSuggestorBuilder(packageName: 'react_material_ui')]), - CodemodInfo( - paths: dartPaths, - sequence: [ImportRenamer(oldPackageName: 'react_material_ui', newPackageName: 'unify_ui')]) + CodemodInfo(paths: dartPaths, sequence: [NamespaceUsageUpdater()]), + // CodemodInfo(paths: dartPaths, sequence: [PackageRenameComponentUsageMigrator()]), + // CodemodInfo( + // paths: dartPaths, + // sequence: importsToUpdate.where((import) => import.namespace != null).map((import) => + // importerSuggestorBuilder(importUri: import.uri, importNamespace: import.namespace!))), + // CodemodInfo( + // paths: dartPaths, + // sequence: [unusedImportRemoverSuggestorBuilder(packageName: 'react_material_ui')]), + // CodemodInfo( + // paths: dartPaths, + // sequence: [ImportRenamer(oldPackageName: 'react_material_ui', newPackageName: 'unify_ui')]) ]); if (exitCode != 0) return; } diff --git a/lib/src/unify_package_rename_suggestors/namespace_usage_updater.dart b/lib/src/unify_package_rename_suggestors/namespace_usage_updater.dart new file mode 100644 index 00000000..6a1dfda0 --- /dev/null +++ b/lib/src/unify_package_rename_suggestors/namespace_usage_updater.dart @@ -0,0 +1,68 @@ +// Copyright 2023 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:codemod/codemod.dart'; +import 'package:collection/collection.dart'; +import 'package:logging/logging.dart'; + +import '../util.dart'; +import '../util/class_suggestor.dart'; +import '../util/element_type_helpers.dart'; +import 'constants.dart'; + +final _log = Logger('NamespaceUsageVisitor'); + +// todo update comment + +/// Suggestor that adds a [scriptToAdd] line after the last usage of a +/// react-dart script in a Dart string literal or list of string literals. +/// +/// Meant to be run on Dart files (use [HtmlScriptAdder] to run on HTML files). +class NamespaceUsageUpdater extends GeneralizingAstVisitor with ClassSuggestor { + NamespaceUsageUpdater(); + + @override + void visitPrefixedIdentifier(PrefixedIdentifier node) { + super.visitPrefixedIdentifier(node); + + // Replace 'mui' namespaces usage with 'unify'. + final uri = node.identifier.staticElement?.source?.uri; + if (uri != null && isUriWithinPackage(uri, 'react_material_ui')) { + final importNamespace = node.prefix; + final newImportNamespace = importsToUpdate + .where((import) => import.possibleMuiNamespaces?.contains(importNamespace.name) ?? false) + .singleOrNull + ?.namespace; + if (newImportNamespace != null) { + yieldPatch(newImportNamespace, importNamespace.offset, importNamespace.end); + } + } + } + + @override + Future generatePatches() async { + _log.info('Resolving ${context.relativePath}...'); + + final result = await context.getResolvedUnit(); + if (result == null) { + throw Exception('Could not get resolved result for "${context.relativePath}"'); + } + result.unit.accept(this); + } + + @override + bool shouldSkip(FileContext context) => hasParseErrors(context.sourceText); +} diff --git a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart index 292c6df4..fcbd9b6e 100644 --- a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart +++ b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart @@ -76,9 +76,9 @@ class PackageRenameComponentUsageMigrator extends ComponentUsageMigrator { } // Replace 'mui' namespaces usage with 'unify'. - if (importNamespace != null && newImportNamespace != null && !isFromWsdEntrypoint) { - yieldPatch(newImportNamespace, importNamespace.offset, importNamespace.end); - } + // if (importNamespace != null && newImportNamespace != null && !isFromWsdEntrypoint) { + // yieldPatch(newImportNamespace, importNamespace.offset, importNamespace.end); + // } // Add comments for components that need manual verification. if (identifier?.name == 'Badge' || identifier?.name == 'LinearProgress') { diff --git a/test/unify_package_rename_suggestors/namespace_usage_updater_test.dart b/test/unify_package_rename_suggestors/namespace_usage_updater_test.dart new file mode 100644 index 00000000..9dffbe46 --- /dev/null +++ b/test/unify_package_rename_suggestors/namespace_usage_updater_test.dart @@ -0,0 +1,203 @@ +// Copyright 2023 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:over_react_codemod/src/unify_package_rename_suggestors/namespace_usage_updater.dart'; +import 'package:test/test.dart'; + +import '../resolved_file_context.dart'; +import '../util.dart'; + +// todo also test builders +// todo also test non-component usage of the namespace + +void main() { + final resolvedContext = SharedAnalysisContext.rmui; + + // Warm up analysis in a setUpAll so that if getting the resolved AST times out + // (which is more common for the WSD context), it fails here instead of failing the first test. + setUpAll(resolvedContext.warmUpAnalysis); + + group('PackageRenameComponentUsageMigrator', () { + final testSuggestor = getSuggestorTester( + NamespaceUsageUpdater(), + resolvedContext: resolvedContext, + ); + + group('namespace on component usage', () { + test('mui namespace from react_material_ui is migrated to unify', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + + content() { + mui.Button()(); + mui.Checkbox()(); + mui.ButtonColor.success; + mui.useTheme(); + mui.UnifyIcons.expandMore()(); + mui.Button; + mui.Button(); + } +''', + expectedOutput: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + + content() { + unify.Button()(); + unify.Checkbox()(); + unify.ButtonColor.success; + unify.useTheme(); + unify.UnifyIcons.expandMore()(); + unify.Button; + unify.Button(); + } +''', + ); + }); + + test('alpha namespace from react_material_ui is migrated to unify', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_mui; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; + + content() { + alpha_mui.Rating()(); + mui_alpha.Rating()(); + alpha_mui.TimelinePosition.left; + alpha_mui.useGridApiRef(); + alpha_mui.Popper; + alpha_mui.Popper(); + mui_alpha.TimelinePosition.left; + mui_alpha.useGridApiRef(); + mui_alpha.Popper; + mui_alpha.Popper(); + } +''', + expectedOutput: /*language=dart*/ ''' + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_mui; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; + + content() { + alpha_unify.Rating()(); + unify_alpha.Rating()(); + alpha_unify.TimelinePosition.left; + alpha_unify.useGridApiRef(); + alpha_unify.Popper; + alpha_unify.Popper(); + unify_alpha.TimelinePosition.left; + unify_alpha.useGridApiRef(); + unify_alpha.Popper; + unify_alpha.Popper(); + } +''', + ); + }); + + test('nested component implementations', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:over_react/over_react.dart'; + + content() { + Fragment()(mui.ButtonToolbar()( + (mui.Button()..size = mui.ButtonSize.small)('Foo'), + (mui.Button() + ..size = mui.ButtonSize.small + ..color = mui.ButtonColor.primary + )( + 'Bar', + ), + )); + + return (mui.Autocomplete() + ..componentsProps = { + 'popper': mui.Popper()..placement = 'top-end', + 'popupIndicator': mui.IconButton()..sx = {'width': '20px'}, + } + ..renderInput = mui.wrapRenderInput((textFieldProps) => (mui.TextField() + ..addProps(textFieldProps) + ..InputLabelProps = (mui.InputLabel() + ..shrink = false) + )()) + ); + } +''', + expectedOutput: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + + content() { + unify.ButtonToolbar()( + (unify.Button()..size = unify.ButtonSize.small)('Foo'), + (unify.Button() + ..size = unify.ButtonSize.small + ..color = unify.ButtonColor.primary + )( + 'Bar', + ), + ); + } +''', + ); + }); + + test('mui namespace from a different package is not migrated', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:over_react/over_react.dart' as mui; + import 'package:over_react/over_react.dart' as alpha_mui; + import 'package:over_react/over_react.dart' as mui_alpha; + + content() { + mui.Fragment()(); + alpha_mui.Fragment()(); + mui_alpha.Fragment()(); + } +''', + ); + }); + + test('non-mui namespace on a react_material_ui import', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as abc; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as other; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as something; + + content() { + abc.Button()(); + abc.Checkbox()(); + other.Rating; + something.Rating(); + } +''', + ); + }); + + test('no namespace on a react_material_ui import', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart'; + + content() { + Button()(); + Checkbox()(); + } +''', + ); + }); + }); + }); +} diff --git a/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart b/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart index c06fb7d4..8d8fa369 100644 --- a/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart +++ b/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart @@ -34,79 +34,6 @@ void main() { resolvedContext: resolvedContext, ); - group('namespace on component usage', () { - test('mui namespace from react_material_ui is migrated to unify', () async { - await testSuggestor( - input: /*language=dart*/ ''' - import 'package:react_material_ui/react_material_ui.dart' as mui; - import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_mui; - import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; - - content() { - mui.Button()(); - mui.Checkbox()(); - alpha_mui.Rating()(); - mui_alpha.Rating()(); - } -''', - expectedOutput: /*language=dart*/ ''' - import 'package:react_material_ui/react_material_ui.dart' as mui; - import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_mui; - import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; - - content() { - unify.Button()(); - unify.Checkbox()(); - alpha_unify.Rating()(); - unify_alpha.Rating()(); - } -''', - ); - }); - - test('mui namespace from a different package is not migrated', () async { - await testSuggestor( - input: /*language=dart*/ ''' - import 'package:over_react/over_react.dart' as mui; - import 'package:over_react/over_react.dart' as alpha_mui; - import 'package:over_react/over_react.dart' as mui_alpha; - - content() { - mui.Fragment()(); - alpha_mui.Fragment()(); - mui_alpha.Fragment()(); - } -''', - ); - }); - - test('non-mui namespace on a react_material_ui import', () async { - await testSuggestor( - input: /*language=dart*/ ''' - import 'package:react_material_ui/react_material_ui.dart' as abc; - - content() { - abc.Button()(); - abc.Checkbox()(); - } -''', - ); - }); - - test('no namespace on a react_material_ui import', () async { - await testSuggestor( - input: /*language=dart*/ ''' - import 'package:react_material_ui/react_material_ui.dart'; - - content() { - Button()(); - Checkbox()(); - } -''', - ); - }); - }); - group('rename components', () { test('from react_material_ui to unify equivalents', () async { await testSuggestor( @@ -142,7 +69,7 @@ void main() { unify_wsd.WsdLinkButton()(); unify_wsd.WsdLinkButton()(); unify_wsd.WsdLinkButton()(); - unify.UnifyList()(); + mui.UnifyList()(); UnifyList()(); random_rmui_namespace.UnifyList()(); UnifyThemeProvider()(); @@ -193,11 +120,11 @@ void main() { content() { // FIXME(unify_package_rename) Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart. - unify.Badge()(); + mui.Badge()(); // FIXME(unify_package_rename) Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart. Badge()(); // FIXME(unify_package_rename) Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart. - unify.LinearProgress()(); + mui.LinearProgress()(); // FIXME(unify_package_rename) Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart. LinearProgress()(); } From 026c209941fffbfcc96c363daa6e7742e44654a9 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Tue, 10 Oct 2023 10:52:46 -0700 Subject: [PATCH 08/29] Update importer suggestor --- lib/src/executables/unify_package_rename.dart | 13 +- .../constants.dart | 4 +- .../import_renamer.dart | 176 +++++++++++++++--- .../namespace_usage_updater.dart | 65 ++++++- ...ckage_rename_component_usage_migrator.dart | 2 +- lib/src/util/importer.dart | 4 +- .../import_renamer_test.dart | 17 +- .../namespace_usage_updater_test.dart | 37 +++- 8 files changed, 269 insertions(+), 49 deletions(-) diff --git a/lib/src/executables/unify_package_rename.dart b/lib/src/executables/unify_package_rename.dart index bb792950..0875702e 100644 --- a/lib/src/executables/unify_package_rename.dart +++ b/lib/src/executables/unify_package_rename.dart @@ -74,7 +74,7 @@ void main(List args) async { aggregate( [ // todo update version: - PubspecUpgrader('unify_ui', parseVersionRange('^1.89.1'), + PubspecUpgrader('unify_ui', parseVersionRange('^1.121.0'), hostedUrl: 'https://pub.workiva.org', shouldAddDependencies: true), ].map((s) => ignoreable(s)), ) @@ -107,12 +107,17 @@ void main(List args) async { // paths: dartPaths, // sequence: importsToUpdate.where((import) => import.namespace != null).map((import) => // importerSuggestorBuilder(importUri: import.uri, importNamespace: import.namespace!))), + CodemodInfo(paths: dartPaths, sequence: [ + importRenamerSuggestorBuilder( + oldPackageName: 'react_material_ui', + newPackageName: 'unify_ui', + oldPackageNamespace: 'mui', + newPackageNamespace: 'unify', + ) + ]), // CodemodInfo( // paths: dartPaths, // sequence: [unusedImportRemoverSuggestorBuilder(packageName: 'react_material_ui')]), - // CodemodInfo( - // paths: dartPaths, - // sequence: [ImportRenamer(oldPackageName: 'react_material_ui', newPackageName: 'unify_ui')]) ]); if (exitCode != 0) return; } diff --git a/lib/src/unify_package_rename_suggestors/constants.dart b/lib/src/unify_package_rename_suggestors/constants.dart index 6f0cee34..99eac96e 100644 --- a/lib/src/unify_package_rename_suggestors/constants.dart +++ b/lib/src/unify_package_rename_suggestors/constants.dart @@ -25,12 +25,12 @@ class UnifyImportInfo { /// /// Only adds namespace information if the import is commonly used with a namespace. /// Only adds RMUI uri information if it is different from a simple package name swap. -final importsToUpdate = [ +final rmuiImportsToUpdate = [ UnifyImportInfo( 'package:unify_ui/unify_ui.dart', rmuiUri: 'package:react_material_ui/react_material_ui.dart', namespace: 'unify', - possibleMuiNamespaces: ['mui'], + possibleMuiNamespaces: ['mui', 'rmui'], ), UnifyImportInfo( 'package:unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart', diff --git a/lib/src/unify_package_rename_suggestors/import_renamer.dart b/lib/src/unify_package_rename_suggestors/import_renamer.dart index ddb445c0..0984feb2 100644 --- a/lib/src/unify_package_rename_suggestors/import_renamer.dart +++ b/lib/src/unify_package_rename_suggestors/import_renamer.dart @@ -16,36 +16,168 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:codemod/codemod.dart'; import 'package:analyzer/dart/ast/visitor.dart'; +import '../util/importer.dart'; import 'constants.dart'; /// Suggestor that updates imports from [oldPackageName] to [newPackageName]. -class ImportRenamer extends RecursiveAstVisitor with AstVisitingSuggestor { - final String oldPackageName; - final String newPackageName; +// class ImportRenamer extends RecursiveAstVisitor with AstVisitingSuggestor { +// final String oldPackageName; +// final String newPackageName; +// final String oldPackageNamespace; +// final String newPackageNamespace; +// +// ImportRenamer( +// {required this.oldPackageName, +// required this.newPackageName, +// required this.oldPackageNamespace, +// required this.newPackageNamespace}); +// +// @override +// Future visitImportDirective(ImportDirective node) async { +// super.visitImportDirective(node); +// +// final importUri = node.uri.stringValue; +// final namespace = node.prefix; +// if (importUri != null && importUri.startsWith('package:$oldPackageName/')) { +// final specialCaseRmuiImport = +// rmuiImportsToUpdate.where((import) => importUri == import.rmuiUri); +// if (specialCaseRmuiImport.isNotEmpty) { +// yieldPatch('\'${specialCaseRmuiImport.single.uri}\'', node.uri.offset, node.uri.end); +// +// final newNamespace = specialCaseRmuiImport.single.namespace; +// if (namespace != null && +// newNamespace != null && +// (specialCaseRmuiImport.single.possibleMuiNamespaces?.contains(namespace.name) ?? +// false)) { +// yieldPatch(newNamespace, namespace.offset, namespace.end); +// } +// } else { +// yieldPatch( +// '\'${importUri.replaceFirst('package:$oldPackageName/', 'package:$newPackageName/')}\'', +// node.uri.offset, +// node.uri.end); +// } +// +// // Update the namespace if necessary. +// if (namespace != null && namespace.name == oldPackageNamespace) { +// yieldPatch(newPackageNamespace, namespace.offset, namespace.end); +// } +// +// // final libraryResult = await context.getResolvedLibrary(); +// // if (libraryResult == null) { +// // // Most likely a part and not a library. +// // return; +// // } +// // // Parts that have not been generated can show up as `exists = false` but also `isPart = false`, +// // // so using the unitResults is a little trickier than using the libraryElement to get it. +// // final mainLibraryUnitResult = libraryResult.units.singleWhere((unitResult) => +// // unitResult.unit.declaredElement == libraryResult.element.definingCompilationUnit); +// // final insertInfo = insertionLocationForPackageImport( +// // importUri, mainLibraryUnitResult.unit, mainLibraryUnitResult.lineInfo); +// // yield Patch( +// // insertInfo.leadingNewlines + +// // "import '$importUri' as $importNamespace;" + +// // insertInfo.trailingNewlines, +// // insertInfo.offset, +// // insertInfo.offset); +// } +// } +// } + +Suggestor importRenamerSuggestorBuilder({ + required String oldPackageName, + required String newPackageName, + required String oldPackageNamespace, + required String newPackageNamespace, +}) { + return (context) async* { + final libraryResult = await context.getResolvedLibrary(); + if (libraryResult == null) { + // Most likely a part and not a library. + return; + } - ImportRenamer({required this.oldPackageName, required this.newPackageName}); + // Parts that have not been generated can show up as `exists = false` but also `isPart = false`, + // so using the unitResults is a little trickier than using the libraryElement to get it. + final mainLibraryUnitResult = libraryResult.units.singleWhere((unitResult) => + unitResult.unit.declaredElement == libraryResult.element.definingCompilationUnit); - @override - void visitImportDirective(ImportDirective node) { - super.visitImportDirective(node); + // Look for imports with old package name. + final importsToUpdate = mainLibraryUnitResult.unit.directives + .whereType() + .where((import) => import.uri.stringValue?.startsWith('package:$oldPackageName/') ?? false); - final importUri = node.uri.stringValue; - if (importUri != null && importUri.startsWith('package:$oldPackageName/')) { - final specialCaseRmuiImport = importsToUpdate.where((import) => importUri == import.rmuiUri); + for (final import in importsToUpdate) { + final importUri = import.uri.stringValue; + final namespace = import.prefix?.name; + var newImportUri = + importUri?.replaceFirst('package:$oldPackageName/', 'package:$newPackageName/'); + var newNamespace = namespace == oldPackageNamespace ? newPackageNamespace : namespace; + final specialCaseRmuiImport = rmuiImportsToUpdate.where((i) => importUri == i.rmuiUri); if (specialCaseRmuiImport.isNotEmpty) { - if (specialCaseRmuiImport.single.possibleMuiNamespaces?.contains(node.prefix?.name) ?? - false) { - // Do nothing if the import prefix is the old RMUI prefix - a new import with a new - // namespace should have already been added by [importerSuggestorBuilder] in `unify_package_rename.dart`. - return; + newImportUri = specialCaseRmuiImport.single.uri; + + final specialCaseNamespace = specialCaseRmuiImport.single.namespace; + if (namespace != null && + specialCaseNamespace != null && + (specialCaseRmuiImport.single.possibleMuiNamespaces?.contains(namespace) ?? false)) { + newNamespace = specialCaseNamespace; } - yieldPatch('\'${specialCaseRmuiImport.single.uri}\'', node.uri.offset, node.uri.end); - } else { - yieldPatch( - '\'${importUri.replaceFirst('package:$oldPackageName/', 'package:$newPackageName/')}\'', - node.uri.offset, - node.uri.end); } + + if (newImportUri != null) { + final insertInfo = insertionLocationForPackageImport( + newImportUri, mainLibraryUnitResult.unit, mainLibraryUnitResult.lineInfo); + yield Patch( + insertInfo.leadingNewlines + + "import '$newImportUri'${newNamespace != null ? ' as $newNamespace' : ''};" + + insertInfo.trailingNewlines, + insertInfo.offset, + insertInfo.offset); + } + + final prevTokenEnd = import.beginToken.previous?.end; + // Try to take the newline before the import, but watch out + // for prevToken's offset/end being -1 if it's this import has the + // first token in the file. + final startOffset = prevTokenEnd != null && prevTokenEnd != -1 ? prevTokenEnd : import.offset; + yield Patch('', startOffset, import.end); } - } + + // for(final import in importsToUpdate) { + // + // } + // if (importUri != null && importUri.startsWith('package:$oldPackageName/')) { + // if (specialCaseRmuiImport.isNotEmpty) { + // yieldPatch('\'${specialCaseRmuiImport.single.uri}\'', node.uri.offset, node.uri.end); + // + // final newNamespace = specialCaseRmuiImport.single.namespace; + // if (namespace != null && + // newNamespace != null && + // (specialCaseRmuiImport.single.possibleMuiNamespaces?.contains(namespace.name) ?? + // false)) { + // yieldPatch(newNamespace, namespace.offset, namespace.end); + // } + // } else { + // yieldPatch( + // '\'${importUri.replaceFirst('package:$oldPackageName/', 'package:$newPackageName/')}\'', + // node.uri.offset, + // node.uri.end); + // } + // + // // Update the namespace if necessary. + // if (namespace != null && namespace.name == oldPackageNamespace) { + // yieldPatch(newPackageNamespace, namespace.offset, namespace.end); + // } + + // Look for errors in the main compilation unit and its part files. + // Ignore null partContexts and partContexts elements caused by + // resolution issues and parts being excluded in the codemod file list. + // final needsMuiImport = libraryResult.units + // .expand((unitResult) => unitResult.errors) + // .where((error) => error.errorCode.name == 'UNDEFINED_IDENTIFIER') + // .any((error) => error.message.contains("Undefined name '$importNamespace'")); + // + // if (!needsMuiImport) return; + }; } diff --git a/lib/src/unify_package_rename_suggestors/namespace_usage_updater.dart b/lib/src/unify_package_rename_suggestors/namespace_usage_updater.dart index 6a1dfda0..a9c05428 100644 --- a/lib/src/unify_package_rename_suggestors/namespace_usage_updater.dart +++ b/lib/src/unify_package_rename_suggestors/namespace_usage_updater.dart @@ -27,6 +27,36 @@ final _log = Logger('NamespaceUsageVisitor'); // todo update comment +// Suggestor namespaceUsageUpdater() { +// return (context) async* { +// final unitResult = await context.getResolvedUnit(); +// if (unitResult == null) { +// // Most likely a part and not a library. +// return; +// } +// final unusedImportErrors = unitResult.errors +// .where((error) => error.errorCode.name.toLowerCase() == 'unused_import') +// .toList(); +// +// final allImports = unitResult.unit.directives.whereType().toList(); +// +// for (final error in unusedImportErrors) { +// final matchingImport = +// allImports.singleWhere((import) => import.containsOffset(error.offset)); +// final importUri = matchingImport.uriContent; +// if (importUri != null && importUri.startsWith('package:$packageName/')) { +// final prevTokenEnd = matchingImport.beginToken.previous?.end; +// // Try to take the newline before the import, but watch out +// // for prevToken's offset/end being -1 if it's this import has the +// // first token in the file. +// final startOffset = +// prevTokenEnd != null && prevTokenEnd != -1 ? prevTokenEnd : matchingImport.offset; +// yield Patch('', startOffset, matchingImport.end); +// } +// } +// }; +// } + /// Suggestor that adds a [scriptToAdd] line after the last usage of a /// react-dart script in a Dart string literal or list of string literals. /// @@ -35,15 +65,14 @@ class NamespaceUsageUpdater extends GeneralizingAstVisitor with ClassSuggestor { NamespaceUsageUpdater(); @override - void visitPrefixedIdentifier(PrefixedIdentifier node) { - super.visitPrefixedIdentifier(node); + visitMethodInvocation(MethodInvocation node) { + super.visitMethodInvocation(node); - // Replace 'mui' namespaces usage with 'unify'. - final uri = node.identifier.staticElement?.source?.uri; - if (uri != null && isUriWithinPackage(uri, 'react_material_ui')) { - final importNamespace = node.prefix; - final newImportNamespace = importsToUpdate - .where((import) => import.possibleMuiNamespaces?.contains(importNamespace.name) ?? false) + final importNamespace = node.target; + if (importNamespace != null) { + final newImportNamespace = rmuiImportsToUpdate + .where((import) => + import.possibleMuiNamespaces?.contains(importNamespace.toSource()) ?? false) .singleOrNull ?.namespace; if (newImportNamespace != null) { @@ -52,6 +81,24 @@ class NamespaceUsageUpdater extends GeneralizingAstVisitor with ClassSuggestor { } } + @override + void visitPrefixedIdentifier(PrefixedIdentifier node) { + super.visitPrefixedIdentifier(node); + + // Replace 'mui' namespaces usage with 'unify'. + final uri = node.identifier.staticElement?.source?.uri; + // if (uri != null && isUriWithinPackage(uri, 'react_material_ui')) { + final importNamespace = node.prefix; + final newImportNamespace = rmuiImportsToUpdate + .where((import) => import.possibleMuiNamespaces?.contains(importNamespace.name) ?? false) + .singleOrNull + ?.namespace; + if (newImportNamespace != null) { + yieldPatch(newImportNamespace, importNamespace.offset, importNamespace.end); + } + // } + } + @override Future generatePatches() async { _log.info('Resolving ${context.relativePath}...'); @@ -60,7 +107,7 @@ class NamespaceUsageUpdater extends GeneralizingAstVisitor with ClassSuggestor { if (result == null) { throw Exception('Could not get resolved result for "${context.relativePath}"'); } - result.unit.accept(this); + result.unit.visitChildren(this); } @override diff --git a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart index fcbd9b6e..c12afe1f 100644 --- a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart +++ b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart @@ -51,7 +51,7 @@ class PackageRenameComponentUsageMigrator extends ComponentUsageMigrator { if (factoryElement.isDeclaredInPackage('react_material_ui')) { // Save the import namespace to later replace with a unify version. final importNamespace = usage.factory.tryCast()?.prefix; - final newImportNamespace = importsToUpdate + final newImportNamespace = rmuiImportsToUpdate .where((import) => importNamespace?.name != null && (import.possibleMuiNamespaces?.contains(importNamespace?.name) ?? false)) diff --git a/lib/src/util/importer.dart b/lib/src/util/importer.dart index 7c3eaf55..a8293363 100644 --- a/lib/src/util/importer.dart +++ b/lib/src/util/importer.dart @@ -48,7 +48,7 @@ Suggestor importerSuggestorBuilder({ if (!needsMuiImport) return; - final insertInfo = _insertionLocationForPackageImport( + final insertInfo = insertionLocationForPackageImport( importUri, mainLibraryUnitResult.unit, mainLibraryUnitResult.lineInfo); yield Patch( insertInfo.leadingNewlines + @@ -79,7 +79,7 @@ class _InsertionLocation { /// insert it alongside other `package:` imports in alphabetical order, /// otherwise inserting it in a new section relative to other imports /// or other directives. -_InsertionLocation _insertionLocationForPackageImport( +_InsertionLocation insertionLocationForPackageImport( String importUri, CompilationUnit unit, LineInfo lineInfo) { final imports = unit.directives.whereType(); final firstImport = imports.firstOrNull; diff --git a/test/unify_package_rename_suggestors/import_renamer_test.dart b/test/unify_package_rename_suggestors/import_renamer_test.dart index d4d8abfb..280b757b 100644 --- a/test/unify_package_rename_suggestors/import_renamer_test.dart +++ b/test/unify_package_rename_suggestors/import_renamer_test.dart @@ -17,10 +17,17 @@ import 'package:test/test.dart'; import '../util.dart'; +// todo add import order tests + void main() { group('importRenamer', () { final testSuggestor = getSuggestorTester( - ImportRenamer(oldPackageName: 'react_material_ui', newPackageName: 'unify_ui'), + importRenamerSuggestorBuilder( + oldPackageName: 'react_material_ui', + newPackageName: 'unify_ui', + oldPackageNamespace: 'mui', + newPackageNamespace: 'unify', + ), ); test('does nothing when there are no imports', () async { @@ -106,7 +113,7 @@ void main() { import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_mui; import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; import 'package:over_react/over_react.dart' as mui; - import 'package:unify_ui/components/badge.dart' as mui; + import 'package:unify_ui/components/badge.dart' as unify; content() => Dom.div()(); ''', @@ -131,7 +138,11 @@ void main() { test('also works for other package name inputs', () async { final testSuggestor = getSuggestorTester( - ImportRenamer(oldPackageName: 'test_old', newPackageName: 'test_new'), + ImportRenamer( + oldPackageName: 'test_old', + newPackageName: 'test_new', + oldPackageNamespace: 'old', + newPackageNamespace: 'new'), ); await testSuggestor( input: /*language=dart*/ ''' diff --git a/test/unify_package_rename_suggestors/namespace_usage_updater_test.dart b/test/unify_package_rename_suggestors/namespace_usage_updater_test.dart index 9dffbe46..3a2286f3 100644 --- a/test/unify_package_rename_suggestors/namespace_usage_updater_test.dart +++ b/test/unify_package_rename_suggestors/namespace_usage_updater_test.dart @@ -39,6 +39,7 @@ void main() { await testSuggestor( input: /*language=dart*/ ''' import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/styles/color_utils.dart' as mui; content() { mui.Button()(); @@ -48,10 +49,12 @@ void main() { mui.UnifyIcons.expandMore()(); mui.Button; mui.Button(); + mui.darken('abc', 1); } ''', expectedOutput: /*language=dart*/ ''' import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/styles/color_utils.dart' as mui; content() { unify.Button()(); @@ -61,6 +64,7 @@ void main() { unify.UnifyIcons.expandMore()(); unify.Button; unify.Button(); + unify.darken('abc', 1); } ''', ); @@ -91,15 +95,15 @@ void main() { content() { alpha_unify.Rating()(); - unify_alpha.Rating()(); + alpha_unify.Rating()(); + alpha_unify.TimelinePosition.left; + alpha_unify.useGridApiRef(); + alpha_unify.Popper; + alpha_unify.Popper(); alpha_unify.TimelinePosition.left; alpha_unify.useGridApiRef(); alpha_unify.Popper; alpha_unify.Popper(); - unify_alpha.TimelinePosition.left; - unify_alpha.useGridApiRef(); - unify_alpha.Popper; - unify_alpha.Popper(); } ''', ); @@ -127,6 +131,10 @@ void main() { 'popper': mui.Popper()..placement = 'top-end', 'popupIndicator': mui.IconButton()..sx = {'width': '20px'}, } + ..sx = { + 'color': (mui.Theme theme) => + mui.getThemePalette(theme).common.white + } ..renderInput = mui.wrapRenderInput((textFieldProps) => (mui.TextField() ..addProps(textFieldProps) ..InputLabelProps = (mui.InputLabel() @@ -137,9 +145,10 @@ void main() { ''', expectedOutput: /*language=dart*/ ''' import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:over_react/over_react.dart'; content() { - unify.ButtonToolbar()( + Fragment()(unify.ButtonToolbar()( (unify.Button()..size = unify.ButtonSize.small)('Foo'), (unify.Button() ..size = unify.ButtonSize.small @@ -147,6 +156,22 @@ void main() { )( 'Bar', ), + )); + + return (unify.Autocomplete() + ..componentsProps = { + 'popper': unify.Popper()..placement = 'top-end', + 'popupIndicator': unify.IconButton()..sx = {'width': '20px'}, + } + ..sx = { + 'color': (unify.Theme theme) => + unify.getThemePalette(theme).common.white + } + ..renderInput = unify.wrapRenderInput((textFieldProps) => (unify.TextField() + ..addProps(textFieldProps) + ..InputLabelProps = (unify.InputLabel() + ..shrink = false) + )()) ); } ''', From a7e86ec20f08b71e9a8507195709fd15b2ebc9e9 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Tue, 10 Oct 2023 17:15:13 -0700 Subject: [PATCH 09/29] Add object renamer logic --- lib/src/executables/unify_package_rename.dart | 10 ++- .../constants.dart | 60 ++++++++++++-- .../namespace_usage_updater.dart | 81 ++++++++++++++++--- ...ckage_rename_component_usage_migrator.dart | 2 +- .../import_renamer_test.dart | 11 +-- .../namespace_usage_updater_test.dart | 15 ++-- 6 files changed, 147 insertions(+), 32 deletions(-) diff --git a/lib/src/executables/unify_package_rename.dart b/lib/src/executables/unify_package_rename.dart index 0875702e..324c859e 100644 --- a/lib/src/executables/unify_package_rename.dart +++ b/lib/src/executables/unify_package_rename.dart @@ -103,10 +103,12 @@ void main(List args) async { // todo add comments CodemodInfo(paths: dartPaths, sequence: [NamespaceUsageUpdater()]), // CodemodInfo(paths: dartPaths, sequence: [PackageRenameComponentUsageMigrator()]), - // CodemodInfo( - // paths: dartPaths, - // sequence: importsToUpdate.where((import) => import.namespace != null).map((import) => - // importerSuggestorBuilder(importUri: import.uri, importNamespace: import.namespace!))), + CodemodInfo(paths: dartPaths, sequence: [ + importerSuggestorBuilder( + importUri: unifyWsdUri, + importNamespace: unifyWsdNamespace, + ) + ]), CodemodInfo(paths: dartPaths, sequence: [ importRenamerSuggestorBuilder( oldPackageName: 'react_material_ui', diff --git a/lib/src/unify_package_rename_suggestors/constants.dart b/lib/src/unify_package_rename_suggestors/constants.dart index 99eac96e..2c7013f8 100644 --- a/lib/src/unify_package_rename_suggestors/constants.dart +++ b/lib/src/unify_package_rename_suggestors/constants.dart @@ -39,10 +39,6 @@ final rmuiImportsToUpdate = [ namespace: 'alpha_unify', possibleMuiNamespaces: ['alpha_mui', 'mui_alpha'], ), - UnifyImportInfo( - 'package:unify_ui/components/wsd.dart', - namespace: unifyWsdNamespace, - ), UnifyImportInfo( 'package:unify_ui/components/list.dart', rmuiUri: 'package:react_material_ui/components/mui_list.dart', @@ -54,12 +50,66 @@ final rmuiImportsToUpdate = [ ]; /// A map of RMUI component names to their new names in unify_ui. -const rmuiToUnifyComponentNames = { +/// +/// This is based on the list of changes in the migration guide: https://github.com/Workiva/react_material_ui/tree/master/react_material_ui#how-to-migrate-from-reactmaterialui-to-unifyui +const rmuiToUnifyIdentifierRenames = { + // Components 'Alert': 'WsdAlert', 'LinkButton': 'WsdLinkButton', 'MuiList': 'UnifyList', 'WorkivaMuiThemeProvider': 'UnifyThemeProvider', + // Autocomplete objects + 'AutocompleteFilterOptionsObject': 'AutocompleteFilterOptionsState', + 'AutocompleteOnChangeObject': 'AutocompleteChangeDetails', + 'AutocompleteRenderOptionObject': 'AutocompleteRenderOptionState', + // Backdrop objects + 'BackdropTimeoutObject': 'BackdropObject', + 'BackdropTransitionDurationObject': 'BackdropObject', + // Badge objects + 'BadgeAnchorOriginObject': 'BadgeOrigin', + 'BadgeAnchorOriginObjectVertical': 'BadgeOriginVertical', + 'BadgeAnchorOriginObjectHorizontal': 'BadgeOriginHorizontal', + // Breadcrumb objects + 'BreadcrumbNavCrumbsObject': 'BreadcrumbNavBreadcrumbModel', + // CSSTransition objects + 'CSSTransitionClassNamesObject': 'CSSTransitionClassNames', + // DropdownButton objects + 'DropdownButtonOnPlacementUpdate': 'DropdownButtonPlacement', + // Menu objects + 'MenuAnchorOriginObject': 'MenuPopoverOrigin', + 'MenuTransformOriginObject': 'MenuPopoverOrigin', + 'MenuAnchorOriginObjectVertical': 'MenuPopoverOriginVertical', + 'MenuTransformOriginObjectVertical': 'MenuPopoverOriginVertical', + 'MenuAnchorOriginObjectHorizontal': 'MenuPopoverOriginHorizontal', + 'MenuTransformOriginObjectHorizontal': 'MenuPopoverOriginHorizontal', + 'MenuAnchorPositionObject': 'MenuPopoverPosition', + // Popover objects + 'PopoverAnchorOriginObject': 'PopoverOrigin', + 'PopoverTransformOriginObject': 'PopoverOrigin', + 'PopoverAnchorOriginObjectVertical': 'PopoverOriginVertical', + 'PopoverTransformOriginObjectVertical': 'PopoverOriginVertical', + 'PopoverAnchorOriginObjectHorizontal': 'PopoverOriginHorizontal', + 'PopoverTransformOriginObjectHorizontal': 'PopoverOriginHorizontal', + 'PopoverAnchorPositionObject': 'PopoverPosition', + // Popper objects + 'PopperAnchorElObject': 'PopperVirtualElement', + 'PopperModifiersObject': 'PopperModifier', + 'PopperModifiersObjectPhase': 'PopperModifierPhases', + 'PopperPopperOptionsObject': 'PopperOptionsGeneric', + 'PopperPopperOptionsObjectPlacement': 'PopperPlacement', + 'PopperPopperOptionsObjectStrategy': 'PopperPositioningStrategy', + // Slider objects + 'SliderMarksObject': 'SliderMark', + // Snackbar objects + 'SnackbarAnchorOriginObject': 'SnackbarOrigin', + 'SnackbarAnchorOriginObjectVertical': 'SnackbarOriginVertical', + 'SnackbarAnchorOriginObjectHorizontal': 'SnackbarOriginHorizontal', + // TablePagination objects + 'TablePaginationLabelDisplayedRowsObject': 'TablePaginationLabelDisplayedRowsArgs', }; /// The namespace that will be used for the `unify_ui/components/wsd.dart` import that is added. const unifyWsdNamespace = 'unify_wsd'; + +/// The uri for the `unify_ui/components/wsd.dart` import that is added. +const unifyWsdUri = 'package:unify_ui/components/wsd.dart'; diff --git a/lib/src/unify_package_rename_suggestors/namespace_usage_updater.dart b/lib/src/unify_package_rename_suggestors/namespace_usage_updater.dart index a9c05428..b361628c 100644 --- a/lib/src/unify_package_rename_suggestors/namespace_usage_updater.dart +++ b/lib/src/unify_package_rename_suggestors/namespace_usage_updater.dart @@ -68,6 +68,7 @@ class NamespaceUsageUpdater extends GeneralizingAstVisitor with ClassSuggestor { visitMethodInvocation(MethodInvocation node) { super.visitMethodInvocation(node); + // todo update this to also check that it's coming from rmui package final importNamespace = node.target; if (importNamespace != null) { final newImportNamespace = rmuiImportsToUpdate @@ -82,20 +83,82 @@ class NamespaceUsageUpdater extends GeneralizingAstVisitor with ClassSuggestor { } @override - void visitPrefixedIdentifier(PrefixedIdentifier node) { - super.visitPrefixedIdentifier(node); + visitIdentifier(Identifier node) { + super.visitIdentifier(node); - // Replace 'mui' namespaces usage with 'unify'. - final uri = node.identifier.staticElement?.source?.uri; + // Check that the parent isn't a prefixed identifier to avoid conflicts if the parent was already updated. + if (node.parent is PrefixedIdentifier) { + return; + } + + final identifier = + node.tryCast() ?? node.tryCast()?.identifier; + final uri = identifier?.staticElement?.source?.uri; + final namespace = node.tryCast()?.prefix; + + // todo fix this // if (uri != null && isUriWithinPackage(uri, 'react_material_ui')) { - final importNamespace = node.prefix; - final newImportNamespace = rmuiImportsToUpdate - .where((import) => import.possibleMuiNamespaces?.contains(importNamespace.name) ?? false) + // Save the import namespace to later replace with a unify version. + final newNamespace = rmuiImportsToUpdate + .where((import) => import.possibleMuiNamespaces?.contains(namespace?.name) ?? false) .singleOrNull ?.namespace; - if (newImportNamespace != null) { - yieldPatch(newImportNamespace, importNamespace.offset, importNamespace.end); + + // Update components and objects that were renamed in unify_ui. + final newName = rmuiToUnifyIdentifierRenames[identifier?.name]; + final isFromWsdEntrypoint = newName?.startsWith('Wsd') ?? false; + if (identifier != null && newName != null) { + if (isFromWsdEntrypoint) { + // Overwrite or add import namespace for components that will be imported from the separate + // unify_ui/components/wsd.dart entrypoint so we can keep the namespace of the import + // we add consistent with the components that use it. + yieldPatch('$unifyWsdNamespace.$newName', node.offset, node.end); + } else { + yieldPatch(newName, identifier.offset, identifier.end); + } + } + + // Replace 'mui' namespaces usage with 'unify'. + if (namespace != null && newNamespace != null && !isFromWsdEntrypoint) { + yieldPatch(newNamespace, namespace.offset, namespace.end); } + + // Add comments for components that need manual verification. + // if (identifier?.name == 'Badge' || identifier?.name == 'LinearProgress') { + // yieldUsageFixmePatch(usage, + // 'Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart.'); + // } + } + + // Replace 'mui' namespaces usage with 'unify'. + // final uri = node.identifier.staticElement?.source?.uri; + // // if (uri != null && isUriWithinPackage(uri, 'react_material_ui')) { + // final importNamespace = node.prefix; + // final newImportNamespace = rmuiImportsToUpdate + // .where((import) => import.possibleMuiNamespaces?.contains(importNamespace.name) ?? false) + // .singleOrNull + // ?.namespace; + // if (newImportNamespace != null) { + // yieldPatch(newImportNamespace, importNamespace.offset, importNamespace.end); + // } + // } + // } + + @override + void visitPrefixedIdentifier(PrefixedIdentifier node) { + super.visitPrefixedIdentifier(node); + + // Replace 'mui' namespaces usage with 'unify'. + // final uri = node.identifier.staticElement?.source?.uri; + // // if (uri != null && isUriWithinPackage(uri, 'react_material_ui')) { + // final importNamespace = node.prefix; + // final newImportNamespace = rmuiImportsToUpdate + // .where((import) => import.possibleMuiNamespaces?.contains(importNamespace.name) ?? false) + // .singleOrNull + // ?.namespace; + // if (newImportNamespace != null) { + // yieldPatch(newImportNamespace, importNamespace.offset, importNamespace.end); + // } // } } diff --git a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart index c12afe1f..9088d5dc 100644 --- a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart +++ b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart @@ -61,7 +61,7 @@ class PackageRenameComponentUsageMigrator extends ComponentUsageMigrator { // Update components that were renamed in unify_ui. final identifier = usage.factory.tryCast() ?? usage.factory.tryCast()?.identifier; - final newComponentName = rmuiToUnifyComponentNames[identifier?.name]; + final newComponentName = rmuiToUnifyIdentifierRenames[identifier?.name]; final isFromWsdEntrypoint = newComponentName?.startsWith('Wsd') ?? false; if (identifier != null && newComponentName != null) { if (isFromWsdEntrypoint) { diff --git a/test/unify_package_rename_suggestors/import_renamer_test.dart b/test/unify_package_rename_suggestors/import_renamer_test.dart index 280b757b..7d4d927d 100644 --- a/test/unify_package_rename_suggestors/import_renamer_test.dart +++ b/test/unify_package_rename_suggestors/import_renamer_test.dart @@ -138,11 +138,12 @@ void main() { test('also works for other package name inputs', () async { final testSuggestor = getSuggestorTester( - ImportRenamer( - oldPackageName: 'test_old', - newPackageName: 'test_new', - oldPackageNamespace: 'old', - newPackageNamespace: 'new'), + importRenamerSuggestorBuilder( + oldPackageName: 'test_old', + newPackageName: 'test_new', + oldPackageNamespace: 'old', + newPackageNamespace: 'new', + ), ); await testSuggestor( input: /*language=dart*/ ''' diff --git a/test/unify_package_rename_suggestors/namespace_usage_updater_test.dart b/test/unify_package_rename_suggestors/namespace_usage_updater_test.dart index 3a2286f3..fdd2d6d6 100644 --- a/test/unify_package_rename_suggestors/namespace_usage_updater_test.dart +++ b/test/unify_package_rename_suggestors/namespace_usage_updater_test.dart @@ -39,17 +39,16 @@ void main() { await testSuggestor( input: /*language=dart*/ ''' import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/react_material_ui.dart'; import 'package:react_material_ui/styles/color_utils.dart' as mui; content() { - mui.Button()(); - mui.Checkbox()(); - mui.ButtonColor.success; - mui.useTheme(); - mui.UnifyIcons.expandMore()(); - mui.Button; - mui.Button(); - mui.darken('abc', 1); + mui.LinkButton()(); + mui.LinkButton(); + mui.LinkButton; + LinkButton()(); + LinkButton(); + LinkButton; } ''', expectedOutput: /*language=dart*/ ''' From 3b90e49e7df42cd6fb99179c02c86b507d6b8bc9 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Thu, 12 Oct 2023 16:21:51 -0700 Subject: [PATCH 10/29] Add ButtonColor updates for WSD colors --- lib/src/executables/unify_package_rename.dart | 4 +- .../namespace_usage_updater.dart | 178 -------- .../unify_rename_suggestor.dart | 144 ++++++ test/test_fixtures/rmui_project/pubspec.yaml | 2 +- .../namespace_usage_updater_test.dart | 227 ---------- .../unify_rename_suggestor_test.dart | 423 ++++++++++++++++++ 6 files changed, 570 insertions(+), 408 deletions(-) delete mode 100644 lib/src/unify_package_rename_suggestors/namespace_usage_updater.dart create mode 100644 lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart delete mode 100644 test/unify_package_rename_suggestors/namespace_usage_updater_test.dart create mode 100644 test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart diff --git a/lib/src/executables/unify_package_rename.dart b/lib/src/executables/unify_package_rename.dart index 324c859e..c07b3786 100644 --- a/lib/src/executables/unify_package_rename.dart +++ b/lib/src/executables/unify_package_rename.dart @@ -23,7 +23,7 @@ import 'package:over_react_codemod/src/rmui_bundle_update_suggestors/dart_script import 'package:over_react_codemod/src/rmui_bundle_update_suggestors/html_script_updater.dart'; import 'package:over_react_codemod/src/unify_package_rename_suggestors/constants.dart'; import 'package:over_react_codemod/src/unify_package_rename_suggestors/import_renamer.dart'; -import 'package:over_react_codemod/src/unify_package_rename_suggestors/namespace_usage_updater.dart'; +import 'package:over_react_codemod/src/unify_package_rename_suggestors/unify_rename_suggestor.dart'; import 'package:over_react_codemod/src/util.dart'; import 'package:over_react_codemod/src/util/pubspec_upgrader.dart'; @@ -101,7 +101,7 @@ void main(List args) async { await pubGetForAllPackageRoots(dartPaths); exitCode = await runCodemods([ // todo add comments - CodemodInfo(paths: dartPaths, sequence: [NamespaceUsageUpdater()]), + CodemodInfo(paths: dartPaths, sequence: [UnifyRenameSuggestor()]), // CodemodInfo(paths: dartPaths, sequence: [PackageRenameComponentUsageMigrator()]), CodemodInfo(paths: dartPaths, sequence: [ importerSuggestorBuilder( diff --git a/lib/src/unify_package_rename_suggestors/namespace_usage_updater.dart b/lib/src/unify_package_rename_suggestors/namespace_usage_updater.dart deleted file mode 100644 index b361628c..00000000 --- a/lib/src/unify_package_rename_suggestors/namespace_usage_updater.dart +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2023 Workiva Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/dart/ast/visitor.dart'; -import 'package:codemod/codemod.dart'; -import 'package:collection/collection.dart'; -import 'package:logging/logging.dart'; - -import '../util.dart'; -import '../util/class_suggestor.dart'; -import '../util/element_type_helpers.dart'; -import 'constants.dart'; - -final _log = Logger('NamespaceUsageVisitor'); - -// todo update comment - -// Suggestor namespaceUsageUpdater() { -// return (context) async* { -// final unitResult = await context.getResolvedUnit(); -// if (unitResult == null) { -// // Most likely a part and not a library. -// return; -// } -// final unusedImportErrors = unitResult.errors -// .where((error) => error.errorCode.name.toLowerCase() == 'unused_import') -// .toList(); -// -// final allImports = unitResult.unit.directives.whereType().toList(); -// -// for (final error in unusedImportErrors) { -// final matchingImport = -// allImports.singleWhere((import) => import.containsOffset(error.offset)); -// final importUri = matchingImport.uriContent; -// if (importUri != null && importUri.startsWith('package:$packageName/')) { -// final prevTokenEnd = matchingImport.beginToken.previous?.end; -// // Try to take the newline before the import, but watch out -// // for prevToken's offset/end being -1 if it's this import has the -// // first token in the file. -// final startOffset = -// prevTokenEnd != null && prevTokenEnd != -1 ? prevTokenEnd : matchingImport.offset; -// yield Patch('', startOffset, matchingImport.end); -// } -// } -// }; -// } - -/// Suggestor that adds a [scriptToAdd] line after the last usage of a -/// react-dart script in a Dart string literal or list of string literals. -/// -/// Meant to be run on Dart files (use [HtmlScriptAdder] to run on HTML files). -class NamespaceUsageUpdater extends GeneralizingAstVisitor with ClassSuggestor { - NamespaceUsageUpdater(); - - @override - visitMethodInvocation(MethodInvocation node) { - super.visitMethodInvocation(node); - - // todo update this to also check that it's coming from rmui package - final importNamespace = node.target; - if (importNamespace != null) { - final newImportNamespace = rmuiImportsToUpdate - .where((import) => - import.possibleMuiNamespaces?.contains(importNamespace.toSource()) ?? false) - .singleOrNull - ?.namespace; - if (newImportNamespace != null) { - yieldPatch(newImportNamespace, importNamespace.offset, importNamespace.end); - } - } - } - - @override - visitIdentifier(Identifier node) { - super.visitIdentifier(node); - - // Check that the parent isn't a prefixed identifier to avoid conflicts if the parent was already updated. - if (node.parent is PrefixedIdentifier) { - return; - } - - final identifier = - node.tryCast() ?? node.tryCast()?.identifier; - final uri = identifier?.staticElement?.source?.uri; - final namespace = node.tryCast()?.prefix; - - // todo fix this - // if (uri != null && isUriWithinPackage(uri, 'react_material_ui')) { - // Save the import namespace to later replace with a unify version. - final newNamespace = rmuiImportsToUpdate - .where((import) => import.possibleMuiNamespaces?.contains(namespace?.name) ?? false) - .singleOrNull - ?.namespace; - - // Update components and objects that were renamed in unify_ui. - final newName = rmuiToUnifyIdentifierRenames[identifier?.name]; - final isFromWsdEntrypoint = newName?.startsWith('Wsd') ?? false; - if (identifier != null && newName != null) { - if (isFromWsdEntrypoint) { - // Overwrite or add import namespace for components that will be imported from the separate - // unify_ui/components/wsd.dart entrypoint so we can keep the namespace of the import - // we add consistent with the components that use it. - yieldPatch('$unifyWsdNamespace.$newName', node.offset, node.end); - } else { - yieldPatch(newName, identifier.offset, identifier.end); - } - } - - // Replace 'mui' namespaces usage with 'unify'. - if (namespace != null && newNamespace != null && !isFromWsdEntrypoint) { - yieldPatch(newNamespace, namespace.offset, namespace.end); - } - - // Add comments for components that need manual verification. - // if (identifier?.name == 'Badge' || identifier?.name == 'LinearProgress') { - // yieldUsageFixmePatch(usage, - // 'Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart.'); - // } - } - - // Replace 'mui' namespaces usage with 'unify'. - // final uri = node.identifier.staticElement?.source?.uri; - // // if (uri != null && isUriWithinPackage(uri, 'react_material_ui')) { - // final importNamespace = node.prefix; - // final newImportNamespace = rmuiImportsToUpdate - // .where((import) => import.possibleMuiNamespaces?.contains(importNamespace.name) ?? false) - // .singleOrNull - // ?.namespace; - // if (newImportNamespace != null) { - // yieldPatch(newImportNamespace, importNamespace.offset, importNamespace.end); - // } - // } - // } - - @override - void visitPrefixedIdentifier(PrefixedIdentifier node) { - super.visitPrefixedIdentifier(node); - - // Replace 'mui' namespaces usage with 'unify'. - // final uri = node.identifier.staticElement?.source?.uri; - // // if (uri != null && isUriWithinPackage(uri, 'react_material_ui')) { - // final importNamespace = node.prefix; - // final newImportNamespace = rmuiImportsToUpdate - // .where((import) => import.possibleMuiNamespaces?.contains(importNamespace.name) ?? false) - // .singleOrNull - // ?.namespace; - // if (newImportNamespace != null) { - // yieldPatch(newImportNamespace, importNamespace.offset, importNamespace.end); - // } - // } - } - - @override - Future generatePatches() async { - _log.info('Resolving ${context.relativePath}...'); - - final result = await context.getResolvedUnit(); - if (result == null) { - throw Exception('Could not get resolved result for "${context.relativePath}"'); - } - result.unit.visitChildren(this); - } - - @override - bool shouldSkip(FileContext context) => hasParseErrors(context.sourceText); -} diff --git a/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart b/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart new file mode 100644 index 00000000..eada8cc0 --- /dev/null +++ b/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart @@ -0,0 +1,144 @@ +// Copyright 2023 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/visitor.dart'; +import 'package:codemod/codemod.dart'; +import 'package:collection/collection.dart'; +import 'package:logging/logging.dart'; + +import '../util.dart'; +import '../util/class_suggestor.dart'; +import '../util/element_type_helpers.dart'; +import 'constants.dart'; + +final _log = Logger('UnifyRenameSuggestor'); + +/// Suggestor that performs all the updates needed to migrate from the react_material_ui package +/// to the unify_ui package: +/// +/// - Rename specific components and objects +/// - Update WSD ButtonColor usages +/// - Rename import namespaces 'mui' => 'unify' +/// - Add fix me comments for manual checks +/// +/// Also see migration guide: https://github.com/Workiva/react_material_ui/tree/master/react_material_ui#how-to-migrate-from-reactmaterialui-to-unifyui +class UnifyRenameSuggestor extends GeneralizingAstVisitor with ClassSuggestor { + UnifyRenameSuggestor(); + + @override + visitMethodInvocation(MethodInvocation node) { + super.visitMethodInvocation(node); + + // Replace 'mui' namespaces usage with 'unify' for method invocations. + final uri = node.methodName.staticElement?.source?.uri; + if (uri != null && + (isUriWithinPackage(uri, 'react_material_ui') || isUriWithinPackage(uri, 'unify_ui'))) { + final importNamespace = node.target; + if (importNamespace != null) { + final newImportNamespace = rmuiImportsToUpdate + .where((import) => + import.possibleMuiNamespaces?.contains(importNamespace.toSource()) ?? false) + .singleOrNull + ?.namespace; + if (newImportNamespace != null) { + yieldPatch(newImportNamespace, importNamespace.offset, importNamespace.end); + } + } + } + } + + @override + visitIdentifier(Identifier node) { + super.visitIdentifier(node); + + // Check that the parent isn't a prefixed identifier to avoid conflicts if the parent was already updated. + if (node.parent is PrefixedIdentifier) { + return; + } + + final identifier = + node.tryCast() ?? node.tryCast()?.identifier; + final uri = identifier?.staticElement?.source?.uri; + final prefix = node.tryCast()?.prefix; + + if (uri != null && + (isUriWithinPackage(uri, 'react_material_ui') || isUriWithinPackage(uri, 'unify_ui'))) { + // Update components and objects that were renamed in unify_ui. + final newName = rmuiToUnifyIdentifierRenames[identifier?.name]; + var isFromWsdEntrypoint = newName?.startsWith('Wsd') ?? false; + if (identifier != null && newName != null) { + if (isFromWsdEntrypoint) { + // Overwrite or add import namespace for components that will be imported from the separate + // unify_ui/components/wsd.dart entrypoint so we can keep the namespace of the import + // we add consistent with the components that use it. + yieldPatch('$unifyWsdNamespace.$newName', node.offset, node.end); + } else { + yieldPatch(newName, identifier.offset, identifier.end); + } + } + + // Update WSD ButtonColor usages. + { + // Update ButtonColor WSD properties to use the WsdButtonColor object if applicable. + yieldButtonColorPatchIfApplicable( + Expression node, String? objectName, String? propertyName) { + if (objectName == 'ButtonColor' && (propertyName?.startsWith('wsd') ?? false)) { + isFromWsdEntrypoint = true; + yieldPatch('$unifyWsdNamespace.WsdButtonColor.$propertyName', node.offset, node.end); + } + } + + final parent = node.parent; + // Check for non-namespaced `ButtonColor.wsd...` usage. + yieldButtonColorPatchIfApplicable(node, prefix?.name, identifier?.name); + // Check for namespaced `mui.ButtonColor.wsd...` usage. + if (node is PrefixedIdentifier && parent is PropertyAccess) { + yieldButtonColorPatchIfApplicable(parent, identifier?.name, parent.propertyName.name); + } + } + + // Replace 'mui' namespaces usage with 'unify'. + final newNamespace = rmuiImportsToUpdate + .where((import) => import.possibleMuiNamespaces?.contains(prefix?.name) ?? false) + .singleOrNull + ?.namespace; + if (prefix != null && newNamespace != null && !isFromWsdEntrypoint) { + yieldPatch(newNamespace, prefix.offset, prefix.end); + } + + // Add comments for components that need manual verification. + if (identifier?.name == 'Badge' || identifier?.name == 'LinearProgress') { + yieldInsertionPatch( + lineComment( + 'FIXME(unify_package_rename) Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart.'), + node.offset); + } + } + } + + @override + Future generatePatches() async { + _log.info('Resolving ${context.relativePath}...'); + + final result = await context.getResolvedUnit(); + if (result == null) { + throw Exception('Could not get resolved result for "${context.relativePath}"'); + } + result.unit.visitChildren(this); + } + + @override + bool shouldSkip(FileContext context) => hasParseErrors(context.sourceText); +} diff --git a/test/test_fixtures/rmui_project/pubspec.yaml b/test/test_fixtures/rmui_project/pubspec.yaml index aac85bab..761c78b3 100644 --- a/test/test_fixtures/rmui_project/pubspec.yaml +++ b/test/test_fixtures/rmui_project/pubspec.yaml @@ -7,4 +7,4 @@ dependencies: hosted: name: react_material_ui url: https://pub.workiva.org - version: ^1.118.0 + version: ^1.121.0 diff --git a/test/unify_package_rename_suggestors/namespace_usage_updater_test.dart b/test/unify_package_rename_suggestors/namespace_usage_updater_test.dart deleted file mode 100644 index fdd2d6d6..00000000 --- a/test/unify_package_rename_suggestors/namespace_usage_updater_test.dart +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright 2023 Workiva Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import 'package:over_react_codemod/src/unify_package_rename_suggestors/namespace_usage_updater.dart'; -import 'package:test/test.dart'; - -import '../resolved_file_context.dart'; -import '../util.dart'; - -// todo also test builders -// todo also test non-component usage of the namespace - -void main() { - final resolvedContext = SharedAnalysisContext.rmui; - - // Warm up analysis in a setUpAll so that if getting the resolved AST times out - // (which is more common for the WSD context), it fails here instead of failing the first test. - setUpAll(resolvedContext.warmUpAnalysis); - - group('PackageRenameComponentUsageMigrator', () { - final testSuggestor = getSuggestorTester( - NamespaceUsageUpdater(), - resolvedContext: resolvedContext, - ); - - group('namespace on component usage', () { - test('mui namespace from react_material_ui is migrated to unify', () async { - await testSuggestor( - input: /*language=dart*/ ''' - import 'package:react_material_ui/react_material_ui.dart' as mui; - import 'package:react_material_ui/react_material_ui.dart'; - import 'package:react_material_ui/styles/color_utils.dart' as mui; - - content() { - mui.LinkButton()(); - mui.LinkButton(); - mui.LinkButton; - LinkButton()(); - LinkButton(); - LinkButton; - } -''', - expectedOutput: /*language=dart*/ ''' - import 'package:react_material_ui/react_material_ui.dart' as mui; - import 'package:react_material_ui/styles/color_utils.dart' as mui; - - content() { - unify.Button()(); - unify.Checkbox()(); - unify.ButtonColor.success; - unify.useTheme(); - unify.UnifyIcons.expandMore()(); - unify.Button; - unify.Button(); - unify.darken('abc', 1); - } -''', - ); - }); - - test('alpha namespace from react_material_ui is migrated to unify', () async { - await testSuggestor( - input: /*language=dart*/ ''' - import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_mui; - import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; - - content() { - alpha_mui.Rating()(); - mui_alpha.Rating()(); - alpha_mui.TimelinePosition.left; - alpha_mui.useGridApiRef(); - alpha_mui.Popper; - alpha_mui.Popper(); - mui_alpha.TimelinePosition.left; - mui_alpha.useGridApiRef(); - mui_alpha.Popper; - mui_alpha.Popper(); - } -''', - expectedOutput: /*language=dart*/ ''' - import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_mui; - import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; - - content() { - alpha_unify.Rating()(); - alpha_unify.Rating()(); - alpha_unify.TimelinePosition.left; - alpha_unify.useGridApiRef(); - alpha_unify.Popper; - alpha_unify.Popper(); - alpha_unify.TimelinePosition.left; - alpha_unify.useGridApiRef(); - alpha_unify.Popper; - alpha_unify.Popper(); - } -''', - ); - }); - - test('nested component implementations', () async { - await testSuggestor( - input: /*language=dart*/ ''' - import 'package:react_material_ui/react_material_ui.dart' as mui; - import 'package:over_react/over_react.dart'; - - content() { - Fragment()(mui.ButtonToolbar()( - (mui.Button()..size = mui.ButtonSize.small)('Foo'), - (mui.Button() - ..size = mui.ButtonSize.small - ..color = mui.ButtonColor.primary - )( - 'Bar', - ), - )); - - return (mui.Autocomplete() - ..componentsProps = { - 'popper': mui.Popper()..placement = 'top-end', - 'popupIndicator': mui.IconButton()..sx = {'width': '20px'}, - } - ..sx = { - 'color': (mui.Theme theme) => - mui.getThemePalette(theme).common.white - } - ..renderInput = mui.wrapRenderInput((textFieldProps) => (mui.TextField() - ..addProps(textFieldProps) - ..InputLabelProps = (mui.InputLabel() - ..shrink = false) - )()) - ); - } -''', - expectedOutput: /*language=dart*/ ''' - import 'package:react_material_ui/react_material_ui.dart' as mui; - import 'package:over_react/over_react.dart'; - - content() { - Fragment()(unify.ButtonToolbar()( - (unify.Button()..size = unify.ButtonSize.small)('Foo'), - (unify.Button() - ..size = unify.ButtonSize.small - ..color = unify.ButtonColor.primary - )( - 'Bar', - ), - )); - - return (unify.Autocomplete() - ..componentsProps = { - 'popper': unify.Popper()..placement = 'top-end', - 'popupIndicator': unify.IconButton()..sx = {'width': '20px'}, - } - ..sx = { - 'color': (unify.Theme theme) => - unify.getThemePalette(theme).common.white - } - ..renderInput = unify.wrapRenderInput((textFieldProps) => (unify.TextField() - ..addProps(textFieldProps) - ..InputLabelProps = (unify.InputLabel() - ..shrink = false) - )()) - ); - } -''', - ); - }); - - test('mui namespace from a different package is not migrated', () async { - await testSuggestor( - input: /*language=dart*/ ''' - import 'package:over_react/over_react.dart' as mui; - import 'package:over_react/over_react.dart' as alpha_mui; - import 'package:over_react/over_react.dart' as mui_alpha; - - content() { - mui.Fragment()(); - alpha_mui.Fragment()(); - mui_alpha.Fragment()(); - } -''', - ); - }); - - test('non-mui namespace on a react_material_ui import', () async { - await testSuggestor( - input: /*language=dart*/ ''' - import 'package:react_material_ui/react_material_ui.dart' as abc; - import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as other; - import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as something; - - content() { - abc.Button()(); - abc.Checkbox()(); - other.Rating; - something.Rating(); - } -''', - ); - }); - - test('no namespace on a react_material_ui import', () async { - await testSuggestor( - input: /*language=dart*/ ''' - import 'package:react_material_ui/react_material_ui.dart'; - - content() { - Button()(); - Checkbox()(); - } -''', - ); - }); - }); - }); -} diff --git a/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart b/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart new file mode 100644 index 00000000..2ab95b4c --- /dev/null +++ b/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart @@ -0,0 +1,423 @@ +// Copyright 2023 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:over_react_codemod/src/unify_package_rename_suggestors/unify_rename_suggestor.dart'; +import 'package:test/test.dart'; + +import '../resolved_file_context.dart'; +import '../util.dart'; + +// todo also test builders +// todo also test non-component usage of the namespace + +void main() { + final resolvedContext = SharedAnalysisContext.rmui; + + // Warm up analysis in a setUpAll so that if getting the resolved AST times out + // (which is more common for the WSD context), it fails here instead of failing the first test. + setUpAll(resolvedContext.warmUpAnalysis); + + group('UnifyRenameSuggestor', () { + final testSuggestor = getSuggestorTester( + UnifyRenameSuggestor(), + resolvedContext: resolvedContext, + ); + + group('namespace on component usage', () { + test('mui namespace from react_material_ui is migrated to unify', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/styles/color_utils.dart' as mui; + + content() { + mui.Button()(); + mui.Checkbox()(); + mui.ButtonColor.success; + mui.useTheme(); + mui.UnifyIcons.expandMore()(); + mui.Button; + mui.Button(); + mui.darken('abc', 1); + } +''', + expectedOutput: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/styles/color_utils.dart' as mui; + + content() { + unify.Button()(); + unify.Checkbox()(); + unify.ButtonColor.success; + unify.useTheme(); + unify.UnifyIcons.expandMore()(); + unify.Button; + unify.Button(); + unify.darken('abc', 1); + } +''', + ); + }); + + test('alpha namespace from react_material_ui is migrated to unify', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_mui; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; + + content() { + alpha_mui.Rating()(); + mui_alpha.Rating()(); + alpha_mui.TimelinePosition.left; + alpha_mui.useGridApiRef(); + alpha_mui.Popper; + alpha_mui.Popper(); + mui_alpha.TimelinePosition.left; + mui_alpha.useGridApiRef(); + mui_alpha.Popper; + mui_alpha.Popper(); + } +''', + expectedOutput: /*language=dart*/ ''' + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_mui; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; + + content() { + alpha_unify.Rating()(); + alpha_unify.Rating()(); + alpha_unify.TimelinePosition.left; + alpha_unify.useGridApiRef(); + alpha_unify.Popper; + alpha_unify.Popper(); + alpha_unify.TimelinePosition.left; + alpha_unify.useGridApiRef(); + alpha_unify.Popper; + alpha_unify.Popper(); + } +''', + ); + }); + + test('nested component implementations', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:over_react/over_react.dart'; + + content() { + Fragment()(mui.ButtonToolbar()( + (mui.Button()..color = mui.ButtonColor.wsdBtnLight)('Foo'), + (mui.Button() + ..size = mui.ButtonSize.small + ..color = mui.ButtonColor.primary + )( + 'Bar', + ), + )); + + return (mui.Autocomplete() + ..componentsProps = { + 'popper': mui.Popper()..placement = 'top-end', + 'popupIndicator': mui.IconButton()..sx = {'width': '20px'}, + } + ..sx = { + 'color': (mui.Theme theme) => + mui.getThemePalette(theme).common.white + } + ..renderInput = mui.wrapRenderInput((textFieldProps) => (mui.TextField() + ..addProps(textFieldProps) + ..InputLabelProps = (mui.InputLabel() + ..shrink = false) + )()) + ); + } +''', + expectedOutput: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:over_react/over_react.dart'; + + content() { + Fragment()(unify.ButtonToolbar()( + (unify.Button()..color = unify_wsd.WsdButtonColor.wsdBtnLight)('Foo'), + (unify.Button() + ..size = unify.ButtonSize.small + ..color = unify.ButtonColor.primary + )( + 'Bar', + ), + )); + + return (unify.Autocomplete() + ..componentsProps = { + 'popper': unify.Popper()..placement = 'top-end', + 'popupIndicator': unify.IconButton()..sx = {'width': '20px'}, + } + ..sx = { + 'color': (unify.Theme theme) => + unify.getThemePalette(theme).common.white + } + ..renderInput = unify.wrapRenderInput((textFieldProps) => (unify.TextField() + ..addProps(textFieldProps) + ..InputLabelProps = (unify.InputLabel() + ..shrink = false) + )()) + ); + } +''', + ); + }); + + test('mui namespace from a different package is not migrated', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:over_react/over_react.dart' as mui; + import 'package:over_react/over_react.dart' as alpha_mui; + import 'package:over_react/over_react.dart' as mui_alpha; + + content() { + mui.Fragment()(); + alpha_mui.Fragment()(); + mui_alpha.Fragment()(); + mui.useRef(); + } +''', + ); + }); + + test('non-mui namespace on a react_material_ui import', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as abc; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as other; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as something; + + content() { + abc.Button()(); + abc.Checkbox()(); + other.Rating; + something.Rating(); + } +''', + ); + }); + + test('no namespace on a react_material_ui import', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart'; + + content() { + Button()(); + Checkbox()(); + } +''', + ); + }); + }); + + group('renames', () { + test('components from react_material_ui to unify equivalents', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/react_material_ui.dart'; + import 'package:react_material_ui/react_material_ui.dart' as random_rmui_namespace; + import 'package:react_material_ui/components/providers/workiva_mui_theme_provider.dart'; + + content() { + mui.Alert()(); + mui.Alert(); + mui.Alert; + Alert()(); + random_rmui_namespace.Alert()(); + mui.LinkButton()(); + LinkButton()(); + random_rmui_namespace.LinkButton()(); + mui.MuiList()(); + MuiList()(); + random_rmui_namespace.MuiList()(); + WorkivaMuiThemeProvider()(); + } +''', + expectedOutput: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/react_material_ui.dart'; + import 'package:react_material_ui/react_material_ui.dart' as random_rmui_namespace; + import 'package:react_material_ui/components/providers/workiva_mui_theme_provider.dart'; + + content() { + unify_wsd.WsdAlert()(); + unify_wsd.WsdAlert(); + unify_wsd.WsdAlert; + unify_wsd.WsdAlert()(); + unify_wsd.WsdAlert()(); + unify_wsd.WsdLinkButton()(); + unify_wsd.WsdLinkButton()(); + unify_wsd.WsdLinkButton()(); + unify.UnifyList()(); + UnifyList()(); + random_rmui_namespace.UnifyList()(); + UnifyThemeProvider()(); + } +''', + ); + }); + + test('objects from react_material_ui to unify equivalents', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/react_material_ui.dart'; + import 'package:react_material_ui/react_material_ui.dart' as random_rmui_namespace; + + content() { + mui.AutocompleteOnChangeObject; + mui.AutocompleteOnChangeObject(); + random_rmui_namespace.BackdropTimeoutObject; + BadgeAnchorOriginObjectHorizontal; + MenuAnchorOriginObject(); + (Slider()..marks = [SliderMarksObject(value: 10)])(); + } +''', + expectedOutput: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/react_material_ui.dart'; + import 'package:react_material_ui/react_material_ui.dart' as random_rmui_namespace; + + content() { + unify.AutocompleteChangeDetails; + unify.AutocompleteChangeDetails(); + random_rmui_namespace.BackdropObject; + BadgeOriginHorizontal; + MenuPopoverOrigin(); + (Slider()..marks = [SliderMark(value: 10)])(); + } +''', + ); + }); + + test('ButtonColor updates', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/react_material_ui.dart'; + + content() { + mui.ButtonColor.success; + mui.ButtonColor.wsdBtnInverse; + mui.ButtonColor.wsdBtnLight; + mui.ButtonColor.wsdBtnWhite; + ButtonColor.success; + ButtonColor.wsdBtnInverse; + ButtonColor.wsdBtnLight; + ButtonColor.wsdBtnWhite; + } +''', + expectedOutput: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/react_material_ui.dart'; + + content() { + unify.ButtonColor.success; + unify_wsd.WsdButtonColor.wsdBtnInverse; + unify_wsd.WsdButtonColor.wsdBtnLight; + unify_wsd.WsdButtonColor.wsdBtnWhite; + ButtonColor.success; + unify_wsd.WsdButtonColor.wsdBtnInverse; + unify_wsd.WsdButtonColor.wsdBtnLight; + unify_wsd.WsdButtonColor.wsdBtnWhite; + } +''', + ); + }); + + test('except when they are not from react_material_ui', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:over_react/over_react.dart'; + + // Shadows the RMUI factories + UiFactory Alert; + UiFactory LinkButton; + UiFactory MuiList; + UiFactory WorkivaMuiThemeProvider; + class BackdropTimeoutObject {} + abstract class ButtonColor { + static const String wsdBtnLight = 'wsdBtnLight'; + } + + content() { + Alert()(); + LinkButton()(); + MuiList()(); + WorkivaMuiThemeProvider()(); + BackdropTimeoutObject; + ButtonColor.wsdBtnLight; + } +''', + ); + }); + }); + + group('fixme comments', () { + test('for specific components that need manual intervention', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/react_material_ui.dart'; + + content() { + mui.Badge()(); + Badge()(); + mui.LinearProgress()(); + LinearProgress()(); + } +''', + expectedOutput: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/react_material_ui.dart'; + + content() { + // FIXME(unify_package_rename) Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart. + unify.Badge()(); + // FIXME(unify_package_rename) Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart. + Badge()(); + // FIXME(unify_package_rename) Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart. + unify.LinearProgress()(); + // FIXME(unify_package_rename) Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart. + LinearProgress()(); + } +''', + ); + }); + + test('except when they are not from react_material_ui', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:over_react/over_react.dart'; + + // Shadows the RMUI factories + UiFactory Badge; + UiFactory LinearProgress; + + content() { + Badge()(); + LinearProgress()(); + } +''', + ); + }); + }); + }); +} From fcdef767ca8bd1d0ff02a19d69bd63899a65c7e5 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Thu, 12 Oct 2023 16:53:36 -0700 Subject: [PATCH 11/29] Do some clean up --- lib/src/executables/mui_migration.dart | 21 ++- lib/src/executables/unify_package_rename.dart | 2 - ...ckage_rename_component_usage_migrator.dart | 90 ----------- lib/src/util/unused_import_remover.dart | 55 ------- .../unused_wsd_import_remover_test.dart} | 6 +- ..._rename_component_usage_migrator_test.dart | 153 ------------------ 6 files changed, 16 insertions(+), 311 deletions(-) delete mode 100644 lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart delete mode 100644 lib/src/util/unused_import_remover.dart rename test/{util/import_remover_test.dart => mui_suggestors/unused_wsd_import_remover_test.dart} (95%) delete mode 100644 test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart diff --git a/lib/src/executables/mui_migration.dart b/lib/src/executables/mui_migration.dart index 3c49454c..bdeec36e 100644 --- a/lib/src/executables/mui_migration.dart +++ b/lib/src/executables/mui_migration.dart @@ -21,16 +21,16 @@ import 'package:codemod/codemod.dart'; import 'package:glob/glob.dart'; import 'package:glob/list_local_fs.dart'; import 'package:logging/logging.dart'; -import 'package:over_react_codemod/src/mui_suggestors/constants.dart'; import 'package:over_react_codemod/src/util.dart'; import 'package:over_react_codemod/src/ignoreable.dart'; import 'package:over_react_codemod/src/mui_suggestors/components.dart'; +import 'package:over_react_codemod/src/mui_suggestors/unused_wsd_import_remover.dart'; import 'package:over_react_codemod/src/util/package_util.dart'; import 'package:over_react_codemod/src/util/pubspec_upgrader.dart'; import 'package:over_react_codemod/src/util/logging.dart'; +import '../mui_suggestors/constants.dart'; import '../util/importer.dart'; -import '../util/unused_import_remover.dart'; final _log = Logger('orcm.mui_migration'); @@ -96,7 +96,8 @@ void main(List args) async { final parsedArgs = parser.parse(args); if (parsedArgs['help'] as bool) { - stderr.writeln('Migrates web_skin_dart component usages to react_material_ui.'); + stderr.writeln( + 'Migrates web_skin_dart component usages to react_material_ui.'); stderr.writeln(); stderr.writeln('Usage:'); stderr.writeln(' mui_migration [arguments]'); @@ -113,8 +114,10 @@ void main(List args) async { // // An alternative would be to use `--` and `arguments.rest` to pass along codemod // args, but that's not as convenient to the user and makes showing help a bit more complicated. - final codemodArgs = - _allCodemodFlags.where((name) => parsedArgs[name] as bool).map((name) => '--$name').toList(); + final codemodArgs = _allCodemodFlags + .where((name) => parsedArgs[name] as bool) + .map((name) => '--$name') + .toList(); // codemod sets up a global logging handler that forwards to the console, and // we want that set up before we do other non-codemodd things that might log. @@ -188,7 +191,7 @@ void main(List args) async { // output of previous migrators. [aggregate(migratorsToRun)], [importerSuggestorBuilder(importUri: rmuiImportUri, importNamespace: muiNs)], - [unusedImportRemoverSuggestorBuilder(packageName: 'web_skin_dart')], + [unusedWsdImportRemover], ]); if (exitCode != 0) return; @@ -197,7 +200,8 @@ void main(List args) async { exitCode = await runInteractiveCodemod( pubspecYamlPaths(), aggregate([ - PubspecUpgrader('react_material_ui', rmuiVersionRange, hostedUrl: 'https://pub.workiva.org'), + PubspecUpgrader('react_material_ui', rmuiVersionRange, + hostedUrl: 'https://pub.workiva.org'), ].map((s) => ignoreable(s))), defaultYes: true, args: codemodArgs, @@ -228,7 +232,8 @@ void sortPartsLast(List dartPaths) { } Future pubGetForAllPackageRoots(Iterable files) async { - _log.info('Running `pub get` if needed so that all Dart files can be resolved...'); + _log.info( + 'Running `pub get` if needed so that all Dart files can be resolved...'); final packageRoots = files.map(findPackageRootFor).toSet(); for (final packageRoot in packageRoots) { await runPubGetIfNeeded(packageRoot); diff --git a/lib/src/executables/unify_package_rename.dart b/lib/src/executables/unify_package_rename.dart index c07b3786..57ae78a3 100644 --- a/lib/src/executables/unify_package_rename.dart +++ b/lib/src/executables/unify_package_rename.dart @@ -27,9 +27,7 @@ import 'package:over_react_codemod/src/unify_package_rename_suggestors/unify_ren import 'package:over_react_codemod/src/util.dart'; import 'package:over_react_codemod/src/util/pubspec_upgrader.dart'; -import '../unify_package_rename_suggestors/package_rename_component_usage_migrator.dart'; import '../util/importer.dart'; -import '../util/unused_import_remover.dart'; const _changesRequiredOutput = """ To update your code, run the following commands in your repository: diff --git a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart b/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart deleted file mode 100644 index 9088d5dc..00000000 --- a/lib/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2023 Workiva Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:collection/collection.dart'; -import 'package:over_react_codemod/src/unify_package_rename_suggestors/constants.dart'; -import 'package:over_react_codemod/src/util.dart'; -import 'package:over_react_codemod/src/util/component_usage.dart'; -import 'package:over_react_codemod/src/util/component_usage_migrator.dart'; -import 'package:over_react_codemod/src/util/element_type_helpers.dart'; - -class PackageRenameComponentUsageMigrator extends ComponentUsageMigrator { - @override - String get fixmePrefix => 'FIXME(unify_package_rename)'; - - @override - bool shouldMigrateUsage(FluentComponentUsage usage) => true; - - // todo update to skip all flags - @override - bool get shouldFlagUnsafeMethodCalls => false; - @override - bool get shouldFlagUntypedSingleProp => false; - @override - bool get shouldFlagExtensionMembers => false; - @override - bool get shouldFlagPrefixedProps => false; - @override - bool get shouldFlagRefProp => false; - @override - bool get shouldFlagClassName => false; - - @override - void migrateUsage(FluentComponentUsage usage) { - super.migrateUsage(usage); - - final factoryElement = usage.factoryTopLevelVariableElement; - if (factoryElement == null) return; - - if (factoryElement.isDeclaredInPackage('react_material_ui')) { - // Save the import namespace to later replace with a unify version. - final importNamespace = usage.factory.tryCast()?.prefix; - final newImportNamespace = rmuiImportsToUpdate - .where((import) => - importNamespace?.name != null && - (import.possibleMuiNamespaces?.contains(importNamespace?.name) ?? false)) - .singleOrNull - ?.namespace; - - // Update components that were renamed in unify_ui. - final identifier = usage.factory.tryCast() ?? - usage.factory.tryCast()?.identifier; - final newComponentName = rmuiToUnifyIdentifierRenames[identifier?.name]; - final isFromWsdEntrypoint = newComponentName?.startsWith('Wsd') ?? false; - if (identifier != null && newComponentName != null) { - if (isFromWsdEntrypoint) { - // Overwrite or add import namespace for components that will be imported from the separate - // unify_ui/components/wsd.dart entrypoint so we can keep the namespace of the import - // we add consistent with the components that use it. - final factory = usage.factory.tryCast() ?? identifier; - yieldPatch('$unifyWsdNamespace.$newComponentName', factory.offset, factory.end); - } else { - yieldPatch(newComponentName, identifier.offset, identifier.end); - } - } - - // Replace 'mui' namespaces usage with 'unify'. - // if (importNamespace != null && newImportNamespace != null && !isFromWsdEntrypoint) { - // yieldPatch(newImportNamespace, importNamespace.offset, importNamespace.end); - // } - - // Add comments for components that need manual verification. - if (identifier?.name == 'Badge' || identifier?.name == 'LinearProgress') { - yieldUsageFixmePatch(usage, - 'Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart.'); - } - } - } -} diff --git a/lib/src/util/unused_import_remover.dart b/lib/src/util/unused_import_remover.dart deleted file mode 100644 index ac09d6fe..00000000 --- a/lib/src/util/unused_import_remover.dart +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2021 Workiva Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/dart/ast/syntactic_entity.dart'; -import 'package:codemod/codemod.dart'; -import 'package:logging/logging.dart'; - -final _log = Logger('unusedWsdImportRemover'); - -/// Creates a suggestor that removes unused imports for [packageName]. -Suggestor unusedImportRemoverSuggestorBuilder({required String packageName}) { - return (context) async* { - final unitResult = await context.getResolvedUnit(); - if (unitResult == null) { - // Most likely a part and not a library. - return; - } - final unusedImportErrors = unitResult.errors - .where((error) => error.errorCode.name.toLowerCase() == 'unused_import') - .toList(); - - final allImports = unitResult.unit.directives.whereType().toList(); - - for (final error in unusedImportErrors) { - final matchingImport = - allImports.singleWhere((import) => import.containsOffset(error.offset)); - final importUri = matchingImport.uriContent; - if (importUri != null && importUri.startsWith('package:$packageName/')) { - final prevTokenEnd = matchingImport.beginToken.previous?.end; - // Try to take the newline before the import, but watch out - // for prevToken's offset/end being -1 if it's this import has the - // first token in the file. - final startOffset = - prevTokenEnd != null && prevTokenEnd != -1 ? prevTokenEnd : matchingImport.offset; - yield Patch('', startOffset, matchingImport.end); - } - } - }; -} - -extension on SyntacticEntity { - bool containsOffset(int offset) => offset >= this.offset && offset < end; -} diff --git a/test/util/import_remover_test.dart b/test/mui_suggestors/unused_wsd_import_remover_test.dart similarity index 95% rename from test/util/import_remover_test.dart rename to test/mui_suggestors/unused_wsd_import_remover_test.dart index 8bdc9a03..6e84f340 100644 --- a/test/util/import_remover_test.dart +++ b/test/mui_suggestors/unused_wsd_import_remover_test.dart @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:over_react_codemod/src/util/unused_import_remover.dart'; +import 'package:over_react_codemod/src/mui_suggestors/unused_wsd_import_remover.dart'; import 'package:test/test.dart'; import '../resolved_file_context.dart'; import '../util.dart'; void main() { - group('unusedImportRemoverSuggestorBuilder', () { + group('unusedWsdImportRemover', () { final resolvedContext = SharedAnalysisContext.wsd; // Warm up analysis in a setUpAll so that if getting the resolved AST times out @@ -27,7 +27,7 @@ void main() { setUpAll(resolvedContext.warmUpAnalysis); final testSuggestor = getSuggestorTester( - unusedImportRemoverSuggestorBuilder(packageName: 'web_skin_dart'), + unusedWsdImportRemover, resolvedContext: resolvedContext, ); diff --git a/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart b/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart deleted file mode 100644 index 8d8fa369..00000000 --- a/test/unify_package_rename_suggestors/package_rename_component_usage_migrator_test.dart +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2023 Workiva Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import 'package:over_react_codemod/src/unify_package_rename_suggestors/package_rename_component_usage_migrator.dart'; -import 'package:test/test.dart'; - -import '../resolved_file_context.dart'; -import '../util.dart'; - -// todo also test builders -// todo also test non-component usage of the namespace - -void main() { - final resolvedContext = SharedAnalysisContext.rmui; - - // Warm up analysis in a setUpAll so that if getting the resolved AST times out - // (which is more common for the WSD context), it fails here instead of failing the first test. - setUpAll(resolvedContext.warmUpAnalysis); - - group('PackageRenameComponentUsageMigrator', () { - final testSuggestor = getSuggestorTester( - PackageRenameComponentUsageMigrator(), - resolvedContext: resolvedContext, - ); - - group('rename components', () { - test('from react_material_ui to unify equivalents', () async { - await testSuggestor( - input: /*language=dart*/ ''' - import 'package:react_material_ui/react_material_ui.dart' as mui; - import 'package:react_material_ui/react_material_ui.dart'; - import 'package:react_material_ui/react_material_ui.dart' as random_rmui_namespace; - import 'package:react_material_ui/components/providers/workiva_mui_theme_provider.dart'; - - content() { - mui.Alert()(); - Alert()(); - random_rmui_namespace.Alert()(); - mui.LinkButton()(); - LinkButton()(); - random_rmui_namespace.LinkButton()(); - mui.MuiList()(); - MuiList()(); - random_rmui_namespace.MuiList()(); - WorkivaMuiThemeProvider()(); - } -''', - expectedOutput: /*language=dart*/ ''' - import 'package:react_material_ui/react_material_ui.dart' as mui; - import 'package:react_material_ui/react_material_ui.dart'; - import 'package:react_material_ui/react_material_ui.dart' as random_rmui_namespace; - import 'package:react_material_ui/components/providers/workiva_mui_theme_provider.dart'; - - content() { - unify_wsd.WsdAlert()(); - unify_wsd.WsdAlert()(); - unify_wsd.WsdAlert()(); - unify_wsd.WsdLinkButton()(); - unify_wsd.WsdLinkButton()(); - unify_wsd.WsdLinkButton()(); - mui.UnifyList()(); - UnifyList()(); - random_rmui_namespace.UnifyList()(); - UnifyThemeProvider()(); - } -''', - ); - }); - - test('except when they are not from react_material_ui', () async { - await testSuggestor( - input: /*language=dart*/ ''' - import 'package:over_react/over_react.dart'; - - // Shadows the RMUI factories - UiFactory Alert; - UiFactory LinkButton; - UiFactory MuiList; - UiFactory WorkivaMuiThemeProvider; - - content() { - Alert()(); - LinkButton()(); - MuiList()(); - WorkivaMuiThemeProvider()(); - } -''', - ); - }); - }); - - group('fixme comments', () { - test('for specific components that need manual intervention', () async { - await testSuggestor( - input: /*language=dart*/ ''' - import 'package:react_material_ui/react_material_ui.dart' as mui; - import 'package:react_material_ui/react_material_ui.dart'; - - content() { - mui.Badge()(); - Badge()(); - mui.LinearProgress()(); - LinearProgress()(); - } -''', - expectedOutput: /*language=dart*/ ''' - import 'package:react_material_ui/react_material_ui.dart' as mui; - import 'package:react_material_ui/react_material_ui.dart'; - - content() { - // FIXME(unify_package_rename) Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart. - mui.Badge()(); - // FIXME(unify_package_rename) Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart. - Badge()(); - // FIXME(unify_package_rename) Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart. - mui.LinearProgress()(); - // FIXME(unify_package_rename) Check what theme provider is wrapping this component: if it is a UnifyThemeProvider, remove this FIXME - no action is required; otherwise, migrate this component back to Web Skin Dart. - LinearProgress()(); - } -''', - ); - }); - - test('except when they are not from react_material_ui', () async { - await testSuggestor( - input: /*language=dart*/ ''' - import 'package:over_react/over_react.dart'; - - // Shadows the RMUI factories - UiFactory Badge; - UiFactory LinearProgress; - - content() { - Badge()(); - LinearProgress()(); - } -''', - ); - }); - }); - }); -} From 0c8e1cbc679c72b23b5ac25254533edd518ddbc0 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Tue, 17 Oct 2023 15:05:21 -0700 Subject: [PATCH 12/29] Do some more clean up --- lib/src/executables/mui_migration.dart | 19 +++---- lib/src/executables/unify_package_rename.dart | 32 ++++++----- .../unused_wsd_import_remover.dart | 55 ------------------- .../constants.dart | 4 ++ .../import_renamer.dart | 44 ++++++++++++--- .../unify_rename_suggestor.dart | 14 +++-- lib/src/util/unused_import_remover.dart | 55 +++++++++++++++++++ .../unused_wsd_import_remover_test.dart | 26 ++++++++- .../import_renamer_test.dart | 55 ++++++++++++------- .../unify_rename_suggestor_test.dart | 5 ++ 10 files changed, 192 insertions(+), 117 deletions(-) delete mode 100644 lib/src/mui_suggestors/unused_wsd_import_remover.dart create mode 100644 lib/src/util/unused_import_remover.dart diff --git a/lib/src/executables/mui_migration.dart b/lib/src/executables/mui_migration.dart index bdeec36e..8eb6f919 100644 --- a/lib/src/executables/mui_migration.dart +++ b/lib/src/executables/mui_migration.dart @@ -24,13 +24,13 @@ import 'package:logging/logging.dart'; import 'package:over_react_codemod/src/util.dart'; import 'package:over_react_codemod/src/ignoreable.dart'; import 'package:over_react_codemod/src/mui_suggestors/components.dart'; -import 'package:over_react_codemod/src/mui_suggestors/unused_wsd_import_remover.dart'; import 'package:over_react_codemod/src/util/package_util.dart'; import 'package:over_react_codemod/src/util/pubspec_upgrader.dart'; import 'package:over_react_codemod/src/util/logging.dart'; import '../mui_suggestors/constants.dart'; import '../util/importer.dart'; +import '../util/unused_import_remover.dart'; final _log = Logger('orcm.mui_migration'); @@ -96,8 +96,7 @@ void main(List args) async { final parsedArgs = parser.parse(args); if (parsedArgs['help'] as bool) { - stderr.writeln( - 'Migrates web_skin_dart component usages to react_material_ui.'); + stderr.writeln('Migrates web_skin_dart component usages to react_material_ui.'); stderr.writeln(); stderr.writeln('Usage:'); stderr.writeln(' mui_migration [arguments]'); @@ -114,10 +113,8 @@ void main(List args) async { // // An alternative would be to use `--` and `arguments.rest` to pass along codemod // args, but that's not as convenient to the user and makes showing help a bit more complicated. - final codemodArgs = _allCodemodFlags - .where((name) => parsedArgs[name] as bool) - .map((name) => '--$name') - .toList(); + final codemodArgs = + _allCodemodFlags.where((name) => parsedArgs[name] as bool).map((name) => '--$name').toList(); // codemod sets up a global logging handler that forwards to the console, and // we want that set up before we do other non-codemodd things that might log. @@ -191,7 +188,7 @@ void main(List args) async { // output of previous migrators. [aggregate(migratorsToRun)], [importerSuggestorBuilder(importUri: rmuiImportUri, importNamespace: muiNs)], - [unusedWsdImportRemover], + [unusedImportRemoverSuggestorBuilder('web_skin_dart')], ]); if (exitCode != 0) return; @@ -200,8 +197,7 @@ void main(List args) async { exitCode = await runInteractiveCodemod( pubspecYamlPaths(), aggregate([ - PubspecUpgrader('react_material_ui', rmuiVersionRange, - hostedUrl: 'https://pub.workiva.org'), + PubspecUpgrader('react_material_ui', rmuiVersionRange, hostedUrl: 'https://pub.workiva.org'), ].map((s) => ignoreable(s))), defaultYes: true, args: codemodArgs, @@ -232,8 +228,7 @@ void sortPartsLast(List dartPaths) { } Future pubGetForAllPackageRoots(Iterable files) async { - _log.info( - 'Running `pub get` if needed so that all Dart files can be resolved...'); + _log.info('Running `pub get` if needed so that all Dart files can be resolved...'); final packageRoots = files.map(findPackageRootFor).toSet(); for (final packageRoot in packageRoots) { await runPubGetIfNeeded(packageRoot); diff --git a/lib/src/executables/unify_package_rename.dart b/lib/src/executables/unify_package_rename.dart index 57ae78a3..c36a92f8 100644 --- a/lib/src/executables/unify_package_rename.dart +++ b/lib/src/executables/unify_package_rename.dart @@ -17,7 +17,6 @@ import 'dart:io'; import 'package:args/args.dart'; import 'package:codemod/codemod.dart'; import 'package:over_react_codemod/src/executables/mui_migration.dart'; -import 'package:over_react_codemod/src/ignoreable.dart'; import 'package:over_react_codemod/src/rmui_bundle_update_suggestors/constants.dart'; import 'package:over_react_codemod/src/rmui_bundle_update_suggestors/dart_script_updater.dart'; import 'package:over_react_codemod/src/rmui_bundle_update_suggestors/html_script_updater.dart'; @@ -25,9 +24,10 @@ import 'package:over_react_codemod/src/unify_package_rename_suggestors/constants import 'package:over_react_codemod/src/unify_package_rename_suggestors/import_renamer.dart'; import 'package:over_react_codemod/src/unify_package_rename_suggestors/unify_rename_suggestor.dart'; import 'package:over_react_codemod/src/util.dart'; -import 'package:over_react_codemod/src/util/pubspec_upgrader.dart'; +import '../dependency_validator_suggestors/ignore_updaters/v3_updater.dart'; import '../util/importer.dart'; +import '../util/unused_import_remover.dart'; const _changesRequiredOutput = """ To update your code, run the following commands in your repository: @@ -68,15 +68,15 @@ void main(List args) async { exitCode = await runCodemods([ // todo can we also remove rmui dependency here??? // Add unify_ui dependency. - CodemodInfo(paths: pubspecYamlPaths(), sequence: [ - aggregate( - [ - // todo update version: - PubspecUpgrader('unify_ui', parseVersionRange('^1.121.0'), - hostedUrl: 'https://pub.workiva.org', shouldAddDependencies: true), - ].map((s) => ignoreable(s)), - ) - ]), + // CodemodInfo(paths: pubspecYamlPaths(), sequence: [ + // aggregate( + // [ + // // todo update version: + // PubspecUpgrader('unify_ui', parseVersionRange('^1.121.0'), + // hostedUrl: 'https://pub.workiva.org', shouldAddDependencies: true), + // ].map((s) => ignoreable(s)), + // ) + // ]), // Update RMUI bundle script in all HTML files (and templates) to Unify bundle. CodemodInfo(paths: allHtmlPathsIncludingTemplates(), sequence: [ HtmlScriptUpdater(rmuiBundleDevUpdated, unifyBundleDev), @@ -87,6 +87,9 @@ void main(List args) async { DartScriptUpdater(rmuiBundleDevUpdated, unifyBundleDev), DartScriptUpdater(rmuiBundleProdUpdated, unifyBundleProd), ]), + CodemodInfo( + paths: pubspecYamlPaths(), + sequence: [V3DependencyValidatorUpdater('react_material_ui', remove: true)]) ]); if (exitCode != 0) return; @@ -115,9 +118,10 @@ void main(List args) async { newPackageNamespace: 'unify', ) ]), - // CodemodInfo( - // paths: dartPaths, - // sequence: [unusedImportRemoverSuggestorBuilder(packageName: 'react_material_ui')]), + CodemodInfo(paths: dartPaths, sequence: [ + unusedImportRemoverSuggestorBuilder('react_material_ui'), + unusedImportRemoverSuggestorBuilder('unify_ui'), + ]), ]); if (exitCode != 0) return; } diff --git a/lib/src/mui_suggestors/unused_wsd_import_remover.dart b/lib/src/mui_suggestors/unused_wsd_import_remover.dart deleted file mode 100644 index 3392b573..00000000 --- a/lib/src/mui_suggestors/unused_wsd_import_remover.dart +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2021 Workiva Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/dart/ast/syntactic_entity.dart'; -import 'package:codemod/codemod.dart'; -import 'package:logging/logging.dart'; - -final _log = Logger('unusedWsdImportRemover'); - -/// A suggestor that removes unused imports for WSD. -Stream unusedWsdImportRemover(FileContext context) async* { - final unitResult = await context.getResolvedUnit(); - if (unitResult == null) { - // Most likely a part and not a library. - return; - } - final unusedImportErrors = unitResult.errors - .where((error) => error.errorCode.name.toLowerCase() == 'unused_import') - .toList(); - - final allImports = - unitResult.unit.directives.whereType().toList(); - - for (final error in unusedImportErrors) { - final matchingImport = - allImports.singleWhere((import) => import.containsOffset(error.offset)); - final importUri = matchingImport.uri.stringValue; - if (importUri != null && importUri.startsWith('package:web_skin_dart/')) { - final prevTokenEnd = matchingImport.beginToken.previous?.end; - // Try to take the newline before the import, but watch out - // for prevToken's offset/end being -1 if it's this import has the - // first token in the file. - final startOffset = prevTokenEnd != null && prevTokenEnd != -1 - ? prevTokenEnd - : matchingImport.offset; - yield Patch('', startOffset, matchingImport.end); - } - } -} - -extension on SyntacticEntity { - bool containsOffset(int offset) => offset >= this.offset && offset < end; -} diff --git a/lib/src/unify_package_rename_suggestors/constants.dart b/lib/src/unify_package_rename_suggestors/constants.dart index 2c7013f8..ec342765 100644 --- a/lib/src/unify_package_rename_suggestors/constants.dart +++ b/lib/src/unify_package_rename_suggestors/constants.dart @@ -55,9 +55,13 @@ final rmuiImportsToUpdate = [ const rmuiToUnifyIdentifierRenames = { // Components 'Alert': 'WsdAlert', + 'AlertPropsMixin': 'WsdAlertPropsMixin', 'LinkButton': 'WsdLinkButton', + 'LinkButtonPropsMixin': 'WsdLinkButtonPropsMixin', 'MuiList': 'UnifyList', + 'MuiListPropsMixin': 'UnifyListPropsMixin', 'WorkivaMuiThemeProvider': 'UnifyThemeProvider', + 'WorkivaMuiThemeProviderPropsMixin': 'UnifyThemeProviderPropsMixin', // Autocomplete objects 'AutocompleteFilterOptionsObject': 'AutocompleteFilterOptionsState', 'AutocompleteOnChangeObject': 'AutocompleteChangeDetails', diff --git a/lib/src/unify_package_rename_suggestors/import_renamer.dart b/lib/src/unify_package_rename_suggestors/import_renamer.dart index 0984feb2..4557899c 100644 --- a/lib/src/unify_package_rename_suggestors/import_renamer.dart +++ b/lib/src/unify_package_rename_suggestors/import_renamer.dart @@ -14,7 +14,6 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:codemod/codemod.dart'; -import 'package:analyzer/dart/ast/visitor.dart'; import '../util/importer.dart'; import 'constants.dart'; @@ -107,6 +106,7 @@ Suggestor importRenamerSuggestorBuilder({ .whereType() .where((import) => import.uri.stringValue?.startsWith('package:$oldPackageName/') ?? false); + final newImportsInfo = []; for (final import in importsToUpdate) { final importUri = import.uri.stringValue; final namespace = import.prefix?.name; @@ -126,14 +126,7 @@ Suggestor importRenamerSuggestorBuilder({ } if (newImportUri != null) { - final insertInfo = insertionLocationForPackageImport( - newImportUri, mainLibraryUnitResult.unit, mainLibraryUnitResult.lineInfo); - yield Patch( - insertInfo.leadingNewlines + - "import '$newImportUri'${newNamespace != null ? ' as $newNamespace' : ''};" + - insertInfo.trailingNewlines, - insertInfo.offset, - insertInfo.offset); + newImportsInfo.add(UnifyImportInfo(newImportUri, namespace: newNamespace)); } final prevTokenEnd = import.beginToken.previous?.end; @@ -144,6 +137,39 @@ Suggestor importRenamerSuggestorBuilder({ yield Patch('', startOffset, import.end); } + // Sort imports before adding them. + newImportsInfo.sort((a, b) => a.uri.compareTo(b.uri)); + + for (final importInfo in newImportsInfo) { + final insertInfo = insertionLocationForPackageImport( + importInfo.uri, mainLibraryUnitResult.unit, mainLibraryUnitResult.lineInfo); + yield Patch( + insertInfo.leadingNewlines + + "import '${importInfo.uri}'${importInfo.namespace != null ? ' as ${importInfo.namespace}' : ''};" + + insertInfo.trailingNewlines, + insertInfo.offset, + insertInfo.offset); + } + + // if (newImportUri != null) { + // final insertInfo = insertionLocationForPackageImport( + // newImportUri, mainLibraryUnitResult.unit, mainLibraryUnitResult.lineInfo); + // yield Patch( + // insertInfo.leadingNewlines + + // "import '$newImportUri'${newNamespace != null ? ' as $newNamespace' : ''};" + + // insertInfo.trailingNewlines, + // insertInfo.offset, + // insertInfo.offset); + // } + // + // final prevTokenEnd = import.beginToken.previous?.end; + // // Try to take the newline before the import, but watch out + // // for prevToken's offset/end being -1 if it's this import has the + // // first token in the file. + // final startOffset = prevTokenEnd != null && prevTokenEnd != -1 ? prevTokenEnd : import.offset; + // yield Patch('', startOffset, import.end); + // } + // for(final import in importsToUpdate) { // // } diff --git a/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart b/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart index eada8cc0..ef4d66f6 100644 --- a/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart +++ b/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart @@ -89,23 +89,25 @@ class UnifyRenameSuggestor extends GeneralizingAstVisitor with ClassSuggestor { } } - // Update WSD ButtonColor usages. + // Update WSD ButtonColor and AlertSize usages. { - // Update ButtonColor WSD properties to use the WsdButtonColor object if applicable. - yieldButtonColorPatchIfApplicable( - Expression node, String? objectName, String? propertyName) { + // Update WSD constant properties objects to use the WSD versions if applicable. + yieldWsdRenamePatchIfApplicable(Expression node, String? objectName, String? propertyName) { if (objectName == 'ButtonColor' && (propertyName?.startsWith('wsd') ?? false)) { isFromWsdEntrypoint = true; yieldPatch('$unifyWsdNamespace.WsdButtonColor.$propertyName', node.offset, node.end); + } else if (objectName == 'AlertSize') { + isFromWsdEntrypoint = true; + yieldPatch('$unifyWsdNamespace.WsdAlertSize.$propertyName', node.offset, node.end); } } final parent = node.parent; // Check for non-namespaced `ButtonColor.wsd...` usage. - yieldButtonColorPatchIfApplicable(node, prefix?.name, identifier?.name); + yieldWsdRenamePatchIfApplicable(node, prefix?.name, identifier?.name); // Check for namespaced `mui.ButtonColor.wsd...` usage. if (node is PrefixedIdentifier && parent is PropertyAccess) { - yieldButtonColorPatchIfApplicable(parent, identifier?.name, parent.propertyName.name); + yieldWsdRenamePatchIfApplicable(parent, identifier?.name, parent.propertyName.name); } } diff --git a/lib/src/util/unused_import_remover.dart b/lib/src/util/unused_import_remover.dart new file mode 100644 index 00000000..bc45f7a1 --- /dev/null +++ b/lib/src/util/unused_import_remover.dart @@ -0,0 +1,55 @@ +// Copyright 2021 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/syntactic_entity.dart'; +import 'package:codemod/codemod.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('unusedWsdImportRemover'); + +/// Creates a suggestor that removes unused [package] imports. +Suggestor unusedImportRemoverSuggestorBuilder(String package) { + return (context) async* { + final unitResult = await context.getResolvedUnit(); + if (unitResult == null) { + // Most likely a part and not a library. + return; + } + final unusedImportErrors = unitResult.errors + .where((error) => error.errorCode.name.toLowerCase() == 'unused_import') + .toList(); + + final allImports = unitResult.unit.directives.whereType().toList(); + + for (final error in unusedImportErrors) { + final matchingImport = + allImports.singleWhere((import) => import.containsOffset(error.offset)); + final importUri = matchingImport.uri.stringValue; + if (importUri != null && importUri.startsWith('package:$package/')) { + final prevTokenEnd = matchingImport.beginToken.previous?.end; + // Try to take the newline before the import, but watch out + // for prevToken's offset/end being -1 if it's this import has the + // first token in the file. + final startOffset = + prevTokenEnd != null && prevTokenEnd != -1 ? prevTokenEnd : matchingImport.offset; + yield Patch('', startOffset, matchingImport.end); + } + } + }; +} + +extension on SyntacticEntity { + bool containsOffset(int offset) => offset >= this.offset && offset < end; +} diff --git a/test/mui_suggestors/unused_wsd_import_remover_test.dart b/test/mui_suggestors/unused_wsd_import_remover_test.dart index 6e84f340..2b2575ef 100644 --- a/test/mui_suggestors/unused_wsd_import_remover_test.dart +++ b/test/mui_suggestors/unused_wsd_import_remover_test.dart @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:over_react_codemod/src/mui_suggestors/unused_wsd_import_remover.dart'; +import 'package:over_react_codemod/src/util/unused_import_remover.dart'; import 'package:test/test.dart'; import '../resolved_file_context.dart'; @@ -27,7 +27,7 @@ void main() { setUpAll(resolvedContext.warmUpAnalysis); final testSuggestor = getSuggestorTester( - unusedWsdImportRemover, + unusedImportRemoverSuggestorBuilder('web_skin_dart'), resolvedContext: resolvedContext, ); @@ -124,6 +124,28 @@ void main() { ''', ); }); + + test('for a different package name', () async { + final testSuggestor = getSuggestorTester( + unusedImportRemoverSuggestorBuilder('over_react'), + resolvedContext: resolvedContext, + ); + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:over_react/over_react.dart'; + import 'package:web_skin_dart/ui_components.dart'; + import 'package:web_skin_dart/component2/all.dart' as wsd2; + + content() => wsd2.Button()(); + ''', + expectedOutput: /*language=dart*/ ''' + import 'package:web_skin_dart/ui_components.dart'; + import 'package:web_skin_dart/component2/all.dart' as wsd2; + + content() => wsd2.Button()(); + ''', + ); + }); }); }, tags: 'wsd'); } diff --git a/test/unify_package_rename_suggestors/import_renamer_test.dart b/test/unify_package_rename_suggestors/import_renamer_test.dart index 7d4d927d..4bcad7ad 100644 --- a/test/unify_package_rename_suggestors/import_renamer_test.dart +++ b/test/unify_package_rename_suggestors/import_renamer_test.dart @@ -56,10 +56,27 @@ void main() { ''', expectedOutput: /*language=dart*/ ''' import 'package:over_react/over_react.dart'; + import 'package:unify_ui/abc.dart'; + import 'package:unify_ui/components/badge.dart'; import 'package:unify_ui/unify_ui.dart'; import 'package:unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart'; - import 'package:unify_ui/components/badge.dart'; - import 'package:unify_ui/abc.dart'; + + content() => Dom.div()(); + ''', + ); + }); + + test('in alphabetical order', () async { + await testSuggestor( + input: /*language=dart*/ ''' + import 'package:react_material_ui/react_material_ui.dart' as mui; + import 'package:react_material_ui/styles/styled.dart' as mui; + + content() => Dom.div()(); + ''', + expectedOutput: /*language=dart*/ ''' + import 'package:unify_ui/styles/styled.dart' as unify; + import 'package:unify_ui/unify_ui.dart' as unify; content() => Dom.div()(); ''', @@ -79,21 +96,19 @@ void main() { content() => Dom.div()(); ''', expectedOutput: /*language=dart*/ ''' - import 'package:unify_ui/unify_ui.dart'; import 'package:over_react/over_react.dart'; - import 'package:web_skin_dart/ui_components.dart'; - import 'package:unify_ui/styles/styled.dart'; import 'package:unify_ui/components/list.dart'; import 'package:unify_ui/styles/styled.dart'; + import 'package:unify_ui/styles/styled.dart'; + import 'package:unify_ui/unify_ui.dart'; + import 'package:web_skin_dart/ui_components.dart'; content() => Dom.div()(); ''', ); }); - test( - 'unless there is a namespace on the main entrypoints that will be updated by a different suggestor', - () async { + test('with namespaces', () async { await testSuggestor( input: /*language=dart*/ ''' library lib; @@ -103,17 +118,19 @@ void main() { import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; import 'package:over_react/over_react.dart' as mui; import 'package:react_material_ui/components/badge.dart' as mui; + import 'package:react_material_ui/components/alert.dart' as something_else; content() => Dom.div()(); ''', expectedOutput: /*language=dart*/ ''' library lib; - import 'package:react_material_ui/react_material_ui.dart' as mui; - import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_mui; - import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; import 'package:over_react/over_react.dart' as mui; + import 'package:unify_ui/components/alert.dart' as something_else; import 'package:unify_ui/components/badge.dart' as unify; + import 'package:unify_ui/unify_ui.dart' as unify; + import 'package:unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_unify; + import 'package:unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_unify; content() => Dom.div()(); ''', @@ -139,24 +156,24 @@ void main() { test('also works for other package name inputs', () async { final testSuggestor = getSuggestorTester( importRenamerSuggestorBuilder( - oldPackageName: 'test_old', - newPackageName: 'test_new', - oldPackageNamespace: 'old', - newPackageNamespace: 'new', + oldPackageName: 'old', + newPackageName: 'new', + oldPackageNamespace: 'o', + newPackageNamespace: 'n', ), ); await testSuggestor( input: /*language=dart*/ ''' - import 'package:test_old/abc.dart' as mui; import 'package:over_react/over_react.dart'; - import 'package:test_old/components/badge.dart'; + import 'package:old/old.dart' as o; + import 'package:old/components/badge.dart'; content() => Dom.div()(); ''', expectedOutput: /*language=dart*/ ''' - import 'package:test_new/abc.dart' as mui; + import 'package:new/components/badge.dart'; + import 'package:new/old.dart' as n; import 'package:over_react/over_react.dart'; - import 'package:test_new/components/badge.dart'; content() => Dom.div()(); ''', diff --git a/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart b/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart index 2ab95b4c..83be014e 100644 --- a/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart +++ b/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart @@ -239,6 +239,8 @@ void main() { mui.Alert()(); mui.Alert(); mui.Alert; + mui.AlertSize.small; + AlertSize.small; Alert()(); random_rmui_namespace.Alert()(); mui.LinkButton()(); @@ -260,6 +262,8 @@ void main() { unify_wsd.WsdAlert()(); unify_wsd.WsdAlert(); unify_wsd.WsdAlert; + unify_wsd.WsdAlertSize.small; + unify_wsd.WsdAlertSize.small; unify_wsd.WsdAlert()(); unify_wsd.WsdAlert()(); unify_wsd.WsdLinkButton()(); @@ -370,6 +374,7 @@ void main() { }); }); + // test existing fixme group('fixme comments', () { test('for specific components that need manual intervention', () async { await testSuggestor( From 1ce706829fe25958be6afa8d57cb212c4b91fdd6 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Tue, 17 Oct 2023 15:13:07 -0700 Subject: [PATCH 13/29] More clean up --- lib/src/executables/unify_package_rename.dart | 21 +-- .../constants.dart | 12 +- .../import_renamer.dart | 123 +----------------- .../import_renamer_test.dart | 2 - .../unify_rename_suggestor_test.dart | 3 - 5 files changed, 18 insertions(+), 143 deletions(-) diff --git a/lib/src/executables/unify_package_rename.dart b/lib/src/executables/unify_package_rename.dart index c36a92f8..d133e884 100644 --- a/lib/src/executables/unify_package_rename.dart +++ b/lib/src/executables/unify_package_rename.dart @@ -25,7 +25,6 @@ import 'package:over_react_codemod/src/unify_package_rename_suggestors/import_re import 'package:over_react_codemod/src/unify_package_rename_suggestors/unify_rename_suggestor.dart'; import 'package:over_react_codemod/src/util.dart'; -import '../dependency_validator_suggestors/ignore_updaters/v3_updater.dart'; import '../util/importer.dart'; import '../util/unused_import_remover.dart'; @@ -66,17 +65,6 @@ void main(List args) async { } exitCode = await runCodemods([ - // todo can we also remove rmui dependency here??? - // Add unify_ui dependency. - // CodemodInfo(paths: pubspecYamlPaths(), sequence: [ - // aggregate( - // [ - // // todo update version: - // PubspecUpgrader('unify_ui', parseVersionRange('^1.121.0'), - // hostedUrl: 'https://pub.workiva.org', shouldAddDependencies: true), - // ].map((s) => ignoreable(s)), - // ) - // ]), // Update RMUI bundle script in all HTML files (and templates) to Unify bundle. CodemodInfo(paths: allHtmlPathsIncludingTemplates(), sequence: [ HtmlScriptUpdater(rmuiBundleDevUpdated, unifyBundleDev), @@ -87,9 +75,6 @@ void main(List args) async { DartScriptUpdater(rmuiBundleDevUpdated, unifyBundleDev), DartScriptUpdater(rmuiBundleProdUpdated, unifyBundleProd), ]), - CodemodInfo( - paths: pubspecYamlPaths(), - sequence: [V3DependencyValidatorUpdater('react_material_ui', remove: true)]) ]); if (exitCode != 0) return; @@ -101,15 +86,16 @@ void main(List args) async { await pubGetForAllPackageRoots(dartPaths); exitCode = await runCodemods([ - // todo add comments + // Make main rename updates. CodemodInfo(paths: dartPaths, sequence: [UnifyRenameSuggestor()]), - // CodemodInfo(paths: dartPaths, sequence: [PackageRenameComponentUsageMigrator()]), + // Add WSD entrypoint imports as needed. CodemodInfo(paths: dartPaths, sequence: [ importerSuggestorBuilder( importUri: unifyWsdUri, importNamespace: unifyWsdNamespace, ) ]), + // Update rmui imports to unify. CodemodInfo(paths: dartPaths, sequence: [ importRenamerSuggestorBuilder( oldPackageName: 'react_material_ui', @@ -118,6 +104,7 @@ void main(List args) async { newPackageNamespace: 'unify', ) ]), + // Remove any left over unused imports. CodemodInfo(paths: dartPaths, sequence: [ unusedImportRemoverSuggestorBuilder('react_material_ui'), unusedImportRemoverSuggestorBuilder('unify_ui'), diff --git a/lib/src/unify_package_rename_suggestors/constants.dart b/lib/src/unify_package_rename_suggestors/constants.dart index ec342765..a7d8e8a4 100644 --- a/lib/src/unify_package_rename_suggestors/constants.dart +++ b/lib/src/unify_package_rename_suggestors/constants.dart @@ -12,12 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -// todo comment +/// Info on a unify_ui import. class UnifyImportInfo { UnifyImportInfo(this.uri, {this.rmuiUri, this.namespace, this.possibleMuiNamespaces}); + + /// Unify import URI. + String uri; + + /// Recommended Unify version of the import namespace, if applicable. String? namespace; + + /// List of common RMUI versions of the namespace for the import, if applicable. List? possibleMuiNamespaces; - String uri; + + /// Previous RMUI import URI (if it's different from the unify_ui path). String? rmuiUri; } diff --git a/lib/src/unify_package_rename_suggestors/import_renamer.dart b/lib/src/unify_package_rename_suggestors/import_renamer.dart index 4557899c..f67737fb 100644 --- a/lib/src/unify_package_rename_suggestors/import_renamer.dart +++ b/lib/src/unify_package_rename_suggestors/import_renamer.dart @@ -19,70 +19,6 @@ import '../util/importer.dart'; import 'constants.dart'; /// Suggestor that updates imports from [oldPackageName] to [newPackageName]. -// class ImportRenamer extends RecursiveAstVisitor with AstVisitingSuggestor { -// final String oldPackageName; -// final String newPackageName; -// final String oldPackageNamespace; -// final String newPackageNamespace; -// -// ImportRenamer( -// {required this.oldPackageName, -// required this.newPackageName, -// required this.oldPackageNamespace, -// required this.newPackageNamespace}); -// -// @override -// Future visitImportDirective(ImportDirective node) async { -// super.visitImportDirective(node); -// -// final importUri = node.uri.stringValue; -// final namespace = node.prefix; -// if (importUri != null && importUri.startsWith('package:$oldPackageName/')) { -// final specialCaseRmuiImport = -// rmuiImportsToUpdate.where((import) => importUri == import.rmuiUri); -// if (specialCaseRmuiImport.isNotEmpty) { -// yieldPatch('\'${specialCaseRmuiImport.single.uri}\'', node.uri.offset, node.uri.end); -// -// final newNamespace = specialCaseRmuiImport.single.namespace; -// if (namespace != null && -// newNamespace != null && -// (specialCaseRmuiImport.single.possibleMuiNamespaces?.contains(namespace.name) ?? -// false)) { -// yieldPatch(newNamespace, namespace.offset, namespace.end); -// } -// } else { -// yieldPatch( -// '\'${importUri.replaceFirst('package:$oldPackageName/', 'package:$newPackageName/')}\'', -// node.uri.offset, -// node.uri.end); -// } -// -// // Update the namespace if necessary. -// if (namespace != null && namespace.name == oldPackageNamespace) { -// yieldPatch(newPackageNamespace, namespace.offset, namespace.end); -// } -// -// // final libraryResult = await context.getResolvedLibrary(); -// // if (libraryResult == null) { -// // // Most likely a part and not a library. -// // return; -// // } -// // // Parts that have not been generated can show up as `exists = false` but also `isPart = false`, -// // // so using the unitResults is a little trickier than using the libraryElement to get it. -// // final mainLibraryUnitResult = libraryResult.units.singleWhere((unitResult) => -// // unitResult.unit.declaredElement == libraryResult.element.definingCompilationUnit); -// // final insertInfo = insertionLocationForPackageImport( -// // importUri, mainLibraryUnitResult.unit, mainLibraryUnitResult.lineInfo); -// // yield Patch( -// // insertInfo.leadingNewlines + -// // "import '$importUri' as $importNamespace;" + -// // insertInfo.trailingNewlines, -// // insertInfo.offset, -// // insertInfo.offset); -// } -// } -// } - Suggestor importRenamerSuggestorBuilder({ required String oldPackageName, required String newPackageName, @@ -113,6 +49,8 @@ Suggestor importRenamerSuggestorBuilder({ var newImportUri = importUri?.replaceFirst('package:$oldPackageName/', 'package:$newPackageName/'); var newNamespace = namespace == oldPackageNamespace ? newPackageNamespace : namespace; + + // Check for special cases where the unify_ui import path does not match the previous RMUI path. final specialCaseRmuiImport = rmuiImportsToUpdate.where((i) => importUri == i.rmuiUri); if (specialCaseRmuiImport.isNotEmpty) { newImportUri = specialCaseRmuiImport.single.uri; @@ -126,6 +64,7 @@ Suggestor importRenamerSuggestorBuilder({ } if (newImportUri != null) { + // Collect info on new imports to add. newImportsInfo.add(UnifyImportInfo(newImportUri, namespace: newNamespace)); } @@ -140,6 +79,7 @@ Suggestor importRenamerSuggestorBuilder({ // Sort imports before adding them. newImportsInfo.sort((a, b) => a.uri.compareTo(b.uri)); + // Add imports in their alphabetical positions. for (final importInfo in newImportsInfo) { final insertInfo = insertionLocationForPackageImport( importInfo.uri, mainLibraryUnitResult.unit, mainLibraryUnitResult.lineInfo); @@ -150,60 +90,5 @@ Suggestor importRenamerSuggestorBuilder({ insertInfo.offset, insertInfo.offset); } - - // if (newImportUri != null) { - // final insertInfo = insertionLocationForPackageImport( - // newImportUri, mainLibraryUnitResult.unit, mainLibraryUnitResult.lineInfo); - // yield Patch( - // insertInfo.leadingNewlines + - // "import '$newImportUri'${newNamespace != null ? ' as $newNamespace' : ''};" + - // insertInfo.trailingNewlines, - // insertInfo.offset, - // insertInfo.offset); - // } - // - // final prevTokenEnd = import.beginToken.previous?.end; - // // Try to take the newline before the import, but watch out - // // for prevToken's offset/end being -1 if it's this import has the - // // first token in the file. - // final startOffset = prevTokenEnd != null && prevTokenEnd != -1 ? prevTokenEnd : import.offset; - // yield Patch('', startOffset, import.end); - // } - - // for(final import in importsToUpdate) { - // - // } - // if (importUri != null && importUri.startsWith('package:$oldPackageName/')) { - // if (specialCaseRmuiImport.isNotEmpty) { - // yieldPatch('\'${specialCaseRmuiImport.single.uri}\'', node.uri.offset, node.uri.end); - // - // final newNamespace = specialCaseRmuiImport.single.namespace; - // if (namespace != null && - // newNamespace != null && - // (specialCaseRmuiImport.single.possibleMuiNamespaces?.contains(namespace.name) ?? - // false)) { - // yieldPatch(newNamespace, namespace.offset, namespace.end); - // } - // } else { - // yieldPatch( - // '\'${importUri.replaceFirst('package:$oldPackageName/', 'package:$newPackageName/')}\'', - // node.uri.offset, - // node.uri.end); - // } - // - // // Update the namespace if necessary. - // if (namespace != null && namespace.name == oldPackageNamespace) { - // yieldPatch(newPackageNamespace, namespace.offset, namespace.end); - // } - - // Look for errors in the main compilation unit and its part files. - // Ignore null partContexts and partContexts elements caused by - // resolution issues and parts being excluded in the codemod file list. - // final needsMuiImport = libraryResult.units - // .expand((unitResult) => unitResult.errors) - // .where((error) => error.errorCode.name == 'UNDEFINED_IDENTIFIER') - // .any((error) => error.message.contains("Undefined name '$importNamespace'")); - // - // if (!needsMuiImport) return; }; } diff --git a/test/unify_package_rename_suggestors/import_renamer_test.dart b/test/unify_package_rename_suggestors/import_renamer_test.dart index 4bcad7ad..d17939cb 100644 --- a/test/unify_package_rename_suggestors/import_renamer_test.dart +++ b/test/unify_package_rename_suggestors/import_renamer_test.dart @@ -17,8 +17,6 @@ import 'package:test/test.dart'; import '../util.dart'; -// todo add import order tests - void main() { group('importRenamer', () { final testSuggestor = getSuggestorTester( diff --git a/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart b/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart index 83be014e..2211d85c 100644 --- a/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart +++ b/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart @@ -18,9 +18,6 @@ import 'package:test/test.dart'; import '../resolved_file_context.dart'; import '../util.dart'; -// todo also test builders -// todo also test non-component usage of the namespace - void main() { final resolvedContext = SharedAnalysisContext.rmui; From ad9a1d642a98fc69f520e2bed1199eb4eea5d894 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Tue, 17 Oct 2023 15:19:01 -0700 Subject: [PATCH 14/29] Try to fix diff --- lib/src/executables/mui_migration.dart | 15 +++-- .../unused_wsd_import_remover.dart | 55 +++++++++++++++++++ .../unused_import_remover_test.dart} | 0 3 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 lib/src/mui_suggestors/unused_wsd_import_remover.dart rename test/{mui_suggestors/unused_wsd_import_remover_test.dart => util/unused_import_remover_test.dart} (100%) diff --git a/lib/src/executables/mui_migration.dart b/lib/src/executables/mui_migration.dart index 8eb6f919..c6c8c65d 100644 --- a/lib/src/executables/mui_migration.dart +++ b/lib/src/executables/mui_migration.dart @@ -96,7 +96,8 @@ void main(List args) async { final parsedArgs = parser.parse(args); if (parsedArgs['help'] as bool) { - stderr.writeln('Migrates web_skin_dart component usages to react_material_ui.'); + stderr.writeln( + 'Migrates web_skin_dart component usages to react_material_ui.'); stderr.writeln(); stderr.writeln('Usage:'); stderr.writeln(' mui_migration [arguments]'); @@ -113,8 +114,10 @@ void main(List args) async { // // An alternative would be to use `--` and `arguments.rest` to pass along codemod // args, but that's not as convenient to the user and makes showing help a bit more complicated. - final codemodArgs = - _allCodemodFlags.where((name) => parsedArgs[name] as bool).map((name) => '--$name').toList(); + final codemodArgs = _allCodemodFlags + .where((name) => parsedArgs[name] as bool) + .map((name) => '--$name') + .toList(); // codemod sets up a global logging handler that forwards to the console, and // we want that set up before we do other non-codemodd things that might log. @@ -197,7 +200,8 @@ void main(List args) async { exitCode = await runInteractiveCodemod( pubspecYamlPaths(), aggregate([ - PubspecUpgrader('react_material_ui', rmuiVersionRange, hostedUrl: 'https://pub.workiva.org'), + PubspecUpgrader('react_material_ui', rmuiVersionRange, + hostedUrl: 'https://pub.workiva.org'), ].map((s) => ignoreable(s))), defaultYes: true, args: codemodArgs, @@ -228,7 +232,8 @@ void sortPartsLast(List dartPaths) { } Future pubGetForAllPackageRoots(Iterable files) async { - _log.info('Running `pub get` if needed so that all Dart files can be resolved...'); + _log.info( + 'Running `pub get` if needed so that all Dart files can be resolved...'); final packageRoots = files.map(findPackageRootFor).toSet(); for (final packageRoot in packageRoots) { await runPubGetIfNeeded(packageRoot); diff --git a/lib/src/mui_suggestors/unused_wsd_import_remover.dart b/lib/src/mui_suggestors/unused_wsd_import_remover.dart new file mode 100644 index 00000000..5cb64296 --- /dev/null +++ b/lib/src/mui_suggestors/unused_wsd_import_remover.dart @@ -0,0 +1,55 @@ +// Copyright 2021 Workiva Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/dart/ast/syntactic_entity.dart'; +import 'package:codemod/codemod.dart'; +import 'package:logging/logging.dart'; + +final _log = Logger('unusedWsdImportRemover'); + +/// Creates a suggestor that removes unused [package] imports. +Suggestor unusedImportRemoverSuggestorBuilder(String package) { + return (context) async* { + final unitResult = await context.getResolvedUnit(); + if (unitResult == null) { + // Most likely a part and not a library. + return; + } + final unusedImportErrors = unitResult.errors + .where((error) => error.errorCode.name.toLowerCase() == 'unused_import') + .toList(); + + final allImports = unitResult.unit.directives.whereType().toList(); + + for (final error in unusedImportErrors) { + final matchingImport = + allImports.singleWhere((import) => import.containsOffset(error.offset)); + final importUri = matchingImport.uri.stringValue; + if (importUri != null && importUri.startsWith('package:$package/')) { + final prevTokenEnd = matchingImport.beginToken.previous?.end; + // Try to take the newline before the import, but watch out + // for prevToken's offset/end being -1 if it's this import has the + // first token in the file. + final startOffset = + prevTokenEnd != null && prevTokenEnd != -1 ? prevTokenEnd : matchingImport.offset; + yield Patch('', startOffset, matchingImport.end); + } + } + }; +} + +extension on SyntacticEntity { + bool containsOffset(int offset) => offset >= this.offset && offset < end; +} diff --git a/test/mui_suggestors/unused_wsd_import_remover_test.dart b/test/util/unused_import_remover_test.dart similarity index 100% rename from test/mui_suggestors/unused_wsd_import_remover_test.dart rename to test/util/unused_import_remover_test.dart From b2d70f7f47e7a2f42f29bae4c39e7f80b23216a2 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Tue, 17 Oct 2023 15:19:56 -0700 Subject: [PATCH 15/29] Try to fix diff --- lib/src/util/unused_import_remover.dart | 55 ------------------------- 1 file changed, 55 deletions(-) delete mode 100644 lib/src/util/unused_import_remover.dart diff --git a/lib/src/util/unused_import_remover.dart b/lib/src/util/unused_import_remover.dart deleted file mode 100644 index bc45f7a1..00000000 --- a/lib/src/util/unused_import_remover.dart +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2021 Workiva Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/dart/ast/syntactic_entity.dart'; -import 'package:codemod/codemod.dart'; -import 'package:logging/logging.dart'; - -final _log = Logger('unusedWsdImportRemover'); - -/// Creates a suggestor that removes unused [package] imports. -Suggestor unusedImportRemoverSuggestorBuilder(String package) { - return (context) async* { - final unitResult = await context.getResolvedUnit(); - if (unitResult == null) { - // Most likely a part and not a library. - return; - } - final unusedImportErrors = unitResult.errors - .where((error) => error.errorCode.name.toLowerCase() == 'unused_import') - .toList(); - - final allImports = unitResult.unit.directives.whereType().toList(); - - for (final error in unusedImportErrors) { - final matchingImport = - allImports.singleWhere((import) => import.containsOffset(error.offset)); - final importUri = matchingImport.uri.stringValue; - if (importUri != null && importUri.startsWith('package:$package/')) { - final prevTokenEnd = matchingImport.beginToken.previous?.end; - // Try to take the newline before the import, but watch out - // for prevToken's offset/end being -1 if it's this import has the - // first token in the file. - final startOffset = - prevTokenEnd != null && prevTokenEnd != -1 ? prevTokenEnd : matchingImport.offset; - yield Patch('', startOffset, matchingImport.end); - } - } - }; -} - -extension on SyntacticEntity { - bool containsOffset(int offset) => offset >= this.offset && offset < end; -} From 254c3c4e5d5f79cbb6d6e80a72db855796709634 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Tue, 17 Oct 2023 15:20:26 -0700 Subject: [PATCH 16/29] Try to fix diff --- .../unused_import_remover.dart} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/src/{mui_suggestors/unused_wsd_import_remover.dart => util/unused_import_remover.dart} (100%) diff --git a/lib/src/mui_suggestors/unused_wsd_import_remover.dart b/lib/src/util/unused_import_remover.dart similarity index 100% rename from lib/src/mui_suggestors/unused_wsd_import_remover.dart rename to lib/src/util/unused_import_remover.dart From bd6e194ab806c448f6a57b74a7b65576b4780379 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Tue, 17 Oct 2023 15:26:12 -0700 Subject: [PATCH 17/29] Format --- lib/src/executables/mui_migration.dart | 4 +- .../constants.dart | 6 +-- .../constants.dart | 6 ++- .../import_renamer.dart | 35 +++++++++++------ .../unify_rename_suggestor.dart | 38 ++++++++++++------- lib/src/util/importer.dart | 31 +++++++++------ lib/src/util/unused_import_remover.dart | 12 +++--- test/resolved_file_context.dart | 4 +- .../import_renamer_test.dart | 4 +- .../unify_rename_suggestor_test.dart | 6 ++- test/util/importer_test.dart | 31 +++++++++------ 11 files changed, 113 insertions(+), 64 deletions(-) diff --git a/lib/src/executables/mui_migration.dart b/lib/src/executables/mui_migration.dart index c6c8c65d..87570d1e 100644 --- a/lib/src/executables/mui_migration.dart +++ b/lib/src/executables/mui_migration.dart @@ -190,7 +190,9 @@ void main(List args) async { // should only be handled by a single migrator, and shouldn't depend on the // output of previous migrators. [aggregate(migratorsToRun)], - [importerSuggestorBuilder(importUri: rmuiImportUri, importNamespace: muiNs)], + [ + importerSuggestorBuilder(importUri: rmuiImportUri, importNamespace: muiNs) + ], [unusedImportRemoverSuggestorBuilder('web_skin_dart')], ]); if (exitCode != 0) return; diff --git a/lib/src/rmui_bundle_update_suggestors/constants.dart b/lib/src/rmui_bundle_update_suggestors/constants.dart index 4a208e89..b8fe71e0 100644 --- a/lib/src/rmui_bundle_update_suggestors/constants.dart +++ b/lib/src/rmui_bundle_update_suggestors/constants.dart @@ -28,12 +28,10 @@ const rmuiBundleProdUpdated = 'packages/react_material_ui/js/react-material-ui.browser.min.esm.js'; /// The script for the dev Unify bundle. -const unifyBundleDev = - 'packages/unify_ui/js/unify-ui.browser.dev.esm.js'; +const unifyBundleDev = 'packages/unify_ui/js/unify-ui.browser.dev.esm.js'; /// The script for the prod Unify bundle. -const unifyBundleProd = - 'packages/unify_ui/js/unify-ui.browser.min.esm.js'; +const unifyBundleProd = 'packages/unify_ui/js/unify-ui.browser.min.esm.js'; /// The type attribute that needs to be added to script tags for the new RMUI bundles. final typeModuleAttribute = 'type="module"'; diff --git a/lib/src/unify_package_rename_suggestors/constants.dart b/lib/src/unify_package_rename_suggestors/constants.dart index a7d8e8a4..9625e62c 100644 --- a/lib/src/unify_package_rename_suggestors/constants.dart +++ b/lib/src/unify_package_rename_suggestors/constants.dart @@ -14,7 +14,8 @@ /// Info on a unify_ui import. class UnifyImportInfo { - UnifyImportInfo(this.uri, {this.rmuiUri, this.namespace, this.possibleMuiNamespaces}); + UnifyImportInfo(this.uri, + {this.rmuiUri, this.namespace, this.possibleMuiNamespaces}); /// Unify import URI. String uri; @@ -117,7 +118,8 @@ const rmuiToUnifyIdentifierRenames = { 'SnackbarAnchorOriginObjectVertical': 'SnackbarOriginVertical', 'SnackbarAnchorOriginObjectHorizontal': 'SnackbarOriginHorizontal', // TablePagination objects - 'TablePaginationLabelDisplayedRowsObject': 'TablePaginationLabelDisplayedRowsArgs', + 'TablePaginationLabelDisplayedRowsObject': + 'TablePaginationLabelDisplayedRowsArgs', }; /// The namespace that will be used for the `unify_ui/components/wsd.dart` import that is added. diff --git a/lib/src/unify_package_rename_suggestors/import_renamer.dart b/lib/src/unify_package_rename_suggestors/import_renamer.dart index f67737fb..8b14823e 100644 --- a/lib/src/unify_package_rename_suggestors/import_renamer.dart +++ b/lib/src/unify_package_rename_suggestors/import_renamer.dart @@ -34,45 +34,56 @@ Suggestor importRenamerSuggestorBuilder({ // Parts that have not been generated can show up as `exists = false` but also `isPart = false`, // so using the unitResults is a little trickier than using the libraryElement to get it. - final mainLibraryUnitResult = libraryResult.units.singleWhere((unitResult) => - unitResult.unit.declaredElement == libraryResult.element.definingCompilationUnit); + final mainLibraryUnitResult = libraryResult.units.singleWhere( + (unitResult) => + unitResult.unit.declaredElement == + libraryResult.element.definingCompilationUnit); // Look for imports with old package name. final importsToUpdate = mainLibraryUnitResult.unit.directives .whereType() - .where((import) => import.uri.stringValue?.startsWith('package:$oldPackageName/') ?? false); + .where((import) => + import.uri.stringValue?.startsWith('package:$oldPackageName/') ?? + false); final newImportsInfo = []; for (final import in importsToUpdate) { final importUri = import.uri.stringValue; final namespace = import.prefix?.name; - var newImportUri = - importUri?.replaceFirst('package:$oldPackageName/', 'package:$newPackageName/'); - var newNamespace = namespace == oldPackageNamespace ? newPackageNamespace : namespace; + var newImportUri = importUri?.replaceFirst( + 'package:$oldPackageName/', 'package:$newPackageName/'); + var newNamespace = + namespace == oldPackageNamespace ? newPackageNamespace : namespace; // Check for special cases where the unify_ui import path does not match the previous RMUI path. - final specialCaseRmuiImport = rmuiImportsToUpdate.where((i) => importUri == i.rmuiUri); + final specialCaseRmuiImport = + rmuiImportsToUpdate.where((i) => importUri == i.rmuiUri); if (specialCaseRmuiImport.isNotEmpty) { newImportUri = specialCaseRmuiImport.single.uri; final specialCaseNamespace = specialCaseRmuiImport.single.namespace; if (namespace != null && specialCaseNamespace != null && - (specialCaseRmuiImport.single.possibleMuiNamespaces?.contains(namespace) ?? false)) { + (specialCaseRmuiImport.single.possibleMuiNamespaces + ?.contains(namespace) ?? + false)) { newNamespace = specialCaseNamespace; } } if (newImportUri != null) { // Collect info on new imports to add. - newImportsInfo.add(UnifyImportInfo(newImportUri, namespace: newNamespace)); + newImportsInfo + .add(UnifyImportInfo(newImportUri, namespace: newNamespace)); } final prevTokenEnd = import.beginToken.previous?.end; // Try to take the newline before the import, but watch out // for prevToken's offset/end being -1 if it's this import has the // first token in the file. - final startOffset = prevTokenEnd != null && prevTokenEnd != -1 ? prevTokenEnd : import.offset; + final startOffset = prevTokenEnd != null && prevTokenEnd != -1 + ? prevTokenEnd + : import.offset; yield Patch('', startOffset, import.end); } @@ -81,8 +92,8 @@ Suggestor importRenamerSuggestorBuilder({ // Add imports in their alphabetical positions. for (final importInfo in newImportsInfo) { - final insertInfo = insertionLocationForPackageImport( - importInfo.uri, mainLibraryUnitResult.unit, mainLibraryUnitResult.lineInfo); + final insertInfo = insertionLocationForPackageImport(importInfo.uri, + mainLibraryUnitResult.unit, mainLibraryUnitResult.lineInfo); yield Patch( insertInfo.leadingNewlines + "import '${importInfo.uri}'${importInfo.namespace != null ? ' as ${importInfo.namespace}' : ''};" + diff --git a/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart b/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart index ef4d66f6..193a6df8 100644 --- a/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart +++ b/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart @@ -44,16 +44,20 @@ class UnifyRenameSuggestor extends GeneralizingAstVisitor with ClassSuggestor { // Replace 'mui' namespaces usage with 'unify' for method invocations. final uri = node.methodName.staticElement?.source?.uri; if (uri != null && - (isUriWithinPackage(uri, 'react_material_ui') || isUriWithinPackage(uri, 'unify_ui'))) { + (isUriWithinPackage(uri, 'react_material_ui') || + isUriWithinPackage(uri, 'unify_ui'))) { final importNamespace = node.target; if (importNamespace != null) { final newImportNamespace = rmuiImportsToUpdate .where((import) => - import.possibleMuiNamespaces?.contains(importNamespace.toSource()) ?? false) + import.possibleMuiNamespaces + ?.contains(importNamespace.toSource()) ?? + false) .singleOrNull ?.namespace; if (newImportNamespace != null) { - yieldPatch(newImportNamespace, importNamespace.offset, importNamespace.end); + yieldPatch( + newImportNamespace, importNamespace.offset, importNamespace.end); } } } @@ -68,13 +72,14 @@ class UnifyRenameSuggestor extends GeneralizingAstVisitor with ClassSuggestor { return; } - final identifier = - node.tryCast() ?? node.tryCast()?.identifier; + final identifier = node.tryCast() ?? + node.tryCast()?.identifier; final uri = identifier?.staticElement?.source?.uri; final prefix = node.tryCast()?.prefix; if (uri != null && - (isUriWithinPackage(uri, 'react_material_ui') || isUriWithinPackage(uri, 'unify_ui'))) { + (isUriWithinPackage(uri, 'react_material_ui') || + isUriWithinPackage(uri, 'unify_ui'))) { // Update components and objects that were renamed in unify_ui. final newName = rmuiToUnifyIdentifierRenames[identifier?.name]; var isFromWsdEntrypoint = newName?.startsWith('Wsd') ?? false; @@ -92,13 +97,17 @@ class UnifyRenameSuggestor extends GeneralizingAstVisitor with ClassSuggestor { // Update WSD ButtonColor and AlertSize usages. { // Update WSD constant properties objects to use the WSD versions if applicable. - yieldWsdRenamePatchIfApplicable(Expression node, String? objectName, String? propertyName) { - if (objectName == 'ButtonColor' && (propertyName?.startsWith('wsd') ?? false)) { + yieldWsdRenamePatchIfApplicable( + Expression node, String? objectName, String? propertyName) { + if (objectName == 'ButtonColor' && + (propertyName?.startsWith('wsd') ?? false)) { isFromWsdEntrypoint = true; - yieldPatch('$unifyWsdNamespace.WsdButtonColor.$propertyName', node.offset, node.end); + yieldPatch('$unifyWsdNamespace.WsdButtonColor.$propertyName', + node.offset, node.end); } else if (objectName == 'AlertSize') { isFromWsdEntrypoint = true; - yieldPatch('$unifyWsdNamespace.WsdAlertSize.$propertyName', node.offset, node.end); + yieldPatch('$unifyWsdNamespace.WsdAlertSize.$propertyName', + node.offset, node.end); } } @@ -107,13 +116,15 @@ class UnifyRenameSuggestor extends GeneralizingAstVisitor with ClassSuggestor { yieldWsdRenamePatchIfApplicable(node, prefix?.name, identifier?.name); // Check for namespaced `mui.ButtonColor.wsd...` usage. if (node is PrefixedIdentifier && parent is PropertyAccess) { - yieldWsdRenamePatchIfApplicable(parent, identifier?.name, parent.propertyName.name); + yieldWsdRenamePatchIfApplicable( + parent, identifier?.name, parent.propertyName.name); } } // Replace 'mui' namespaces usage with 'unify'. final newNamespace = rmuiImportsToUpdate - .where((import) => import.possibleMuiNamespaces?.contains(prefix?.name) ?? false) + .where((import) => + import.possibleMuiNamespaces?.contains(prefix?.name) ?? false) .singleOrNull ?.namespace; if (prefix != null && newNamespace != null && !isFromWsdEntrypoint) { @@ -136,7 +147,8 @@ class UnifyRenameSuggestor extends GeneralizingAstVisitor with ClassSuggestor { final result = await context.getResolvedUnit(); if (result == null) { - throw Exception('Could not get resolved result for "${context.relativePath}"'); + throw Exception( + 'Could not get resolved result for "${context.relativePath}"'); } result.unit.visitChildren(this); } diff --git a/lib/src/util/importer.dart b/lib/src/util/importer.dart index bb57ad7c..ccef217e 100644 --- a/lib/src/util/importer.dart +++ b/lib/src/util/importer.dart @@ -35,8 +35,10 @@ Suggestor importerSuggestorBuilder({ // Parts that have not been generated can show up as `exists = false` but also `isPart = false`, // so using the unitResults is a little trickier than using the libraryElement to get it. - final mainLibraryUnitResult = libraryResult.units.singleWhere((unitResult) => - unitResult.unit.declaredElement == libraryResult.element.definingCompilationUnit); + final mainLibraryUnitResult = libraryResult.units.singleWhere( + (unitResult) => + unitResult.unit.declaredElement == + libraryResult.element.definingCompilationUnit); // Look for errors in the main compilation unit and its part files. // Ignore null partContexts and partContexts elements caused by @@ -44,7 +46,8 @@ Suggestor importerSuggestorBuilder({ final needsMuiImport = libraryResult.units .expand((unitResult) => unitResult.errors) .where((error) => error.errorCode.name == 'UNDEFINED_IDENTIFIER') - .any((error) => error.message.contains("Undefined name '$importNamespace'")); + .any((error) => + error.message.contains("Undefined name '$importNamespace'")); if (!needsMuiImport) return; @@ -84,16 +87,21 @@ _InsertionLocation insertionLocationForPackageImport( final imports = unit.directives.whereType(); final firstImport = imports.firstOrNull; - final dartImports = imports.where((i) => i.uri.stringValue?.startsWith('dart:') ?? false); + final dartImports = + imports.where((i) => i.uri.stringValue?.startsWith('dart:') ?? false); final lastDartImport = dartImports.lastOrNull; - final packageImports = imports.where((i) => i.uri.stringValue?.startsWith('package:') ?? false); - final firstPackageImportSortedAfterNewImport = - packageImports.where((i) => i.uri.stringValue!.compareTo(importUri) > 0).firstOrNull; - final lastPackageImportSortedBeforeNewImport = - packageImports.where((i) => i.uri.stringValue!.compareTo(importUri) < 0).lastOrNull; + final packageImports = + imports.where((i) => i.uri.stringValue?.startsWith('package:') ?? false); + final firstPackageImportSortedAfterNewImport = packageImports + .where((i) => i.uri.stringValue!.compareTo(importUri) > 0) + .firstOrNull; + final lastPackageImportSortedBeforeNewImport = packageImports + .where((i) => i.uri.stringValue!.compareTo(importUri) < 0) + .lastOrNull; - final firstNonImportDirective = unit.directives.where((d) => d is! ImportDirective).firstOrNull; + final firstNonImportDirective = + unit.directives.where((d) => d is! ImportDirective).firstOrNull; final AstNode relativeNode; final bool insertAfter; @@ -124,7 +132,8 @@ _InsertionLocation insertionLocationForPackageImport( } else { // No directive to insert relative to; insert before the first member or // at the beginning of the file. - return _InsertionLocation(unit.declarations.firstOrNull?.offset ?? 0, trailingNewlineCount: 2); + return _InsertionLocation(unit.declarations.firstOrNull?.offset ?? 0, + trailingNewlineCount: 2); } return _InsertionLocation( diff --git a/lib/src/util/unused_import_remover.dart b/lib/src/util/unused_import_remover.dart index 5cb64296..07aeb559 100644 --- a/lib/src/util/unused_import_remover.dart +++ b/lib/src/util/unused_import_remover.dart @@ -31,19 +31,21 @@ Suggestor unusedImportRemoverSuggestorBuilder(String package) { .where((error) => error.errorCode.name.toLowerCase() == 'unused_import') .toList(); - final allImports = unitResult.unit.directives.whereType().toList(); + final allImports = + unitResult.unit.directives.whereType().toList(); for (final error in unusedImportErrors) { - final matchingImport = - allImports.singleWhere((import) => import.containsOffset(error.offset)); + final matchingImport = allImports + .singleWhere((import) => import.containsOffset(error.offset)); final importUri = matchingImport.uri.stringValue; if (importUri != null && importUri.startsWith('package:$package/')) { final prevTokenEnd = matchingImport.beginToken.previous?.end; // Try to take the newline before the import, but watch out // for prevToken's offset/end being -1 if it's this import has the // first token in the file. - final startOffset = - prevTokenEnd != null && prevTokenEnd != -1 ? prevTokenEnd : matchingImport.offset; + final startOffset = prevTokenEnd != null && prevTokenEnd != -1 + ? prevTokenEnd + : matchingImport.offset; yield Patch('', startOffset, matchingImport.end); } } diff --git a/test/resolved_file_context.dart b/test/resolved_file_context.dart index 4ecae919..e8ae738c 100644 --- a/test/resolved_file_context.dart +++ b/test/resolved_file_context.dart @@ -61,8 +61,8 @@ class SharedAnalysisContext { /// A context root located at `test/test_fixtures/rmui_project` /// that depends on the `react_material_ui` package (as well as `over_react`). - static final rmui = SharedAnalysisContext(p.join( - findPackageRootFor(p.current), 'test/test_fixtures/rmui_project')); + static final rmui = SharedAnalysisContext( + p.join(findPackageRootFor(p.current), 'test/test_fixtures/rmui_project')); /// The path to the package root in which test files will be created /// and resolved. diff --git a/test/unify_package_rename_suggestors/import_renamer_test.dart b/test/unify_package_rename_suggestors/import_renamer_test.dart index d17939cb..94ac5655 100644 --- a/test/unify_package_rename_suggestors/import_renamer_test.dart +++ b/test/unify_package_rename_suggestors/import_renamer_test.dart @@ -81,7 +81,9 @@ void main() { ); }); - test('for special cases when the new file path is different from the old one', () async { + test( + 'for special cases when the new file path is different from the old one', + () async { await testSuggestor( input: /*language=dart*/ ''' import 'package:react_material_ui/react_material_ui.dart'; diff --git a/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart b/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart index 2211d85c..5860c514 100644 --- a/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart +++ b/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart @@ -32,7 +32,8 @@ void main() { ); group('namespace on component usage', () { - test('mui namespace from react_material_ui is migrated to unify', () async { + test('mui namespace from react_material_ui is migrated to unify', + () async { await testSuggestor( input: /*language=dart*/ ''' import 'package:react_material_ui/react_material_ui.dart' as mui; @@ -67,7 +68,8 @@ void main() { ); }); - test('alpha namespace from react_material_ui is migrated to unify', () async { + test('alpha namespace from react_material_ui is migrated to unify', + () async { await testSuggestor( input: /*language=dart*/ ''' import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_mui; diff --git a/test/util/importer_test.dart b/test/util/importer_test.dart index 4d2798f5..2d2878fa 100644 --- a/test/util/importer_test.dart +++ b/test/util/importer_test.dart @@ -23,7 +23,8 @@ import '../util.dart'; void main() { group('importerSuggestorBuilder', () { final resolvedContext = SharedAnalysisContext.overReact; - final muiImporter = importerSuggestorBuilder(importUri: rmuiImportUri, importNamespace: muiNs); + final muiImporter = importerSuggestorBuilder( + importUri: rmuiImportUri, importNamespace: muiNs); // Warm up analysis in a setUpAll so that if getting the resolved AST times out // (which is more common for the WSD context), it fails here instead of failing the first test. @@ -36,7 +37,9 @@ void main() { resolvedContext: resolvedContext, ); - group('adds a RMUI import when there is an undefined `mui` identifier in the file', () { + group( + 'adds a RMUI import when there is an undefined `mui` identifier in the file', + () { bool isFakeUriError(AnalysisError error) => error.errorCode.name.toLowerCase() == 'uri_does_not_exist' && error.message.contains('fake'); @@ -294,24 +297,28 @@ void main() { }); }); - test('adds a RMUI import when there is an undefined `mui` identifier in a part file', () async { + test( + 'adds a RMUI import when there is an undefined `mui` identifier in a part file', + () async { // testSuggestor isn't really set up for multiple files, // so the test setup here is a little more manual. const partFilename = 'mui_importer_test_part.dart'; const mainLibraryFilename = 'mui_importer_test_main_library.dart'; - final partFileContext = await resolvedContext.resolvedFileContextForTest(''' + final partFileContext = + await resolvedContext.resolvedFileContextForTest(''' part of '${mainLibraryFilename}'; content() => mui.Button(); ''', - filename: partFilename, - // Don't pre-resolve since this isn't a library. - preResolveLibrary: false, - throwOnAnalysisErrors: false); + filename: partFilename, + // Don't pre-resolve since this isn't a library. + preResolveLibrary: false, + throwOnAnalysisErrors: false); - final mainLibraryFileContext = await resolvedContext.resolvedFileContextForTest( + final mainLibraryFileContext = + await resolvedContext.resolvedFileContextForTest( ''' part '${partFilename}'; ''', @@ -321,7 +328,8 @@ void main() { final mainPatches = await muiImporter(mainLibraryFileContext).toList(); expect(mainPatches, [ - hasPatchText(contains("import 'package:react_material_ui/react_material_ui.dart' as mui;")), + hasPatchText(contains( + "import 'package:react_material_ui/react_material_ui.dart' as mui;")), ]); final partPatches = await muiImporter(partFileContext).toList(); @@ -349,4 +357,5 @@ void main() { }); } -bool isUndefinedMuiError(AnalysisError error) => error.message.contains("Undefined name 'mui'"); +bool isUndefinedMuiError(AnalysisError error) => + error.message.contains("Undefined name 'mui'"); From 1315278e1832704252801def999a300880c41307 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Tue, 17 Oct 2023 15:37:46 -0700 Subject: [PATCH 18/29] Update tests --- pubspec.yaml | 4 ++++ test/util/importer_test.dart | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/pubspec.yaml b/pubspec.yaml index f41ac779..cda6aead 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -50,3 +50,7 @@ dependency_validator: ignore: - meta - mockito + # For testing purposes: + - unify_ui + - new + - old diff --git a/test/util/importer_test.dart b/test/util/importer_test.dart index 2d2878fa..30576429 100644 --- a/test/util/importer_test.dart +++ b/test/util/importer_test.dart @@ -353,6 +353,25 @@ void main() { ''', ); }); + + test('for a different package name', () async { + final testSuggestor = getSuggestorTester( + importerSuggestorBuilder(importUri: 'package:over_react/over_react.dart', importNamespace: 'or'), + resolvedContext: resolvedContext, + ); + await testSuggestor( + input: /*language=dart*/ ''' + + content() => or.Fragment(); + ''', + isExpectedError: (error) => error.message.contains("Undefined name 'or'"), + expectedOutput: /*language=dart*/ ''' + import 'package:over_react/over_react.dart' as or; + + content() => or.Fragment(); + ''', + ); + }); }); }); } From b6af6c920743640c14e6ea757bb9b285118b4791 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Tue, 17 Oct 2023 15:48:02 -0700 Subject: [PATCH 19/29] Fix dep validator issue --- pubspec.yaml | 4 - .../import_renamer_test.dart | 95 ++++++++++++------- test/util/importer_test.dart | 7 +- 3 files changed, 65 insertions(+), 41 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index cda6aead..f41ac779 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -50,7 +50,3 @@ dependency_validator: ignore: - meta - mockito - # For testing purposes: - - unify_ui - - new - - old diff --git a/test/unify_package_rename_suggestors/import_renamer_test.dart b/test/unify_package_rename_suggestors/import_renamer_test.dart index 94ac5655..5e0a2822 100644 --- a/test/unify_package_rename_suggestors/import_renamer_test.dart +++ b/test/unify_package_rename_suggestors/import_renamer_test.dart @@ -40,10 +40,11 @@ void main() { ); }); + // All tests strings are split by package name to work around issues with dependency_validator. group('updates react_material_ui imports in a file', () { test('', () async { await testSuggestor( - input: /*language=dart*/ ''' + input: ''' import 'package:over_react/over_react.dart'; import 'package:react_material_ui/react_material_ui.dart'; import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart'; @@ -52,12 +53,16 @@ void main() { content() => Dom.div()(); ''', - expectedOutput: /*language=dart*/ ''' + expectedOutput: ''' import 'package:over_react/over_react.dart'; - import 'package:unify_ui/abc.dart'; - import 'package:unify_ui/components/badge.dart'; - import 'package:unify_ui/unify_ui.dart'; - import 'package:unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart'; + import 'package:''' + '''unify_ui/abc.dart'; + import 'package:''' + '''unify_ui/components/badge.dart'; + import 'package:''' + '''unify_ui/unify_ui.dart'; + import 'package:''' + '''unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart'; content() => Dom.div()(); ''', @@ -66,15 +71,17 @@ void main() { test('in alphabetical order', () async { await testSuggestor( - input: /*language=dart*/ ''' + input: ''' import 'package:react_material_ui/react_material_ui.dart' as mui; import 'package:react_material_ui/styles/styled.dart' as mui; content() => Dom.div()(); ''', - expectedOutput: /*language=dart*/ ''' - import 'package:unify_ui/styles/styled.dart' as unify; - import 'package:unify_ui/unify_ui.dart' as unify; + expectedOutput: ''' + import 'package:''' + '''unify_ui/styles/styled.dart' as unify; + import 'package:''' + '''unify_ui/unify_ui.dart' as unify; content() => Dom.div()(); ''', @@ -85,7 +92,7 @@ void main() { 'for special cases when the new file path is different from the old one', () async { await testSuggestor( - input: /*language=dart*/ ''' + input: ''' import 'package:react_material_ui/react_material_ui.dart'; import 'package:over_react/over_react.dart'; import 'package:web_skin_dart/ui_components.dart'; @@ -95,12 +102,16 @@ void main() { content() => Dom.div()(); ''', - expectedOutput: /*language=dart*/ ''' + expectedOutput: ''' import 'package:over_react/over_react.dart'; - import 'package:unify_ui/components/list.dart'; - import 'package:unify_ui/styles/styled.dart'; - import 'package:unify_ui/styles/styled.dart'; - import 'package:unify_ui/unify_ui.dart'; + import 'package:''' + '''unify_ui/components/list.dart'; + import 'package:''' + '''unify_ui/styles/styled.dart'; + import 'package:''' + '''unify_ui/styles/styled.dart'; + import 'package:''' + '''unify_ui/unify_ui.dart'; import 'package:web_skin_dart/ui_components.dart'; content() => Dom.div()(); @@ -110,7 +121,7 @@ void main() { test('with namespaces', () async { await testSuggestor( - input: /*language=dart*/ ''' + input: ''' library lib; import 'package:react_material_ui/react_material_ui.dart' as mui; @@ -122,15 +133,20 @@ void main() { content() => Dom.div()(); ''', - expectedOutput: /*language=dart*/ ''' + expectedOutput: ''' library lib; import 'package:over_react/over_react.dart' as mui; - import 'package:unify_ui/components/alert.dart' as something_else; - import 'package:unify_ui/components/badge.dart' as unify; - import 'package:unify_ui/unify_ui.dart' as unify; - import 'package:unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_unify; - import 'package:unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_unify; + import 'package:''' + '''unify_ui/components/alert.dart' as something_else; + import 'package:''' + '''unify_ui/components/badge.dart' as unify; + import 'package:''' + '''unify_ui/unify_ui.dart' as unify; + import 'package:''' + '''unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_unify; + import 'package:''' + '''unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_unify; content() => Dom.div()(); ''', @@ -139,14 +155,19 @@ void main() { test('unless the imports are already updated to the new name', () async { await testSuggestor( - input: /*language=dart*/ ''' + input: ''' library lib; - import 'package:unify_ui/unify_ui.dart' as mui; - import 'package:unify_ui/unify_ui.dart'; - import 'package:unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; - import 'package:unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart'; - import 'package:unify_ui/abc.dart'; + import 'package:''' + '''unify_ui/unify_ui.dart' as mui; + import 'package:''' + '''unify_ui/unify_ui.dart'; + import 'package:''' + '''unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha; + import 'package:''' + '''unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart'; + import 'package:''' + '''unify_ui/abc.dart'; content() => Dom.div()(); ''', @@ -163,16 +184,20 @@ void main() { ), ); await testSuggestor( - input: /*language=dart*/ ''' + input: ''' import 'package:over_react/over_react.dart'; - import 'package:old/old.dart' as o; - import 'package:old/components/badge.dart'; + import 'package:''' + '''old/old.dart' as o; + import 'package:''' + '''old/components/badge.dart'; content() => Dom.div()(); ''', - expectedOutput: /*language=dart*/ ''' - import 'package:new/components/badge.dart'; - import 'package:new/old.dart' as n; + expectedOutput: ''' + import 'package:''' + '''new/components/badge.dart'; + import 'package:''' + '''new/old.dart' as n; import 'package:over_react/over_react.dart'; content() => Dom.div()(); diff --git a/test/util/importer_test.dart b/test/util/importer_test.dart index 30576429..2be1af36 100644 --- a/test/util/importer_test.dart +++ b/test/util/importer_test.dart @@ -356,7 +356,9 @@ void main() { test('for a different package name', () async { final testSuggestor = getSuggestorTester( - importerSuggestorBuilder(importUri: 'package:over_react/over_react.dart', importNamespace: 'or'), + importerSuggestorBuilder( + importUri: 'package:over_react/over_react.dart', + importNamespace: 'or'), resolvedContext: resolvedContext, ); await testSuggestor( @@ -364,7 +366,8 @@ void main() { content() => or.Fragment(); ''', - isExpectedError: (error) => error.message.contains("Undefined name 'or'"), + isExpectedError: (error) => + error.message.contains("Undefined name 'or'"), expectedOutput: /*language=dart*/ ''' import 'package:over_react/over_react.dart' as or; From 5077b622f458e7b48f1506e94bc8bcf4d4ef3e8c Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Tue, 17 Oct 2023 16:04:52 -0700 Subject: [PATCH 20/29] Try fixing test --- lib/src/util/package_util.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/util/package_util.dart b/lib/src/util/package_util.dart index 4930856b..e2037389 100644 --- a/lib/src/util/package_util.dart +++ b/lib/src/util/package_util.dart @@ -55,7 +55,7 @@ Future runPubGetIfNeeded(String packageRoot) async { Future runPubGet(String workingDirectory) async { _logger.info('Running `pub get` in `$workingDirectory`...'); - final process = await Process.start('pub', ['get'], + final process = await Process.start('dart', ['pub', 'get'], workingDirectory: workingDirectory, runInShell: true, mode: ProcessStartMode.inheritStdio); From 0244d3a718eeebbc66752f374f9bd91938de53d6 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Tue, 17 Oct 2023 16:13:47 -0700 Subject: [PATCH 21/29] Try fixing test --- lib/src/util/package_util.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/util/package_util.dart b/lib/src/util/package_util.dart index e2037389..dd59ddf2 100644 --- a/lib/src/util/package_util.dart +++ b/lib/src/util/package_util.dart @@ -64,7 +64,7 @@ Future runPubGet(String workingDirectory) async { if (exitCode == 69) { _logger.info( 'Re-running `pub get` but with `--offline`, to hopefully fix the above error.'); - final process = await Process.start('pub', ['get', '--offline'], + final process = await Process.start('dart', ['pub', 'get', '--offline'], workingDirectory: workingDirectory, runInShell: true, mode: ProcessStartMode.inheritStdio); From 42d2f85cfa904f3ff6752975f60b9ccd7362381d Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Tue, 17 Oct 2023 16:21:00 -0700 Subject: [PATCH 22/29] Try fixing test --- skynet.yaml | 1 + .../unify_rename_suggestor_test.dart | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/skynet.yaml b/skynet.yaml index 2fc31ad4..479ba68c 100644 --- a/skynet.yaml +++ b/skynet.yaml @@ -14,6 +14,7 @@ scripts: - echo 'Running dart pub get in test fixtures beforehand to prevent concurrent `dart pub get`s in tests from failing' - (cd test/test_fixtures/over_react_project && dart pub get) - (cd test/test_fixtures/wsd_project && dart pub get) + - (cd test/test_fixtures/rmui_project && dart pub get) - echo 'Running only the tests with the "wsd" tag' # TODO think about using an aggregated test suite for these so that we can reuse the same SharedAnalysisContext/AnalysisContextCollection instances and run tests faster. - dart test --tags=wsd --file-reporter=json:test-reports/wsd.json || RESULT=1 diff --git a/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart b/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart index 5860c514..0555f3a1 100644 --- a/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart +++ b/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart @@ -423,5 +423,6 @@ void main() { ); }); }); - }); + // Run in private Skynet unit tests with wsd-related tests. + }, tags: 'wsd'); } From 20d7c9eb12327016f9314c2062bad9e51a761f9b Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Tue, 17 Oct 2023 16:35:10 -0700 Subject: [PATCH 23/29] Remove fixme --- .../unify_rename_suggestor_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart b/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart index 0555f3a1..f4dd4e17 100644 --- a/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart +++ b/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart @@ -373,7 +373,6 @@ void main() { }); }); - // test existing fixme group('fixme comments', () { test('for specific components that need manual intervention', () async { await testSuggestor( From fddc460ceaebcddc982c9bcf0336d20102cafe53 Mon Sep 17 00:00:00 2001 From: sydneyjodon-wk <51122966+sydneyjodon-wk@users.noreply.github.com> Date: Tue, 24 Oct 2023 09:04:47 -0700 Subject: [PATCH 24/29] Apply suggestions from code review Co-authored-by: Keal Jones <41018730+kealjones-wk@users.noreply.github.com> --- lib/src/util/importer.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/util/importer.dart b/lib/src/util/importer.dart index ccef217e..954754b6 100644 --- a/lib/src/util/importer.dart +++ b/lib/src/util/importer.dart @@ -43,13 +43,13 @@ Suggestor importerSuggestorBuilder({ // Look for errors in the main compilation unit and its part files. // Ignore null partContexts and partContexts elements caused by // resolution issues and parts being excluded in the codemod file list. - final needsMuiImport = libraryResult.units + final needsImport = libraryResult.units .expand((unitResult) => unitResult.errors) .where((error) => error.errorCode.name == 'UNDEFINED_IDENTIFIER') .any((error) => error.message.contains("Undefined name '$importNamespace'")); - if (!needsMuiImport) return; + if (!needsImport) return; final insertInfo = insertionLocationForPackageImport( importUri, mainLibraryUnitResult.unit, mainLibraryUnitResult.lineInfo); From dbc4af604bd2148b7b8e280e343f314163268acc Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 30 Oct 2023 15:43:32 -0700 Subject: [PATCH 25/29] Fix AlertIconMappingObject --- lib/src/unify_package_rename_suggestors/constants.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/unify_package_rename_suggestors/constants.dart b/lib/src/unify_package_rename_suggestors/constants.dart index 9625e62c..f30cab5e 100644 --- a/lib/src/unify_package_rename_suggestors/constants.dart +++ b/lib/src/unify_package_rename_suggestors/constants.dart @@ -71,6 +71,8 @@ const rmuiToUnifyIdentifierRenames = { 'MuiListPropsMixin': 'UnifyListPropsMixin', 'WorkivaMuiThemeProvider': 'UnifyThemeProvider', 'WorkivaMuiThemeProviderPropsMixin': 'UnifyThemeProviderPropsMixin', + // Alert objects + 'AlertIconMappingObject': 'WsdAlertIconMappingObject', // Autocomplete objects 'AutocompleteFilterOptionsObject': 'AutocompleteFilterOptionsState', 'AutocompleteOnChangeObject': 'AutocompleteChangeDetails', From dda48125eb89f974c171ea49dd80783aea239295 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 30 Oct 2023 15:50:14 -0700 Subject: [PATCH 26/29] Fix Alert constant updates --- .../unify_rename_suggestor.dart | 5 +++-- .../unify_rename_suggestor_test.dart | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart b/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart index 193a6df8..1b03a696 100644 --- a/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart +++ b/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart @@ -99,14 +99,15 @@ class UnifyRenameSuggestor extends GeneralizingAstVisitor with ClassSuggestor { // Update WSD constant properties objects to use the WSD versions if applicable. yieldWsdRenamePatchIfApplicable( Expression node, String? objectName, String? propertyName) { + const alertConstantNames = ['AlertSize', 'AlertColor', 'AlertVariant', 'AlertSeverity']; if (objectName == 'ButtonColor' && (propertyName?.startsWith('wsd') ?? false)) { isFromWsdEntrypoint = true; yieldPatch('$unifyWsdNamespace.WsdButtonColor.$propertyName', node.offset, node.end); - } else if (objectName == 'AlertSize') { + } else if (alertConstantNames.contains(objectName)) { isFromWsdEntrypoint = true; - yieldPatch('$unifyWsdNamespace.WsdAlertSize.$propertyName', + yieldPatch('$unifyWsdNamespace.Wsd$objectName.$propertyName', node.offset, node.end); } } diff --git a/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart b/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart index f4dd4e17..139cad2d 100644 --- a/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart +++ b/test/unify_package_rename_suggestors/unify_rename_suggestor_test.dart @@ -240,6 +240,9 @@ void main() { mui.Alert; mui.AlertSize.small; AlertSize.small; + AlertSeverity.error; + mui.AlertColor.warning; + mui.AlertVariant.outlined; Alert()(); random_rmui_namespace.Alert()(); mui.LinkButton()(); @@ -263,6 +266,9 @@ void main() { unify_wsd.WsdAlert; unify_wsd.WsdAlertSize.small; unify_wsd.WsdAlertSize.small; + unify_wsd.WsdAlertSeverity.error; + unify_wsd.WsdAlertColor.warning; + unify_wsd.WsdAlertVariant.outlined; unify_wsd.WsdAlert()(); unify_wsd.WsdAlert()(); unify_wsd.WsdLinkButton()(); From cb672682a9ad7387c5cd2ee844f4061ff43bdb23 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 30 Oct 2023 15:52:56 -0700 Subject: [PATCH 27/29] Format --- .../unify_rename_suggestor.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart b/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart index 1b03a696..7480e6c9 100644 --- a/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart +++ b/lib/src/unify_package_rename_suggestors/unify_rename_suggestor.dart @@ -99,7 +99,12 @@ class UnifyRenameSuggestor extends GeneralizingAstVisitor with ClassSuggestor { // Update WSD constant properties objects to use the WSD versions if applicable. yieldWsdRenamePatchIfApplicable( Expression node, String? objectName, String? propertyName) { - const alertConstantNames = ['AlertSize', 'AlertColor', 'AlertVariant', 'AlertSeverity']; + const alertConstantNames = [ + 'AlertSize', + 'AlertColor', + 'AlertVariant', + 'AlertSeverity' + ]; if (objectName == 'ButtonColor' && (propertyName?.startsWith('wsd') ?? false)) { isFromWsdEntrypoint = true; From e1ff5375e3879e3250b3d2ff278834086890576e Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 30 Oct 2023 16:12:01 -0700 Subject: [PATCH 28/29] Format --- lib/src/unify_package_rename_suggestors/constants.dart | 6 +++++- .../import_renamer_test.dart | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/src/unify_package_rename_suggestors/constants.dart b/lib/src/unify_package_rename_suggestors/constants.dart index f30cab5e..d69b23f3 100644 --- a/lib/src/unify_package_rename_suggestors/constants.dart +++ b/lib/src/unify_package_rename_suggestors/constants.dart @@ -55,7 +55,11 @@ final rmuiImportsToUpdate = [ UnifyImportInfo( 'package:unify_ui/styles/styled.dart', rmuiUri: 'package:react_material_ui/for_cp_use_only/styled.dart', - ) + ), + UnifyImportInfo('package:unify_ui/styles/theme_provider.dart', + rmuiUri: 'package:react_material_ui/styles/theme_provider.dart', + namespace: 'unify_theme', + possibleMuiNamespaces: ['mui_theme']) ]; /// A map of RMUI component names to their new names in unify_ui. diff --git a/test/unify_package_rename_suggestors/import_renamer_test.dart b/test/unify_package_rename_suggestors/import_renamer_test.dart index 5e0a2822..4fc4f973 100644 --- a/test/unify_package_rename_suggestors/import_renamer_test.dart +++ b/test/unify_package_rename_suggestors/import_renamer_test.dart @@ -130,6 +130,7 @@ void main() { import 'package:over_react/over_react.dart' as mui; import 'package:react_material_ui/components/badge.dart' as mui; import 'package:react_material_ui/components/alert.dart' as something_else; + import 'package:react_material_ui/styles/theme_provider.dart' as mui_theme; content() => Dom.div()(); ''', @@ -142,6 +143,8 @@ void main() { import 'package:''' '''unify_ui/components/badge.dart' as unify; import 'package:''' + '''unify_ui/styles/theme_provider.dart' as unify_theme; + import 'package:''' '''unify_ui/unify_ui.dart' as unify; import 'package:''' '''unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_unify; From 80a9c4decef31d915f92b905e7ca58fbd044bcc5 Mon Sep 17 00:00:00 2001 From: Sydney Jodon Date: Mon, 30 Oct 2023 16:41:43 -0700 Subject: [PATCH 29/29] Fix show / hide import updates --- .../constants.dart | 8 ++++- .../import_renamer.dart | 10 ++++-- .../import_renamer_test.dart | 32 +++++++++++++++++-- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/lib/src/unify_package_rename_suggestors/constants.dart b/lib/src/unify_package_rename_suggestors/constants.dart index d69b23f3..e4d92fc6 100644 --- a/lib/src/unify_package_rename_suggestors/constants.dart +++ b/lib/src/unify_package_rename_suggestors/constants.dart @@ -15,7 +15,10 @@ /// Info on a unify_ui import. class UnifyImportInfo { UnifyImportInfo(this.uri, - {this.rmuiUri, this.namespace, this.possibleMuiNamespaces}); + {this.rmuiUri, + this.namespace, + this.possibleMuiNamespaces, + this.showHideInfo}); /// Unify import URI. String uri; @@ -28,6 +31,9 @@ class UnifyImportInfo { /// Previous RMUI import URI (if it's different from the unify_ui path). String? rmuiUri; + + /// Additional show / hide information used in [importRenamerSuggestorBuilder] to add to updated imports. + String? showHideInfo; } /// A list of the standard imports for unify_ui that should be updated. diff --git a/lib/src/unify_package_rename_suggestors/import_renamer.dart b/lib/src/unify_package_rename_suggestors/import_renamer.dart index 8b14823e..e9b2948e 100644 --- a/lib/src/unify_package_rename_suggestors/import_renamer.dart +++ b/lib/src/unify_package_rename_suggestors/import_renamer.dart @@ -73,8 +73,12 @@ Suggestor importRenamerSuggestorBuilder({ if (newImportUri != null) { // Collect info on new imports to add. - newImportsInfo - .add(UnifyImportInfo(newImportUri, namespace: newNamespace)); + newImportsInfo.add(UnifyImportInfo(newImportUri, + namespace: newNamespace, + showHideInfo: import.combinators + .map((c) => c.toSource()) + .toList() + .join(' '))); } final prevTokenEnd = import.beginToken.previous?.end; @@ -96,7 +100,7 @@ Suggestor importRenamerSuggestorBuilder({ mainLibraryUnitResult.unit, mainLibraryUnitResult.lineInfo); yield Patch( insertInfo.leadingNewlines + - "import '${importInfo.uri}'${importInfo.namespace != null ? ' as ${importInfo.namespace}' : ''};" + + "import '${importInfo.uri}'${importInfo.namespace != null ? ' as ${importInfo.namespace}' : ''}${importInfo.showHideInfo != null ? ' ${importInfo.showHideInfo}' : ''};" + insertInfo.trailingNewlines, insertInfo.offset, insertInfo.offset); diff --git a/test/unify_package_rename_suggestors/import_renamer_test.dart b/test/unify_package_rename_suggestors/import_renamer_test.dart index 4fc4f973..7d9c6717 100644 --- a/test/unify_package_rename_suggestors/import_renamer_test.dart +++ b/test/unify_package_rename_suggestors/import_renamer_test.dart @@ -130,7 +130,7 @@ void main() { import 'package:over_react/over_react.dart' as mui; import 'package:react_material_ui/components/badge.dart' as mui; import 'package:react_material_ui/components/alert.dart' as something_else; - import 'package:react_material_ui/styles/theme_provider.dart' as mui_theme; + import 'package:react_material_ui/styles/theme_provider.dart' as mui_theme show UnifyThemeProvider; content() => Dom.div()(); ''', @@ -143,7 +143,7 @@ void main() { import 'package:''' '''unify_ui/components/badge.dart' as unify; import 'package:''' - '''unify_ui/styles/theme_provider.dart' as unify_theme; + '''unify_ui/styles/theme_provider.dart' as unify_theme show UnifyThemeProvider; import 'package:''' '''unify_ui/unify_ui.dart' as unify; import 'package:''' @@ -156,6 +156,34 @@ void main() { ); }); + test('with show / hide', () async { + await testSuggestor( + input: ''' + import 'package:react_material_ui/react_material_ui.dart' hide Alert; + import 'package:react_material_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as mui_alpha hide Alert show LinearProgress; + import 'package:react_material_ui/components/badge.dart' show Badge hide BadgeColor; + import 'package:react_material_ui/components/alert.dart' as something_else show Alert; + import 'package:react_material_ui/styles/theme_provider.dart' as mui_theme show UnifyThemeProvider; + + content() => Dom.div()(); + ''', + expectedOutput: ''' + import 'package:''' + '''unify_ui/components/alert.dart' as something_else show Alert; + import 'package:''' + '''unify_ui/components/badge.dart' show Badge hide BadgeColor; + import 'package:''' + '''unify_ui/styles/theme_provider.dart' as unify_theme show UnifyThemeProvider; + import 'package:''' + '''unify_ui/unify_ui.dart' hide Alert; + import 'package:''' + '''unify_ui/z_alpha_may_break_at_runtime_do_not_release_to_customers.dart' as alpha_unify hide Alert show LinearProgress; + + content() => Dom.div()(); + ''', + ); + }); + test('unless the imports are already updated to the new name', () async { await testSuggestor( input: '''