diff --git a/bin/null_safety_migrator_companion.dart b/bin/null_safety_migrator_companion.dart deleted file mode 100644 index bf2b0410..00000000 --- a/bin/null_safety_migrator_companion.dart +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2024 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/null_safety_migrator_companion.dart'; diff --git a/bin/null_safety_prep.dart b/bin/null_safety_prep.dart deleted file mode 100644 index 5e9a24af..00000000 --- a/bin/null_safety_prep.dart +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2024 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/null_safety_prep.dart'; diff --git a/bin/required_flux_props.dart b/bin/required_flux_props.dart deleted file mode 100644 index 5ae74318..00000000 --- a/bin/required_flux_props.dart +++ /dev/null @@ -1,15 +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. - -export 'package:over_react_codemod/src/executables/required_flux_props.dart'; diff --git a/lib/src/boilerplate_suggestors/boilerplate_utilities.dart b/lib/src/boilerplate_suggestors/boilerplate_utilities.dart index 3c5dcb8e..f5446948 100644 --- a/lib/src/boilerplate_suggestors/boilerplate_utilities.dart +++ b/lib/src/boilerplate_suggestors/boilerplate_utilities.dart @@ -123,7 +123,7 @@ class SemverHelper { } final locations = []; - _exportList!.forEach((key, value) { + _exportList.forEach((key, value) { if (value['type'] == 'class' && value['grammar']['name'] == className) { locations.add(key); } diff --git a/lib/src/creator_utils.dart b/lib/src/creator_utils.dart index d4487bc6..7531f648 100644 --- a/lib/src/creator_utils.dart +++ b/lib/src/creator_utils.dart @@ -206,7 +206,7 @@ class DartProjectCreatorTestConfig { } String get testName { - if (_testName != null) return _testName!; + if (_testName != null) return _testName; var name = 'returns exit code ${expectedExitCode} with '; if (pubspecCreators.isEmpty) { diff --git a/lib/src/dart3_suggestors/null_safety_prep/analyzer_plugin_utils.dart b/lib/src/dart3_suggestors/null_safety_prep/analyzer_plugin_utils.dart deleted file mode 100644 index 5afcc755..00000000 --- a/lib/src/dart3_suggestors/null_safety_prep/analyzer_plugin_utils.dart +++ /dev/null @@ -1,151 +0,0 @@ -// These utilities were copied from analyzer utils in over_react/analyzer_plugin -// Permalink: https://github.com/Workiva/over_react/blob/a8129f38ea8dfa0023d06250349fc8e86025df3a/tools/analyzer_plugin/lib/src/util/analyzer_util.dart#L4 -// Permalink: https://github.com/Workiva/over_react/blob/a8129f38ea8dfa0023d06250349fc8e86025df3a/tools/analyzer_plugin/lib/src/util/ast_util.dart -// -// Copyright 2024 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/element/element.dart'; -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/dart/ast/visitor.dart'; - -/// Returns the AST node of the variable declaration associated with the [element] within [root], -/// or null if the [element] doesn't correspond to a variable declaration, or if it can't be found in [root]. -VariableDeclaration? lookUpVariable(Element element, AstNode root) { - final node = NodeLocator2(element.nameOffset).searchWithin(root); - if (node is VariableDeclaration && node.declaredElement == element) { - return node; - } - - return null; -} - -/// An object used to locate the [AstNode] associated with a source range. -/// More specifically, they will return the deepest [AstNode] which completely -/// encompasses the specified range with some exceptions: -/// -/// - Offsets that fall between the name and type/formal parameter list of a -/// declaration will return the declaration node and not the parameter list -/// node. -class NodeLocator2 extends UnifyingAstVisitor { - /// The inclusive start offset of the range used to identify the node. - final int _startOffset; - - /// The inclusive end offset of the range used to identify the node. - final int _endOffset; - - /// The found node or `null` if there is no such node. - AstNode? _foundNode; - - /// Initialize a newly created locator to locate the deepest [AstNode] for - /// which `node.offset <= [startOffset]` and `[endOffset] < node.end`. - /// - /// If [endOffset] is not provided, then it is considered the same as the - /// given [startOffset]. - NodeLocator2(int startOffset, [int? endOffset]) - : _startOffset = startOffset, - _endOffset = endOffset ?? startOffset; - - /// Search within the given AST [node] and return the node that was found, - /// or `null` if no node was found. - AstNode? searchWithin(AstNode? node) { - if (node == null) { - return null; - } - try { - node.accept(this); - } catch (_) { - return null; - } - return _foundNode; - } - - @override - void visitConstructorDeclaration(ConstructorDeclaration node) { - // Names do not have AstNodes but offsets at the end should be treated as - // part of the declaration (not parameter list). - if (_startOffset == _endOffset && - _startOffset == (node.name ?? node.returnType).end) { - _foundNode = node; - return; - } - - super.visitConstructorDeclaration(node); - } - - @override - void visitFunctionDeclaration(FunctionDeclaration node) { - // Names do not have AstNodes but offsets at the end should be treated as - // part of the declaration (not parameter list). - if (_startOffset == _endOffset && _startOffset == node.name.end) { - _foundNode = node; - return; - } - - super.visitFunctionDeclaration(node); - } - - @override - void visitMethodDeclaration(MethodDeclaration node) { - // Names do not have AstNodes but offsets at the end should be treated as - // part of the declaration (not parameter list). - if (_startOffset == _endOffset && _startOffset == node.name.end) { - _foundNode = node; - return; - } - - super.visitMethodDeclaration(node); - } - - @override - void visitNode(AstNode node) { - // Don't visit a new tree if the result has been already found. - if (_foundNode != null) { - return; - } - // Check whether the current node covers the selection. - var beginToken = node.beginToken; - var endToken = node.endToken; - // Don't include synthetic tokens. - while (endToken != beginToken) { - // Fasta scanner reports unterminated string literal errors - // and generates a synthetic string token with non-zero length. - // Because of this, check for length > 0 rather than !isSynthetic. - if (endToken.isEof || endToken.length > 0) { - break; - } - endToken = endToken.previous!; - } - var end = endToken.end; - var start = node.offset; - if (end <= _startOffset || start > _endOffset) { - return; - } - // Check children. - try { - node.visitChildren(this); - } catch (_) { - // Ignore the exception and proceed in order to visit the rest of the - // structure. - } - // Found a child. - if (_foundNode != null) { - return; - } - // Check this node. - if (start <= _startOffset && _endOffset < end) { - _foundNode = node; - } - } -} diff --git a/lib/src/dart3_suggestors/null_safety_prep/callback_ref_hint_suggestor.dart b/lib/src/dart3_suggestors/null_safety_prep/callback_ref_hint_suggestor.dart deleted file mode 100644 index 892aa85b..00000000 --- a/lib/src/dart3_suggestors/null_safety_prep/callback_ref_hint_suggestor.dart +++ /dev/null @@ -1,151 +0,0 @@ -// Adapted from the add_create_ref assist in over_react/analyzer_plugin -// Permalink: https://github.com/Workiva/over_react/blob/a8129f38ea8dfa0023d06250349fc8e86025df3a/tools/analyzer_plugin/lib/src/assist/refs/add_create_ref.dart#L4 -// -// Copyright 2024 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/analysis/results.dart'; -import 'package:analyzer/dart/element/element.dart'; -import 'package:collection/collection.dart'; -import 'package:over_react_codemod/src/dart3_suggestors/null_safety_prep/utils/hint_detection.dart'; -import 'package:over_react_codemod/src/util/component_usage.dart'; -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/dart/ast/visitor.dart'; - -import '../../util.dart'; -import '../../util/class_suggestor.dart'; -import 'analyzer_plugin_utils.dart'; - -/// Suggestor to add nullability hints to ref types. -/// -/// (1) For ref prop param types: -/// ``` -/// - (ButtonToolbar()..ref = (ButtonElement r) => ref = r)(); -/// + (ButtonToolbar()..ref = (ButtonElement /*?*/ r) => ref = r)(); -/// ``` -/// -/// (2) For ref variable declarations: -/// ``` -/// - ButtonElement ref; -/// + ButtonElement /*?*/ ref; -/// (ButtonToolbar()..ref = (r) => ref = r)(); -/// ``` -/// -/// (3) For ref prop type casts: -/// ``` -/// - (ButtonToolbar()..ref = (r) => ref = r as ButtonElement)(); -/// + (ButtonToolbar()..ref = (r) => ref = r as ButtonElement /*?*/)(); -/// ``` -/// -/// These hints are needed because the null-safety migration tool does not do -/// well at inferring that ref types should be nullable. -class CallbackRefHintSuggestor extends RecursiveAstVisitor - with ClassSuggestor { - CallbackRefHintSuggestor(); - - late ResolvedUnitResult result; - - @override - Future visitCascadeExpression(CascadeExpression node) async { - super.visitCascadeExpression(node); - - final cascadedProps = node.cascadeSections - .whereType() - .where((assignment) => assignment.leftHandSide is PropertyAccess) - .map((assignment) => PropAssignment(assignment)); - - for (final prop in cascadedProps) { - if (prop.name.name == 'ref') { - final rhs = - prop.rightHandSide.unParenthesized.tryCast(); - if (rhs == null) return null; - - // Add nullability hint to parameter if typed. - final param = rhs.parameters?.parameters.first; - if (param is SimpleFormalParameter) { - final type = param.type; - if (type != null && !nullableHintAlreadyExists(type)) { - yieldPatch(nullableHint, type.end, type.end); - } - } - - final refParamName = param?.name?.toString(); - if (refParamName != null) { - // Add nullability hint to ref variable declarations. - final refCallbackArg = rhs.parameters?.parameters.firstOrNull; - if (refCallbackArg != null) { - final referencesToArg = allDescendantsOfType(rhs.body) - .where((identifier) => - identifier.staticElement == refCallbackArg.declaredElement); - - for (final reference in referencesToArg) { - final parent = reference.parent; - if (parent is AssignmentExpression && - parent.rightHandSide == reference) { - final lhs = parent.leftHandSide; - if (lhs is Identifier) { - final varElement = - // Variable in function component. - lhs.staticElement?.tryCast() ?? - // Variable in class component. - lhs.parent - ?.tryCast() - ?.writeElement - ?.tryCast() - ?.variable; - if (varElement != null) { - final varType = lookUpVariable(varElement, result.unit) - ?.parent - .tryCast() - ?.type; - if (varType != null && - !nullableHintAlreadyExists(varType) && - varType.toSource() != 'dynamic') { - yieldPatch(nullableHint, varType.end, varType.end); - } - } - } - } - } - } - - // Add nullability hint to any casts in the body of the callback ref. - final refCasts = allDescendantsOfType(rhs.body).where( - (expression) => - expression.expression.toSource() == refParamName && - !nullableHintAlreadyExists(expression.type)); - for (final cast in refCasts) { - yieldPatch(nullableHint, cast.type.end, cast.type.end); - } - } - } - } - } - - @override - Future generatePatches() async { - final r = await context.getResolvedUnit(); - if (r == null) { - throw Exception( - 'Could not get resolved result for "${context.relativePath}"'); - } - result = r; - // Don't make any updates if the file is already null safe. - if (result.libraryElement.isNonNullableByDefault) { - return; - } - - result.unit.visitChildren(this); - } -} diff --git a/lib/src/dart3_suggestors/null_safety_prep/class_component_required_default_props.dart b/lib/src/dart3_suggestors/null_safety_prep/class_component_required_default_props.dart deleted file mode 100644 index 6efafd35..00000000 --- a/lib/src/dart3_suggestors/null_safety_prep/class_component_required_default_props.dart +++ /dev/null @@ -1,112 +0,0 @@ -// Adapted from the missing_required_prop diagnostic in over_react/analyzer_plugin -// Permalink: https://github.com/Workiva/over_react/blob/ae8c898650537e49f35f98ad1b065c516207838e/tools/analyzer_plugin/lib/src/util/prop_declarations/defaulted_props.dart - -// Copyright 2024 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/dart3_suggestors/null_safety_prep/utils/props_utils.dart'; -import 'package:over_react_codemod/src/util.dart'; -import 'package:over_react_codemod/src/util/component_usage.dart'; -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:over_react_codemod/src/vendor/over_react_analyzer_plugin/get_all_props.dart'; -import 'package:pub_semver/pub_semver.dart'; - -import 'utils/class_component_required_fields.dart'; - -/// Suggestor to assist with preparations for null-safety by adding -/// "requiredness" (`late`) / nullability (`?`/`!`) hints to prop types -/// based on their access within a class component's `defaultProps`. -/// -/// If a prop is defaulted to a non-null value within `defaultProps`, the -/// corresponding prop declaration will gain a `/*late*/` modifier hint to the -/// left of the type, and a non-nullable type hint (`/*!*/`) to the right of -/// the type to assist the `nnbd_migration:migrate` script when it attempts to -/// infer a prop's nullability. -/// -/// **Optionally**, an [sdkVersion] can be passed to the constructor. -/// When set to a version that opts-in to Dart's null safety feature, -/// the `late` / `?` type modifiers will be actual modifiers rather -/// than commented hints. This should only be done using an explicit opt-in -/// flag from the executable as most consumers that have migrated to null-safety -/// will have already run this script prior to the null safety migration and thus -/// the `/*late*/` / `/*?*/` hints will already be converted to actual modifiers. -/// -/// **Before** -/// ```dart -/// mixin FooProps on UiProps { -/// String defaultedNullable; -/// num defaultedNonNullable; -/// } -/// class FooComponent extends UiComponent2 { -/// @override -/// get defaultProps => (newProps() -/// ..defaultedNullable = null -/// ..defaultedNonNullable = 2.1 -/// ); -/// -/// // ... -/// } -/// ``` -/// -/// **After** -/// ```dart -/// mixin FooProps on UiProps { -/// /*late*/ String/*?*/ defaultedNullable; -/// /*late*/ num/*!*/ defaultedNonNullable; -/// } -/// class FooComponent extends UiComponent2 { -/// @override -/// get defaultProps => (newProps() -/// ..defaultedNullable = null -/// ..defaultedNonNullable = 2.1 -/// ); -/// -/// // ... -/// } -/// ``` -class ClassComponentRequiredDefaultPropsMigrator - extends ClassComponentRequiredFieldsMigrator { - ClassComponentRequiredDefaultPropsMigrator([Version? sdkVersion]) - : super('defaultProps', 'getDefaultProps', sdkVersion); - - @override - Future visitCascadeExpression(CascadeExpression node) async { - // Don't make any updates if the file is already null safe. - if (result.libraryElement.isNonNullableByDefault) { - return; - } - - super.visitCascadeExpression(node); - - final isDefaultProps = node.ancestors.any((ancestor) { - if (ancestor is MethodDeclaration) { - return [relevantGetterName, relevantMethodName] - .contains(ancestor.declaredElement?.name); - } - if (ancestor is VariableDeclaration && - (ancestor.parentFieldDeclaration?.isStatic ?? false)) { - return RegExp(RegExp.escape(relevantGetterName), caseSensitive: false) - .hasMatch(ancestor.name.lexeme); - } - return false; - }); - - // If this cascade is not assigning values to defaultProps, bail. - if (!isDefaultProps) return; - - final cascadedDefaultProps = getCascadedProps(node); - - patchFieldDeclarations(getAllProps, cascadedDefaultProps, node); - } -} diff --git a/lib/src/dart3_suggestors/null_safety_prep/class_component_required_initial_state.dart b/lib/src/dart3_suggestors/null_safety_prep/class_component_required_initial_state.dart deleted file mode 100644 index 3488f42a..00000000 --- a/lib/src/dart3_suggestors/null_safety_prep/class_component_required_initial_state.dart +++ /dev/null @@ -1,104 +0,0 @@ -// Adapted from the missing_required_prop diagnostic in over_react/analyzer_plugin -// Permalink: https://github.com/Workiva/over_react/blob/ae8c898650537e49f35f98ad1b065c516207838e/tools/analyzer_plugin/lib/src/util/prop_declarations/defaulted_props.dart - -// Copyright 2024 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/util/component_usage.dart'; -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:over_react_codemod/src/util/get_all_state.dart'; -import 'package:pub_semver/pub_semver.dart'; - -import 'utils/class_component_required_fields.dart'; - -/// Suggestor to assist with preparations for null-safety by adding -/// "requiredness" (`late`) / nullability (`?`/`!`) hints to state field types -/// based on their access within a class component's `initialState`. -/// -/// If a piece of state is initialized to a non-null value within `initialState`, -/// the corresponding declaration will gain a `/*late*/` modifier hint to -/// the left of the type, and a non-nullable type hint (`/*!*/`) to the right of -/// the type to assist the `nnbd_migration:migrate` script when it attempts to -/// infer a state field's nullability. -/// -/// **Optionally**, an [sdkVersion] can be passed to the constructor. -/// When set to a version that opts-in to Dart's null safety feature, -/// the `late` / `?` type modifiers will be actual modifiers rather -/// than commented hints. This should only be done using an explicit opt-in -/// flag from the executable as most consumers that have migrated to null-safety -/// will have already run this script prior to the null safety migration and thus -/// the `/*late*/` / `/*?*/` hints will already be converted to actual modifiers. -/// -/// **Before** -/// ```dart -/// mixin FooState on UiState { -/// String defaultedNullable; -/// num defaultedNonNullable; -/// } -/// class FooComponent extends UiStatefulComponent2 { -/// @override -/// get initialState => (newState() -/// ..defaultedNullable = null -/// ..defaultedNonNullable = 2.1 -/// ); -/// -/// // ... -/// } -/// ``` -/// -/// **After** -/// ```dart -/// mixin FooState on UiState { -/// /*late*/ String/*?*/ defaultedNullable; -/// /*late*/ num/*!*/ defaultedNonNullable; -/// } -/// class FooComponent extends UiStatefulComponent2 { -/// @override -/// get initialState => (newState() -/// ..defaultedNullable = null -/// ..defaultedNonNullable = 2.1 -/// ); -/// -/// // ... -/// } -/// ``` -class ClassComponentRequiredInitialStateMigrator - extends ClassComponentRequiredFieldsMigrator { - ClassComponentRequiredInitialStateMigrator([Version? sdkVersion]) - : super('initialState', 'getInitialState', sdkVersion); - - @override - Future visitCascadeExpression(CascadeExpression node) async { - // Don't make any updates if the file is already null safe. - if (result.libraryElement.isNonNullableByDefault) { - return; - } - - super.visitCascadeExpression(node); - - final isInitialState = [relevantGetterName, relevantMethodName].contains( - node.thisOrAncestorOfType()?.declaredElement?.name); - - // If this cascade is not assigning values to defaultProps, bail. - if (!isInitialState) return; - - final cascadedInitialState = node.cascadeSections - .whereType() - .where((assignment) => assignment.leftHandSide is PropertyAccess) - .map((assignment) => StateAssignment(assignment)) - .where((prop) => prop.node.writeElement?.displayName != null); - - patchFieldDeclarations(getAllState, cascadedInitialState, node); - } -} diff --git a/lib/src/dart3_suggestors/null_safety_prep/connect_required_props.dart b/lib/src/dart3_suggestors/null_safety_prep/connect_required_props.dart deleted file mode 100644 index 83aba93d..00000000 --- a/lib/src/dart3_suggestors/null_safety_prep/connect_required_props.dart +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2024 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:analyzer/dart/element/element.dart'; -import 'package:analyzer/dart/element/type.dart'; -import 'package:collection/collection.dart'; -import 'package:over_react_codemod/src/dart3_suggestors/null_safety_prep/utils/props_utils.dart'; -import 'package:over_react_codemod/src/util.dart'; -import 'package:over_react_codemod/src/util/class_suggestor.dart'; - -import 'analyzer_plugin_utils.dart'; - -/// Suggestor that adds `@Props(disableRequiredPropValidation: {...})` annotations -/// for props that are set in `connect` components. -class ConnectRequiredProps extends RecursiveAstVisitor with ClassSuggestor { - /// Running list of props that should be ignored per mixin that will all be added - /// at the end in [generatePatches]. - final _ignoredPropsByMixin = >{}; - - @override - visitCascadeExpression(CascadeExpression node) { - super.visitCascadeExpression(node); - - // Verify the builder usage is within the `connect` method call. - final connect = node.thisOrAncestorMatching( - (n) => n is MethodInvocation && n.methodName.name == 'connect'); - if (connect == null) return; - - // Verify the builder usage is within one of the targeted connect args. - final connectArgs = - connect.argumentList.arguments.whereType(); - final connectArg = node.thisOrAncestorMatching((n) => - n is NamedExpression && - connectArgs.contains(n) && - connectArgNames.contains(n.name.label.name)); - if (connectArg == null) return; - - final cascadedProps = getCascadedProps(node).toList(); - - for (final field in cascadedProps) { - final propsElement = - node.staticType?.typeOrBound.tryCast()?.element; - if (propsElement == null) continue; - - // Keep a running list of props to ignore per props mixin. - final fieldName = field.name.name; - _ignoredPropsByMixin.putIfAbsent(propsElement, () => {}).add(fieldName); - } - } - - @override - Future generatePatches() async { - _ignoredPropsByMixin.clear(); - final result = await context.getResolvedUnit(); - if (result == null) { - throw Exception( - 'Could not get resolved result for "${context.relativePath}"'); - } - // Don't make any updates if the file is already null safe. - if (result.libraryElement.isNonNullableByDefault) { - return; - } - result.unit.accept(this); - - // Add the patches at the end so that all the props to be ignored can be collected - // from the different args in `connect` before adding patches to avoid duplicate patches. - _ignoredPropsByMixin.forEach((propsClass, propsToIgnore) { - final classNode = - NodeLocator2(propsClass.nameOffset).searchWithin(result.unit); - if (classNode != null && classNode is NamedCompilationUnitMember) { - final existingAnnotation = - classNode.metadata.where((c) => c.name.name == 'Props').firstOrNull; - - if (existingAnnotation == null) { - // Add full @Props annotation if it doesn't exist. - yieldPatch( - '@Props($annotationArg: {${propsToIgnore.map((p) => '\'$p\'').join(', ')}})\n', - classNode.offset, - classNode.offset); - } else { - final existingAnnotationArg = existingAnnotation.arguments?.arguments - .whereType() - .where((e) => e.name.label.name == annotationArg) - .firstOrNull; - - if (existingAnnotationArg == null) { - // Add disable validation arg to existing @Props annotation. - final offset = existingAnnotation.arguments?.leftParenthesis.end; - if (offset != null) { - yieldPatch( - '$annotationArg: {${propsToIgnore.map((p) => '\'$p\'').join(', ')}}${existingAnnotation.arguments?.arguments.isNotEmpty ?? false ? ', ' : ''}', - offset, - offset); - } - } else { - // Add props to disable validation for to the existing list of disabled - // props in the @Props annotation if they aren't already listed. - final existingList = - existingAnnotationArg.expression.tryCast(); - if (existingList != null) { - final alreadyIgnored = existingList.elements - .whereType() - .map((e) => e.stringValue) - .toList(); - final newPropsToIgnore = - propsToIgnore.where((p) => !alreadyIgnored.contains(p)); - if (newPropsToIgnore.isNotEmpty) { - final offset = existingList.leftBracket.end; - yieldPatch( - '${newPropsToIgnore.map((p) => '\'$p\'').join(', ')}, ', - offset, - offset); - } - } - } - } - } - }); - } - - static const connectArgNames = [ - 'mapStateToProps', - 'mapStateToPropsWithOwnProps', - 'mapDispatchToProps', - 'mapDispatchToPropsWithOwnProps', - ]; - static const annotationArg = 'disableRequiredPropValidation'; -} diff --git a/lib/src/dart3_suggestors/null_safety_prep/required_flux_props.dart b/lib/src/dart3_suggestors/null_safety_prep/required_flux_props.dart deleted file mode 100644 index 30108b0a..00000000 --- a/lib/src/dart3_suggestors/null_safety_prep/required_flux_props.dart +++ /dev/null @@ -1,268 +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/analysis/results.dart'; -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/dart/ast/visitor.dart'; -import 'package:analyzer/dart/element/element.dart'; -import 'package:analyzer/dart/element/type.dart'; -import 'package:collection/collection.dart'; -import 'package:meta/meta.dart'; -import 'package:over_react_codemod/src/util.dart'; -import 'package:over_react_codemod/src/util/class_suggestor.dart'; - -/// Suggestor that adds required `store` and/or `actions` prop(s) to the -/// call-site of `FluxUiComponent` instances that omit them since version -/// 5.0.0 of over_react makes flux `store`/`actions` props required. -/// -/// In the case of a component that is rendered in a scope where a store/actions -/// instance is available, but simply not passed along to the component, those -/// instance(s) will be used as the value for `props.store`/`props.actions`, -/// even though the component itself may not make use of them internally. -/// -/// In the case of a component that is rendered in a scope where a store/actions -/// instance is not available, `null` will be used as the value for the prop(s). -class RequiredFluxProps extends RecursiveAstVisitor with ClassSuggestor { - ResolvedUnitResult? _result; - - static const fluxPropsMixinName = 'FluxUiPropsMixin'; - - @visibleForTesting - static String getTodoForPossiblyValidStoreVar(String fluxStoreVarName) { - return ' // TODO: There is a valid flux store value in scope that could be set here (`$fluxStoreVarName`). Should it be set?'; - } - - @override - visitCascadeExpression(CascadeExpression node) { - final cascadeWriteEl = node.staticType?.element; - if (cascadeWriteEl is! ClassElement) return; - const typesToIgnore = { - '_PanelTitleProps', - 'PanelTitleProps', - 'PanelTitleV2Props', - '_PanelToolbarProps', - 'PanelToolbarProps', - }; - if (typesToIgnore.contains(cascadeWriteEl.name)) { - return; - } - final isReturnedAsDefaultProps = node.ancestors - .whereType() - .firstOrNull - ?.name - .lexeme - .contains(RegExp(r'getDefaultProps|defaultProps')) ?? - false; - if (isReturnedAsDefaultProps) return; - - final maybeFluxUiPropsMixin = cascadeWriteEl.mixins - .singleWhereOrNull((e) => e.element.name == fluxPropsMixinName); - if (maybeFluxUiPropsMixin == null) return; - - final fluxActionsType = maybeFluxUiPropsMixin.typeArguments[0]; - final fluxStoreType = maybeFluxUiPropsMixin.typeArguments[1]; - - final cascadingAssignments = - node.cascadeSections.whereType(); - var storeAssigned = cascadingAssignments.any((cascade) { - final lhs = cascade.leftHandSide; - return lhs is PropertyAccess && lhs.propertyName.name == 'store'; - }); - var actionsAssigned = cascadingAssignments.any((cascade) { - final lhs = cascade.leftHandSide; - return lhs is PropertyAccess && lhs.propertyName.name == 'actions'; - }); - - if (!storeAssigned) { - storeAssigned = true; - final storeValue = - _getNameOfVarOrFieldInScopeWithType(node, fluxStoreType); - if (storeValue != null) { - final todoComment = getTodoForPossiblyValidStoreVar(storeValue); - yieldNewCascadeSection(node, '$todoComment\n..store = null'); - } else { - yieldNewCascadeSection(node, '..store = null'); - } - } - - if (!actionsAssigned) { - actionsAssigned = true; - final actionsValue = - _getNameOfVarOrFieldInScopeWithType(node, fluxActionsType) ?? 'null'; - yieldNewCascadeSection(node, '..actions = $actionsValue'); - } - } - - void yieldNewCascadeSection(CascadeExpression node, String newSection) { - final offset = node.target.end; - yieldPatch(newSection, offset, offset); - } - - @override - Future generatePatches() async { - _result = await context.getResolvedUnit(); - if (_result == null) { - throw Exception( - 'Could not get resolved result for "${context.relativePath}"'); - } - _result!.unit.accept(this); - } -} - -class InScopeVariable { - final String name; - final DartType? type; - - InScopeVariable(this.name, this.type); -} - -String? _getNameOfVarOrFieldInScopeWithType(AstNode node, DartType type) { - if (type is DynamicType || type.isDartCoreNull) return null; - - final mostInScopeVariables = node.ancestors.expand((ancestor) sync* { - if (ancestor is FunctionDeclaration) { - // Function arguments - final element = ancestor.declaredElement; - if (element != null) { - yield* element.parameters.map((p) => InScopeVariable(p.name, p.type)); - } - } else if (ancestor is Block) { - // Variables declared in the block (function body, if/else block, etc.) - yield* ancestor.statements - .whereType() - .expand((d) => d.variables.variables) - .map((v) => InScopeVariable(v.name.lexeme, v.declaredElement?.type)); - } else if (ancestor is ClassDeclaration) { - // Class fields - final element = ancestor.declaredElement; - if (element != null) { - yield* element.fields.map((f) => InScopeVariable(f.name, f.type)); - } - } else if (ancestor is CompilationUnit) { - // Top-level variables - yield* ancestor.declarations - .whereType() - .expand((d) => d.variables.variables) - .map((v) => InScopeVariable(v.name.lexeme, v.declaredElement?.type)); - } - }); - - // Usually we'd grab typeSystem from the ResolvedUnitResult, but we don't have access to that - // in this class, so just get it from the compilation unit. - final typeSystem = - (node.root as CompilationUnit).declaredElement!.library.typeSystem; - bool isMatchingType(DartType? maybeMatchingType) => - maybeMatchingType != null && - maybeMatchingType is! DynamicType && - typeSystem.isAssignableTo(maybeMatchingType, type); - - final inScopeVarName = mostInScopeVariables - .firstWhereOrNull((v) => isMatchingType(v.type)) - ?.name; - - final componentScopePropDetector = _ComponentScopeFluxPropsDetector(); - // Find actions/store in props of class components - componentScopePropDetector.handlePotentialClassComponent( - node.thisOrAncestorOfType()); - // Find actions/store in props of fn components - componentScopePropDetector.handlePotentialFunctionComponent( - node.thisOrAncestorOfType()); - - final inScopePropName = - componentScopePropDetector.found.firstWhereOrNull((el) { - final maybeMatchingType = componentScopePropDetector.getAccessorType(el); - return maybeMatchingType?.element?.name == type.element?.name; - })?.name; - - if (inScopeVarName != null && inScopePropName != null) { - // TODO: Do we need to handle this edge case with something better than returning null? - // No way to determine which should be used - the scoped variable or the field on props - // so return null to avoid setting the incorrect value on the consumer's code. - return null; - } - - if (inScopePropName != null) { - return '${componentScopePropDetector.propsName}.${inScopePropName}'; - } - - return inScopeVarName; -} - -bool _isFnComponentDeclaration(Expression? varInitializer) => - varInitializer is MethodInvocation && - varInitializer.methodName.name.startsWith('uiF'); - -/// A visitor to detect store/actions values in a props class (supports both class and fn components) -class _ComponentScopeFluxPropsDetector { - final Map _foundWithMappedTypes; - - List get found => - _foundWithMappedTypes.keys.toList(); - - _ComponentScopeFluxPropsDetector() : _foundWithMappedTypes = {}; - - String _propsName = 'props'; - - /// The name of the function component props arg, or the class component `props` instance field. - String get propsName => _propsName; - - DartType? getAccessorType(PropertyAccessorElement el) => - _foundWithMappedTypes[el]; - - void _lookForFluxStoreAndActionsInPropsClass(Element? elWithProps) { - if (elWithProps is ClassElement) { - final fluxPropsEl = elWithProps.mixins.singleWhereOrNull( - (e) => e.element.name == RequiredFluxProps.fluxPropsMixinName); - - if (fluxPropsEl != null) { - final actionsType = fluxPropsEl.typeArguments[0]; - final storeType = fluxPropsEl.typeArguments[1]; - fluxPropsEl.accessors.forEach((a) { - final accessorTypeName = a.declaration.variable.type.element?.name; - if (accessorTypeName == 'ActionsT') { - _foundWithMappedTypes.putIfAbsent(a.declaration, () => actionsType); - } else if (accessorTypeName == 'StoresT') { - _foundWithMappedTypes.putIfAbsent(a.declaration, () => storeType); - } - }); - } - } - } - - /// Visit function components - void handlePotentialFunctionComponent(MethodInvocation? node) { - if (node == null) return; - if (!_isFnComponentDeclaration(node)) return; - - final nodeType = node.staticType; - if (nodeType is FunctionType) { - final propsArg = - node.argumentList.arguments.firstOrNull as FunctionExpression?; - final propsArgName = - propsArg?.parameters?.parameterElements.firstOrNull?.name; - if (propsArgName != null) { - _propsName = propsArgName; - } - _lookForFluxStoreAndActionsInPropsClass(nodeType.returnType.element); - } - } - - /// Visit composite (class) components - void handlePotentialClassComponent(ClassDeclaration? node) { - if (node == null) return; - final elWithProps = - node.declaredElement?.supertype?.typeArguments.singleOrNull?.element; - _lookForFluxStoreAndActionsInPropsClass(elWithProps); - } -} diff --git a/lib/src/dart3_suggestors/null_safety_prep/state_mixin_suggestor.dart b/lib/src/dart3_suggestors/null_safety_prep/state_mixin_suggestor.dart deleted file mode 100644 index 284ceee8..00000000 --- a/lib/src/dart3_suggestors/null_safety_prep/state_mixin_suggestor.dart +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2024 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/visitor.dart'; -import 'package:analyzer/dart/element/element.dart'; -import 'package:over_react_codemod/src/dart3_suggestors/null_safety_prep/utils/hint_detection.dart'; -import 'package:over_react_codemod/src/util.dart'; -import 'package:analyzer/dart/ast/ast.dart'; - -import '../../util/class_suggestor.dart'; - -/// Suggestor to assist with preparations for null-safety by adding -/// nullability (`?`) hints to state field types. -/// -/// This is intended to be run after [ClassComponentRequiredInitialStateMigrator] -/// to make the rest of the state fields nullable. -class StateMixinSuggestor extends RecursiveAstVisitor - with ClassSuggestor { - @override - void visitVariableDeclaration(VariableDeclaration node) { - super.visitVariableDeclaration(node); - - final isStateClass = (node.declaredElement?.enclosingElement - ?.tryCast() - ?.allSupertypes - .any((s) => s.element.name == 'UiState') ?? - false); - if (!isStateClass) return; - - final fieldDeclaration = node.parentFieldDeclaration; - if (fieldDeclaration == null) return; - if (fieldDeclaration.isStatic) return; - if (fieldDeclaration.fields.isConst) return; - - final type = fieldDeclaration.fields.type; - if (type != null && - (requiredHintAlreadyExists(type) || nullableHintAlreadyExists(type))) { - return; - } - - // Make state field optional. - if (type != null) { - yieldPatch(nullableHint, type.end, type.end); - } - } - - @override - Future generatePatches() async { - final r = await context.getResolvedUnit(); - if (r == null) { - throw Exception( - 'Could not get resolved result for "${context.relativePath}"'); - } - - // Don't make any updates if the file is already null safe. - if (r.libraryElement.isNonNullableByDefault) { - return; - } - r.unit.accept(this); - } -} diff --git a/lib/src/dart3_suggestors/null_safety_prep/use_ref_init_migration.dart b/lib/src/dart3_suggestors/null_safety_prep/use_ref_init_migration.dart deleted file mode 100644 index 87a84c4f..00000000 --- a/lib/src/dart3_suggestors/null_safety_prep/use_ref_init_migration.dart +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2024 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'; - -/// Suggestor that finds instances of `useRef` function invocations that -/// pass an argument, and replaces them with `useRefInit` to prep for -/// null safety. -/// -/// Example: -/// -/// ```dart -/// // Before -/// final ref1 = useRef(someNonNulLValue); -/// final ref2 = useRef(null); -/// // After -/// final ref1 = useRefInit(someNonNulLValue); -/// final ref2 = useRef(); -/// ``` -class UseRefInitMigration extends RecursiveAstVisitor - with AstVisitingSuggestor { - @override - visitArgumentList(ArgumentList node) { - super.visitArgumentList(node); - - if (node.arguments.isEmpty) return; - - dynamic possibleInvocation = node.parent; - if (possibleInvocation is MethodInvocation) { - String fnName = ''; - if (possibleInvocation.function is SimpleIdentifier) { - fnName = (possibleInvocation.function as SimpleIdentifier).name; - } - - if (fnName == 'useRef') { - final argument = node.arguments.singleOrNull; - if (argument is NullLiteral) { - // Remove unnecessary null argument - yieldPatch('', argument.offset, argument.end); - } else { - yieldPatch('useRefInit', possibleInvocation.function.offset, - possibleInvocation.function.end); - } - } - } - } -} diff --git a/lib/src/dart3_suggestors/null_safety_prep/utils/class_component_required_fields.dart b/lib/src/dart3_suggestors/null_safety_prep/utils/class_component_required_fields.dart deleted file mode 100644 index 7e7ce2ec..00000000 --- a/lib/src/dart3_suggestors/null_safety_prep/utils/class_component_required_fields.dart +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2024 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/analysis/results.dart'; -import 'package:analyzer/dart/ast/token.dart'; -import 'package:analyzer/dart/element/element.dart'; -import 'package:analyzer/dart/element/type.dart'; -import 'package:collection/collection.dart'; -import 'package:over_react_codemod/src/dart3_suggestors/null_safety_prep/utils/hint_detection.dart'; -import 'package:over_react_codemod/src/util.dart'; -import 'package:over_react_codemod/src/util/component_usage.dart'; -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/dart/ast/visitor.dart'; -import 'package:pub_semver/pub_semver.dart'; - -import '../../../util/class_suggestor.dart'; -import '../analyzer_plugin_utils.dart'; - -/// A class shared by the suggestors that manage defaultProps/initialState. -abstract class ClassComponentRequiredFieldsMigrator< - Assignment extends PropOrStateAssignment> - extends RecursiveAstVisitor with ClassSuggestor { - final String relevantGetterName; - final String relevantMethodName; - - /// When set to a version that opts-in to Dart's null safety feature, - /// the `late` / `?` type modifiers will be actual modifiers rather - /// than commented hints. This should only be done using an explicit opt-in - /// flag from the executable as most consumers that have migrated to null-safety - /// will have already run this script prior to the null safety migration and thus - /// the `/*late*/` / `/*?*/` hints will already be converted to actual modifiers. - final Version? sdkVersion; - - ClassComponentRequiredFieldsMigrator( - this.relevantGetterName, this.relevantMethodName, - [this.sdkVersion]); - - late ResolvedUnitResult result; - final Set fieldData = {}; - - void patchFieldDeclarations( - Iterable Function(InterfaceElement) getAll, - Iterable cascadedDefaultPropsOrInitialState, - CascadeExpression node) { - for (final field in cascadedDefaultPropsOrInitialState) { - final isDefaultedToNull = - field.node.rightHandSide.staticType!.isDartCoreNull; - final fieldEl = (field.node.writeElement! as PropertyAccessorElement) - .variable as FieldElement; - final propsOrStateElement = - node.staticType?.typeOrBound.tryCast()?.element; - if (propsOrStateElement == null) continue; - final fieldDeclaration = _getFieldDeclaration(getAll, - propsOrStateElement: propsOrStateElement, fieldName: fieldEl.name); - // The field declaration is likely in another file which our logic currently doesn't handle. - // In this case, don't add an entry to `fieldData`. - if (fieldDeclaration == null) continue; - - fieldData.add(DefaultedOrInitializedDeclaration( - fieldDeclaration, fieldEl, isDefaultedToNull)); - } - - fieldData.where((data) => !data.patchedDeclaration).forEach((data) { - data.patch(yieldPatch, sdkVersion: sdkVersion); - }); - } - - VariableDeclaration? _getFieldDeclaration( - Iterable Function(InterfaceElement) getAll, - {required InterfaceElement propsOrStateElement, - required String fieldName}) { - // For component1 boilerplate its possible that `fieldEl` won't be found using `lookUpVariable` below - // since its `enclosingElement` will be the generated abstract mixin. So we'll use the provided `getAll` fn to - // cross reference the return value with the `fieldName`to locate the actual prop/state field declaration we want to patch. - final siblingFields = getAll(propsOrStateElement); - final matchingField = - siblingFields.singleWhereOrNull((element) => element.name == fieldName); - if (matchingField == null) return null; - - // NOTE: result.unit will only work if the declaration of the field is in this file - return lookUpVariable(matchingField, result.unit); - } - - @override - Future generatePatches() async { - // Clear so we don't share state across CompilationUnits - fieldData.clear(); - final r = await context.getResolvedUnit(); - if (r == null) { - throw Exception( - 'Could not get resolved result for "${context.relativePath}"'); - } - result = r; - r.unit.accept(this); - } -} - -class DefaultedOrInitializedDeclaration { - final VariableDeclaration fieldDecl; - final FieldElement fieldEl; - final bool isDefaultedToNull; - final String name; - - DefaultedOrInitializedDeclaration( - this.fieldDecl, this.fieldEl, this.isDefaultedToNull) - : _patchedDeclaration = false, - name = '${fieldDecl.name.lexeme}'; - - /// Whether the declaration has been patched with the late / nullable hints. - bool get patchedDeclaration => _patchedDeclaration; - bool _patchedDeclaration; - - void patch( - void Function(String updatedText, int startOffset, [int? endOffset]) - handleYieldPatch, - {Version? sdkVersion}) { - final parent = fieldDecl.parent! as VariableDeclarationList; - final keyword = parent.keyword; // e.g. var - final type = parent.type; - final fieldNameToken = fieldDecl.name; - if (type != null && - requiredHintAlreadyExists(type) && - (nullableHintAlreadyExists(type) || - nonNullableHintAlreadyExists(type))) { - // Short circuit - it has already been patched - _patchedDeclaration = true; - return; - } - - String? late = - type != null && requiredHintAlreadyExists(type) ? null : '/*late*/'; - String nullability = ''; - if (isDefaultedToNull) { - if (type == null || !nullableHintAlreadyExists(type)) { - nullability = nullableHint; - } - } else { - if (type == null || !nonNullableHintAlreadyExists(type)) { - nullability = nonNullableHint; - } - } - - if (sdkVersion != null && - VersionRange(min: Version.parse('2.12.0')).allows(sdkVersion)) { - if (late != null) { - // If the repo has opted into null safety, patch with the real thing instead of hints - late = 'late'; - // Unless it already has the late keyword applied - if ((type?.parent as VariableDeclarationList?)?.lateKeyword is Token) { - late = null; - } - } - - nullability = isDefaultedToNull ? '?' : ''; - - if (late == null && nullability.isEmpty) { - // Short circuit - it has already been patched - _patchedDeclaration = true; - return; - } - } - - late = late ?? ''; - // dynamic added if type is null b/c we gotta have a type to add the nullable `?`/`!` hints to - even if for some reason the prop/state decl. has no left side type. - final patchedType = - type == null ? 'dynamic$nullability' : '${type.toSource()}$nullability'; - final startOffset = - type?.offset ?? keyword?.offset ?? fieldNameToken.offset; - handleYieldPatch('$late $patchedType ', startOffset, fieldNameToken.offset); - - _patchedDeclaration = true; - } - - @override - bool operator ==(Object other) { - return other is DefaultedOrInitializedDeclaration && - other.fieldEl == this.fieldEl; - } - - @override - int get hashCode => this.fieldEl.hashCode; -} diff --git a/lib/src/dart3_suggestors/null_safety_prep/utils/hint_detection.dart b/lib/src/dart3_suggestors/null_safety_prep/utils/hint_detection.dart deleted file mode 100644 index 72cc40ea..00000000 --- a/lib/src/dart3_suggestors/null_safety_prep/utils/hint_detection.dart +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2024 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/util.dart'; - -/// Whether the nullability hint already exists after [type]. -bool nullableHintAlreadyExists(TypeAnnotation type) { - // The nullability hint will follow the type so we need to check the next token to find the comment if it exists. - final commentsPrecedingType = type.endToken.next?.precedingComments?.value(); - return commentsPrecedingType?.contains(nullableHint) ?? false; -} - -const nullableHint = '/*?*/'; - -/// Whether the non-nullable hint already exists after [type]. -bool nonNullableHintAlreadyExists(TypeAnnotation type) { - // The nullability hint will follow the type so we need to check the next token to find the comment if it exists. - final commentsPrecedingType = type.endToken.next?.precedingComments?.value(); - return commentsPrecedingType?.contains(nonNullableHint) ?? false; -} - -const nonNullableHint = '/*!*/'; - -const lateHint = '/*late*/'; - -/// Whether the late hint already exists before [type] -bool requiredHintAlreadyExists(TypeAnnotation type) { - // Since the `/*late*/` comment is possibly adjacent to the prop declaration's doc comments, - // we have to recursively traverse the `precedingComments` in order to determine if the `/*late*/` - // comment actually exists. - return allCommentsForNode(type).any((t) => t.value() == lateHint); -} diff --git a/lib/src/dart3_suggestors/null_safety_prep/utils/props_utils.dart b/lib/src/dart3_suggestors/null_safety_prep/utils/props_utils.dart deleted file mode 100644 index a902342c..00000000 --- a/lib/src/dart3_suggestors/null_safety_prep/utils/props_utils.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:over_react_codemod/src/util/component_usage.dart'; - -/// Returns a list of props from [cascade]. -Iterable getCascadedProps(CascadeExpression cascade) { - return cascade.cascadeSections - .whereType() - .where((assignment) => assignment.leftHandSide is PropertyAccess) - .map((assignment) => PropAssignment(assignment)) - .where((prop) => prop.node.writeElement?.displayName != null); -} diff --git a/lib/src/executables/null_safety_migrator_companion.dart b/lib/src/executables/null_safety_migrator_companion.dart deleted file mode 100644 index cf7335f6..00000000 --- a/lib/src/executables/null_safety_migrator_companion.dart +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2024 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/dart3_suggestors/null_safety_prep/class_component_required_initial_state.dart'; -import 'package:over_react_codemod/src/dart3_suggestors/null_safety_prep/connect_required_props.dart'; -import 'package:over_react_codemod/src/util.dart'; - -import '../dart3_suggestors/null_safety_prep/callback_ref_hint_suggestor.dart'; -import '../dart3_suggestors/null_safety_prep/state_mixin_suggestor.dart'; -import '../util/package_util.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:null_safety_migrator_companion -"""; - -/// Codemods in this executable add nullability "hints" to assist with a -/// null-safety migration. -/// -/// If it has not already been run, the `null_safety_prep` codemod should -/// also be run when migrating to null-safety. -void main(List args) async { - final parser = ArgParser.allowAnything(); - - final parsedArgs = parser.parse(args); - final packageRoot = findPackageRootFor('.'); - await runPubGetIfNeeded(packageRoot); - final dartPaths = allDartPathsExceptHiddenAndGenerated(); - - exitCode = await runInteractiveCodemodSequence( - dartPaths, - [ - CallbackRefHintSuggestor(), - ], - defaultYes: true, - args: parsedArgs.rest, - additionalHelpOutput: parser.usage, - changesRequiredOutput: _changesRequiredOutput, - ); - - if (exitCode != 0) return; - - exitCode = await runInteractiveCodemodSequence( - dartPaths, - [ - ClassComponentRequiredInitialStateMigrator(), - ], - defaultYes: true, - args: parsedArgs.rest, - additionalHelpOutput: parser.usage, - changesRequiredOutput: _changesRequiredOutput, - ); - - if (exitCode != 0) return; - - exitCode = await runInteractiveCodemodSequence( - dartPaths, - [ - StateMixinSuggestor(), - ], - defaultYes: true, - args: parsedArgs.rest, - additionalHelpOutput: parser.usage, - changesRequiredOutput: _changesRequiredOutput, - ); - - if (exitCode != 0) return; - - exitCode = await runInteractiveCodemodSequence( - dartPaths, - [ - ConnectRequiredProps(), - ], - defaultYes: true, - args: parsedArgs.rest, - additionalHelpOutput: parser.usage, - changesRequiredOutput: _changesRequiredOutput, - ); -} diff --git a/lib/src/executables/null_safety_prep.dart b/lib/src/executables/null_safety_prep.dart deleted file mode 100644 index e3bc2bae..00000000 --- a/lib/src/executables/null_safety_prep.dart +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2024 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/dart3_suggestors/null_safety_prep/use_ref_init_migration.dart'; -import 'package:over_react_codemod/src/util.dart'; - -import '../dart3_suggestors/null_safety_prep/callback_ref_hint_suggestor.dart'; - -const _changesRequiredOutput = """ - To update your code, run the following commands in your repository: - pub global activate over_react_codemod - pub global run over_react_codemod:null_safety_prep -"""; - -/// Codemods in this executable should be changes that teams -/// can make ahead of moving forward with their null-safety migration. -/// -/// Codemods that do things like add nullability "hints" should be placed -/// within `null_safety_migrator_companion` - and run only when a team is -/// ready to move forward with a null-safety migration. -void main(List args) async { - final parser = ArgParser.allowAnything(); - - final parsedArgs = parser.parse(args); - final dartPaths = allDartPathsExceptHiddenAndGenerated(); - - exitCode = await runInteractiveCodemod( - dartPaths, - aggregate([ - UseRefInitMigration(), - ]), - defaultYes: true, - args: parsedArgs.rest, - additionalHelpOutput: parser.usage, - changesRequiredOutput: _changesRequiredOutput, - ); -} diff --git a/lib/src/executables/required_flux_props.dart b/lib/src/executables/required_flux_props.dart deleted file mode 100644 index dbba8707..00000000 --- a/lib/src/executables/required_flux_props.dart +++ /dev/null @@ -1,60 +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 'dart:io'; - -import 'package:args/args.dart'; -import 'package:codemod/codemod.dart'; -import 'package:logging/logging.dart'; -import 'package:over_react_codemod/src/dart3_suggestors/null_safety_prep/required_flux_props.dart'; -import 'package:over_react_codemod/src/ignoreable.dart'; -import 'package:over_react_codemod/src/util.dart'; -import 'package:over_react_codemod/src/util/package_util.dart'; - -const _changesRequiredOutput = """ - To update your code, run the following commands in your repository: - pub global activate over_react_codemod - pub global run over_react_codemod:required_flux_props -"""; - -final _log = Logger('orcm.required_flux_props'); - -Future pubGetForAllPackageRoots(Iterable files) async { - _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); - } -} - -void main(List args) async { - final parser = ArgParser.allowAnything(); - - final parsedArgs = parser.parse(args); - final dartPaths = allDartPathsExceptHidden(); - - await pubGetForAllPackageRoots(dartPaths); - - exitCode = await runInteractiveCodemod( - dartPaths, - aggregate([ - RequiredFluxProps(), - ].map((s) => ignoreable(s))), - defaultYes: true, - args: parsedArgs.rest, - additionalHelpOutput: parser.usage, - changesRequiredOutput: _changesRequiredOutput, - ); -} diff --git a/lib/src/intl_suggestors/intl_importer.dart b/lib/src/intl_suggestors/intl_importer.dart index 636509cf..dc8b99fa 100644 --- a/lib/src/intl_suggestors/intl_importer.dart +++ b/lib/src/intl_suggestors/intl_importer.dart @@ -148,7 +148,7 @@ _InsertionLocation _insertionLocationForPackageImport( final uriContent = importDirective.uri.stringValue; if (uriContent != null) { final uri = Uri.parse(uriContent); - return uri != null && uri.scheme != 'package' && uri.scheme != 'dart'; + return uri.scheme != 'package' && uri.scheme != 'dart'; } return true; }); diff --git a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart index 14cfa404..6bb6e58c 100644 --- a/lib/src/mui_suggestors/system_props_to_sx_migrator.dart +++ b/lib/src/mui_suggestors/system_props_to_sx_migrator.dart @@ -163,7 +163,7 @@ class SystemPropsToSxMigrator extends ComponentUsageMigrator { final fixmes = [ if (anySystemPropSetBeforeForwarding) - 'Previously, it was possible for forwarded system props to overwrite these migrated styles, but not anymore since sx takes precedence over any system props.' + 'Previously, it was possible for forwarded system props to overwrite these migrated styles, but not anymore since sx takes precedence over any system props.' + '\n Double-check that this new behavior is okay.', ]; String getFixmesSource() { diff --git a/lib/src/util/importer.dart b/lib/src/util/importer.dart index 954754b6..44a37eff 100644 --- a/lib/src/util/importer.dart +++ b/lib/src/util/importer.dart @@ -16,9 +16,6 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/source/line_info.dart'; import 'package:codemod/codemod.dart'; import 'package:collection/collection.dart'; -import 'package:logging/logging.dart'; - -final _log = Logger('muiImporter'); /// Creates a suggestor that adds [importUri] imports in libraries that reference /// the [importNamespace] (including in parts) but don't yet import it. diff --git a/lib/src/util/unused_import_remover.dart b/lib/src/util/unused_import_remover.dart index 07aeb559..fc92d46f 100644 --- a/lib/src/util/unused_import_remover.dart +++ b/lib/src/util/unused_import_remover.dart @@ -15,9 +15,6 @@ 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) { diff --git a/pubspec.yaml b/pubspec.yaml index cf8e87d0..d63ced4b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,11 +41,8 @@ dev_dependencies: executables: dart2_9_upgrade: dependency_validator_ignore: - null_safety_migrator_companion: - null_safety_prep: mui_migration: mui_system_props_migration: - required_flux_props: rmui_preparation: rmui_bundle_update: intl_message_migration: diff --git a/test/dart3_suggestors/null_safety_prep/callback_ref_hint_suggestor_test.dart b/test/dart3_suggestors/null_safety_prep/callback_ref_hint_suggestor_test.dart deleted file mode 100644 index 828be85a..00000000 --- a/test/dart3_suggestors/null_safety_prep/callback_ref_hint_suggestor_test.dart +++ /dev/null @@ -1,344 +0,0 @@ -// Copyright 2024 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/dart3_suggestors/null_safety_prep/callback_ref_hint_suggestor.dart'; -import 'package:test/test.dart'; - -import '../../mui_suggestors/components/shared.dart'; -import '../../resolved_file_context.dart'; -import '../../util.dart'; -import '../../util/component_usage_migrator_test.dart'; - -void main() { - final resolvedContext = SharedAnalysisContext.wsd; - - // 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('CallbackRefHintSuggestor', () { - final testSuggestor = getSuggestorTester( - CallbackRefHintSuggestor(), - resolvedContext: resolvedContext, - ); - - group('adds nullability hint to ref prop typed parameters', () { - test('', () async { - await testSuggestor( - input: withOverReactAndWsdImports(/*language=dart*/ ''' - content() { - var ref; - (ButtonToolbar()..ref = (ButtonElement r) => ref = r)(); - (Dom.div()..ref = (ButtonElement r) { ref = r; })(); - ref; - } - '''), - expectedOutput: withOverReactAndWsdImports(/*language=dart*/ ''' - content() { - var ref; - (ButtonToolbar()..ref = (ButtonElement /*?*/ r) => ref = r)(); - (Dom.div()..ref = (ButtonElement /*?*/ r) { ref = r; })(); - ref; - } - '''), - ); - }); - - test('for builders', () async { - await testSuggestor( - input: withOverReactAndWsdImports(/*language=dart*/ ''' - content() { - var ref; - (ButtonToolbar()..ref = (ButtonElement r) => ref = r); - (Dom.div()..ref = (ButtonElement r) { ref = r; }); - ref; - } - '''), - expectedOutput: withOverReactAndWsdImports(/*language=dart*/ ''' - content() { - var ref; - (ButtonToolbar()..ref = (ButtonElement /*?*/ r) => ref = r); - (Dom.div()..ref = (ButtonElement /*?*/ r) { ref = r; }); - ref; - } - '''), - ); - }); - }); - - group('adds nullability hint to casts in a callback ref body', () { - test('', () async { - await testSuggestor( - input: withOverReactAndWsdImports(/*language=dart*/ ''' - content() { - var ref; - (ButtonToolbar()..ref = (r) => ref = r as ButtonElement)(); - (Dom.div()..ref = (r) { ref = r as ButtonElement; })(); - ref; - } - '''), - expectedOutput: withOverReactAndWsdImports(/*language=dart*/ ''' - content() { - var ref; - (ButtonToolbar()..ref = (r) => ref = r as ButtonElement /*?*/)(); - (Dom.div()..ref = (r) { ref = r as ButtonElement /*?*/; })(); - ref; - } - '''), - ); - }); - - test('for builders', () async { - await testSuggestor( - input: withOverReactAndWsdImports(/*language=dart*/ ''' - content() { - var ref; - (ButtonToolbar()..ref = (r) => ref = r as ButtonElement); - (Dom.div()..ref = (r) { ref = r as ButtonElement; }); - ref; - } - '''), - expectedOutput: withOverReactAndWsdImports(/*language=dart*/ ''' - content() { - var ref; - (ButtonToolbar()..ref = (r) => ref = r as ButtonElement /*?*/); - (Dom.div()..ref = (r) { ref = r as ButtonElement /*?*/; }); - ref; - } - '''), - ); - }); - - test('only for casts of the ref param', () async { - await testSuggestor( - input: withOverReactAndWsdImports(/*language=dart*/ ''' - content() { - var ref; - final a = 1; - (ButtonToolbar() - ..ref = (r) { - ref = r as int; - ref = a as ButtonElement; - ref as int; - ref = r as ButtonElement; - })(); - (Dom.div() - ..ref = (ButtonElement r) { - ref = r as int; - ref = a as ButtonElement; - })(); - (ButtonToolbar()..ref = (ButtonElement r) => ref = a as ButtonElement)(); - (ButtonToolbar()..ref = (_) => ref = a as ButtonElement)(); - ref; - } - '''), - expectedOutput: withOverReactAndWsdImports(/*language=dart*/ ''' - content() { - var ref; - final a = 1; - (ButtonToolbar() - ..ref = (r) { - ref = r as int /*?*/; - ref = a as ButtonElement; - ref as int; - ref = r as ButtonElement /*?*/; - })(); - (Dom.div() - ..ref = (ButtonElement /*?*/ r) { - ref = r as int /*?*/; - ref = a as ButtonElement; - })(); - (ButtonToolbar()..ref = (ButtonElement /*?*/ r) => ref = a as ButtonElement)(); - (ButtonToolbar()..ref = (_) => ref = a as ButtonElement)(); - ref; - } - '''), - ); - }); - }); - - group('adds nullability hint to class ref variables', () { - test('', () async { - await testSuggestor( - input: withOverReactAndWsdImports(/*language=dart*/ ''' - ButtonElement ref1; - content() { - ButtonElement ref2; - ButtonElement ref3; - (ButtonToolbar()..ref = (r) => ref1 = r)(); - (Dom.div()..ref = (r) { - ButtonElement ref4; - ref2 = r; - final a = ButtonElement(); - ref3 = a; - ref4 = r; - ref4; - }); - ref1; - ref2; - ref3; - } - '''), - expectedOutput: withOverReactAndWsdImports(/*language=dart*/ ''' - ButtonElement /*?*/ ref1; - content() { - ButtonElement /*?*/ ref2; - ButtonElement ref3; - (ButtonToolbar()..ref = (r) => ref1 = r)(); - (Dom.div()..ref = (r) { - ButtonElement /*?*/ ref4; - ref2 = r; - final a = ButtonElement(); - ref3 = a; - ref4 = r; - ref4; - }); - ref1; - ref2; - ref3; - } - '''), - ); - }); - - test('unless there is no type on the declaration', () async { - await testSuggestor( - input: withOverReactAndWsdImports(/*language=dart*/ ''' - content() { - dynamic ref1; - var ref2; - (ButtonToolbar()..ref = (r) { - ref1 = r; - ref2 = r; - }); - ref1; - ref2; - } - '''), - expectedOutput: withOverReactAndWsdImports(/*language=dart*/ ''' - content() { - dynamic ref1; - var ref2; - (ButtonToolbar()..ref = (r) { - ref1 = r; - ref2 = r; - }); - ref1; - ref2; - } - '''), - ); - }); - }); - - test('does not add hints if they already exist', () async { - await testSuggestor( - input: withOverReactAndWsdImports(/*language=dart*/ ''' - content() { - ButtonElement /*?*/ ref; - (ButtonToolbar()..ref = (ButtonElement /*?*/ r) => ref = r)(); - (Dom.div()..ref = (r) { ref = r as ButtonElement /*?*/; })(); - ref; - } - '''), - expectedOutput: withOverReactAndWsdImports(/*language=dart*/ ''' - content() { - ButtonElement /*?*/ ref; - (ButtonToolbar()..ref = (ButtonElement /*?*/ r) => ref = r)(); - (Dom.div()..ref = (r) { ref = r as ButtonElement /*?*/; })(); - ref; - } - '''), - ); - }); - - test('does not add hints for non-ref props', () async { - await testSuggestor( - input: withOverReactAndWsdImports(/*language=dart*/ ''' - content() { - ButtonElement ref; - (ButtonToolbar()..onClick = (r) { ref = r as ButtonElement; })(); - ref; - } - '''), - expectedOutput: withOverReactAndWsdImports(/*language=dart*/ ''' - content() { - ButtonElement ref; - (ButtonToolbar()..onClick = (r) { ref = r as ButtonElement; })(); - ref; - } - '''), - ); - }); - - group('makes no update if file is already on a null safe Dart version', () { - final resolvedContext = SharedAnalysisContext.overReactNullSafe; - - // 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); - - late SuggestorTester nullSafeTestSuggestor; - - setUp(() { - nullSafeTestSuggestor = getSuggestorTester( - CallbackRefHintSuggestor(), - resolvedContext: resolvedContext, - ); - }); - - test('', () async { - await nullSafeTestSuggestor( - expectedPatchCount: 0, - input: withOverReactImport(/*language=dart*/ ''' - import 'dart:html'; - - content() { - var ref; - (Dom.div()..ref = (ButtonElement r) { ref = r; })(); - ref; - } - '''), - ); - }); - - test('unless there is a lang version comment', () async { - await nullSafeTestSuggestor( - input: withOverReactImport(/*language=dart*/ ''' - import 'dart:html'; - - content() { - var ref; - (Dom.div()..ref = (ButtonElement r) { ref = r; })(); - ref; - } - ''', filePrefix: '// @dart=2.11\n'), - expectedOutput: withOverReactImport(/*language=dart*/ ''' - import 'dart:html'; - - content() { - var ref; - (Dom.div()..ref = (ButtonElement /*?*/ r) { ref = r; })(); - ref; - } - ''', filePrefix: '// @dart=2.11\n'), - // Ignore error on language version comment. - isExpectedError: (error) => - error.errorCode.name.toLowerCase() == - 'illegal_language_version_override', - ); - }); - }); - }, tags: 'wsd'); -} diff --git a/test/dart3_suggestors/null_safety_prep/required_flux_props_test.dart b/test/dart3_suggestors/null_safety_prep/required_flux_props_test.dart deleted file mode 100644 index c8e84f72..00000000 --- a/test/dart3_suggestors/null_safety_prep/required_flux_props_test.dart +++ /dev/null @@ -1,1656 +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:meta/meta.dart'; -import 'package:over_react_codemod/src/dart3_suggestors/null_safety_prep/required_flux_props.dart'; -import 'package:test/test.dart'; - -import '../../resolved_file_context.dart'; -import '../../util.dart'; -import '../../util/component_usage_migrator_test.dart'; - -void main() { - final resolvedContext = SharedAnalysisContext.overReact; - - // 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('RequiredFluxProps', () { - late SuggestorTester testSuggestor; - - setUp(() { - testSuggestor = getSuggestorTester( - RequiredFluxProps(), - resolvedContext: resolvedContext, - ); - }); - - test( - 'leaves builders alone if they don\'t use FluxUiPropsMixin, ' - 'even if they have props named store/actions', () async { - await testSuggestor( - isExpectedError: (err) => err.message - .contains(RegExp(r"'(theStore|theActions)' isn't used.")), - expectedPatchCount: 0, - input: withFluxComponentUsage(/*language=dart*/ r''' - main() { - final theStore = BazFooStore(); - final theActions = BazFooActions(); - - return (NotFoo() - ..id = '123' - )(); - } - '''), - ); - }); - - test('leaves defaultProps/getDefaultProps alone', () async { - await testSuggestor( - expectedPatchCount: 0, - input: withFluxComponentUsage(/*language=dart*/ r''' - class FizComponent extends FluxUiComponent2 { - @override - getDefaultProps() => newProps()..id = '123'; - - @override - get defaultProps => newProps()..id = '123'; - - @override - render() => null; - } - '''), - ); - }); - - test('leaves PanelTitle/PanelTitleV2 alone', () async { - await testSuggestor( - expectedPatchCount: 0, - input: withMockPanelTitleComponents(/*language=dart*/ r''' - main() { - final pt = (PanelTitle()..id = 'pt')(); - final pt2 = (PanelTitleV2()..id = 'pt2')(); - final pt3 = (_PanelTitle()..id = 'pt3')(); - return [pt, pt2, pt3]; - } - '''), - ); - }); - - test('leaves PanelToolbars alone', () async { - await testSuggestor( - expectedPatchCount: 0, - input: withMockPanelToolbarComponents(/*language=dart*/ r''' - main() { - final pt = (PanelToolbar()..id = 'pt')(); - final pt2 = (_PanelToolbar()..id = 'pt2')(); - return [pt, pt2]; - } - '''), - ); - }); - - @isTestGroup - void sharedTests({required bool invokeBuilder}) { - String maybeInvokeBuilder(String builderString) { - return (!invokeBuilder ? builderString : '($builderString)()') + ';'; - } - - String expectedTodo(String varName) { - return RequiredFluxProps.getTodoForPossiblyValidStoreVar(varName); - } - - group( - 'patches ${invokeBuilder ? 'invoked' : 'un-invoked'} builders that use FluxUiPropsMixin and', - () { - group('have no actions setter', () { - test('when no actions var is available in scope', () async { - await testSuggestor( - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - main() { - final theStore = FooStore(); - ${maybeInvokeBuilder('''Foo()..store = theStore''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main() { - final theStore = FooStore(); - ${maybeInvokeBuilder(''' - Foo() - ..actions = null - ..store = theStore - ''')} - } - '''), - ); - }); - - test('unless the in-scope var is dynamic', () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'notTheActions' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - main() { - dynamic notTheActions = 123; - final theStore = FooStore(); - - ${maybeInvokeBuilder('''Foo()..store = theStore''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main() { - dynamic notTheActions = 123; - final theStore = FooStore(); - - ${maybeInvokeBuilder(''' - Foo() - ..actions = null - ..store = theStore - ''')} - } - '''), - ); - }); - - test('unless the in-scope var is null', () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'notTheActions' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - main() { - final notTheActions = null; - final theStore = FooStore(); - - ${maybeInvokeBuilder('''Foo()..store = theStore''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main() { - final notTheActions = null; - final theStore = FooStore(); - - ${maybeInvokeBuilder(''' - Foo() - ..actions = null - ..store = theStore - ''')} - } - '''), - ); - }); - - test('unless TActions is dynamic', () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'notTheActions' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - main() { - dynamic notTheActions = 123; - dynamic theStore = FooStore(); - - ${maybeInvokeBuilder('''DynamicFoo()..store = theStore''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main() { - dynamic notTheActions = 123; - dynamic theStore = FooStore(); - - ${maybeInvokeBuilder(''' - DynamicFoo() - ..actions = null - ..store = theStore - ''')} - } - '''), - ); - }); - - group('when a top-level actions var is available', () { - test('unless the type does not match (uses null instead)', - () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theActions' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - final theActions = BazFooActions(); - main() { - final theStore = FooStore(); - ${maybeInvokeBuilder(''' - Foo() - ..store = theStore - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - final theActions = BazFooActions(); - main() { - final theStore = FooStore(); - ${maybeInvokeBuilder(''' - Foo() - ..actions = null - ..store = theStore - ''')} - } - '''), - ); - }); - - test('and the type matches', () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theActions' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - final theActions = FooActions(); - main() { - final theStore = FooStore(); - ${maybeInvokeBuilder(''' - Foo() - ..store = theStore - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - final theActions = FooActions(); - main() { - final theStore = FooStore(); - ${maybeInvokeBuilder(''' - Foo() - ..actions = theActions - ..store = theStore - ''')} - } - '''), - ); - }); - }); - - group('when an actions var is available in block function scope', () { - test('unless the type does not match (uses null instead)', - () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theActions' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - main() { - final theStore = FooStore(); - final theActions = BazFooActions(); - - ${maybeInvokeBuilder(''' - Foo() - ..store = theStore - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main() { - final theStore = FooStore(); - final theActions = BazFooActions(); - - ${maybeInvokeBuilder(''' - Foo() - ..actions = null - ..store = theStore - ''')} - } - '''), - ); - }); - - test('and the type matches', () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theActions' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - main() { - final theStore = FooStore(); - final theActions = FooActions(); - - ${maybeInvokeBuilder(''' - Foo() - ..store = theStore - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main() { - final theStore = FooStore(); - final theActions = FooActions(); - - ${maybeInvokeBuilder(''' - Foo() - ..actions = theActions - ..store = theStore - ''')} - } - '''), - ); - }); - }); - - group('when an actions var is available as a function argument', () { - test('unless the type does not match (uses null instead)', - () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theActions' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - main(BazFooActions theActions) { - final theStore = FooStore(); - - ${maybeInvokeBuilder(''' - Foo() - ..store = theStore - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main(BazFooActions theActions) { - final theStore = FooStore(); - - ${maybeInvokeBuilder(''' - Foo() - ..actions = null - ..store = theStore - ''')} - } - '''), - ); - }); - - test('and the type matches', () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theActions' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - main(FooActions theActions) { - final theStore = FooStore(); - - ${maybeInvokeBuilder(''' - Foo() - ..store = theStore - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main(FooActions theActions) { - final theStore = FooStore(); - - ${maybeInvokeBuilder(''' - Foo() - ..actions = theActions - ..store = theStore - ''')} - } - '''), - ); - }); - }); - - group('when an actions var is available as a class field', () { - test('unless the type does not match (uses null instead)', - () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theActions' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - class TheBaz { - final BazFooActions theActions; - final FooStore theStore; - TheBaz(this.theActions, this.theStore); - - someMethod() { - ${maybeInvokeBuilder(''' - Foo() - ..store = theStore - ''')} - } - } - '''), - expectedOutput: withFluxComponentUsage(''' - class TheBaz { - final BazFooActions theActions; - final FooStore theStore; - TheBaz(this.theActions, this.theStore); - - someMethod() { - ${maybeInvokeBuilder(''' - Foo() - ..actions = null - ..store = theStore - ''')} - } - } - '''), - ); - }); - - test('and the type matches', () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theActions' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - class TheFoo { - final FooActions theActions; - final FooStore theStore; - TheFoo(this.theActions, this.theStore); - - someMethod() { - ${maybeInvokeBuilder(''' - Foo() - ..store = theStore - ''')} - } - } - '''), - expectedOutput: withFluxComponentUsage(''' - class TheFoo { - final FooActions theActions; - final FooStore theStore; - TheFoo(this.theActions, this.theStore); - - someMethod() { - ${maybeInvokeBuilder(''' - Foo() - ..actions = theActions - ..store = theStore - ''')} - } - } - '''), - ); - }); - }); - - group('when an actions var is available in function component props', - () { - test('unless the type does not match (uses null instead)', - () async { - await testSuggestor( - isExpectedError: (err) => err.message.contains('someFunction'), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - class FooConsumerProps = UiProps with FluxUiPropsMixin; - final FooConsumer = uiFunction( - (localProps) { - someFunction() { - return ${maybeInvokeBuilder('''Foo() - ..store = FooStore() - ''')} - } - - return null; - }, - _\$FooConsumerConfig, // ignore: undefined_identifier - ); - '''), - expectedOutput: withFluxComponentUsage(''' - class FooConsumerProps = UiProps with FluxUiPropsMixin; - final FooConsumer = uiFunction( - (localProps) { - someFunction() { - return ${maybeInvokeBuilder('''Foo() - ..actions = null - ..store = FooStore() - ''')} - } - - return null; - }, - _\$FooConsumerConfig, // ignore: undefined_identifier - ); - '''), - ); - }); - - test('and the type matches', () async { - await testSuggestor( - isExpectedError: (err) => err.message.contains('someFunction'), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - class FooConsumerProps = UiProps with FluxUiPropsMixin; - final FooConsumer = uiFunction( - (localProps) { - someFunction() { - return ${maybeInvokeBuilder('''Foo() - ..store = localProps.store - ''')} - } - - return null; - }, - _\$FooConsumerConfig, // ignore: undefined_identifier - ); - '''), - expectedOutput: withFluxComponentUsage(''' - class FooConsumerProps = UiProps with FluxUiPropsMixin; - final FooConsumer = uiFunction( - (localProps) { - someFunction() { - return ${maybeInvokeBuilder('''Foo() - ..actions = localProps.actions - ..store = localProps.store - ''')} - } - - return null; - }, - _\$FooConsumerConfig, // ignore: undefined_identifier - ); - '''), - ); - }); - }); - - group('when an actions var is available in class component props', - () { - test('unless the type does not match (uses null instead)', - () async { - await testSuggestor( - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - // ignore: undefined_identifier - UiFactory FooConsumer = castUiFactory(_\$FooConsumer); - class FooConsumerProps = UiProps with FluxUiPropsMixin; - class FooConsumerComponent extends FluxUiComponent2 { - someMethod() { - return ${maybeInvokeBuilder('''Foo() - ..store = FooStore() - ''')} - } - - @override - render() => null; - } - '''), - expectedOutput: withFluxComponentUsage(''' - // ignore: undefined_identifier - UiFactory FooConsumer = castUiFactory(_\$FooConsumer); - class FooConsumerProps = UiProps with FluxUiPropsMixin; - class FooConsumerComponent extends FluxUiComponent2 { - someMethod() { - return ${maybeInvokeBuilder('''Foo() - ..actions = null - ..store = FooStore() - ''')} - } - - @override - render() => null; - } - '''), - ); - }); - - test('and the type matches', () async { - await testSuggestor( - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - // ignore: undefined_identifier - UiFactory FooConsumer = castUiFactory(_\$FooConsumer); - class FooConsumerProps = UiProps with FluxUiPropsMixin; - class FooConsumerComponent extends FluxUiComponent2 { - someMethod() { - return ${maybeInvokeBuilder('''Foo() - ..store = props.store - ''')} - } - - @override - render() => null; - } - '''), - expectedOutput: withFluxComponentUsage(''' - // ignore: undefined_identifier - UiFactory FooConsumer = castUiFactory(_\$FooConsumer); - class FooConsumerProps = UiProps with FluxUiPropsMixin; - class FooConsumerComponent extends FluxUiComponent2 { - someMethod() { - return ${maybeInvokeBuilder('''Foo() - ..actions = props.actions - ..store = props.store - ''')} - } - - @override - render() => null; - } - '''), - ); - }); - }); - }); - - group('have no store setter', () { - test('when no store var is available in scope', () async { - await testSuggestor( - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - main() { - final theActions = FooActions(); - - ${maybeInvokeBuilder('''Foo() - ..actions = theActions - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main() { - final theActions = FooActions(); - - ${maybeInvokeBuilder('''Foo() - ..store = null - ..actions = theActions - ''')} - } - '''), - ); - }); - - test('unless TStore is dynamic', () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'notTheStore' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - main() { - dynamic notTheStore = 123; - dynamic theActions = FooActions(); - - ${maybeInvokeBuilder('''DynamicFoo()..actions = theActions''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main() { - dynamic notTheStore = 123; - dynamic theActions = FooActions(); - - ${maybeInvokeBuilder(''' - DynamicFoo() - ..store = null - ..actions = theActions - ''')} - } - '''), - ); - }); - - group('when a top-level store var is available', () { - test('unless the type does not match (uses null instead)', - () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theStore' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - final theStore = BazFooStore(); - main() { - final theActions = FooActions(); - - ${maybeInvokeBuilder('''Foo() - ..actions = theActions - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - final theStore = BazFooStore(); - main() { - final theActions = FooActions(); - - ${maybeInvokeBuilder('''Foo() - ..store = null - ..actions = theActions - ''')} - } - '''), - ); - }); - - test('and the type matches', () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theStore' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - final theStore = FooStore(); - main() { - final theActions = FooActions(); - - ${maybeInvokeBuilder('''Foo() - ..actions = theActions - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - final theStore = FooStore(); - main() { - final theActions = FooActions(); - - ${maybeInvokeBuilder('''Foo()${expectedTodo('theStore')} - ..store = null - ..actions = theActions - ''')} - } - '''), - ); - }); - }); - - group('when a store var is available in block function scope', () { - test('unless the type does not match (uses null instead)', - () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theStore' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - main() { - final theStore = BazFooStore(); - final theActions = FooActions(); - - ${maybeInvokeBuilder('''Foo() - ..actions = theActions - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main() { - final theStore = BazFooStore(); - final theActions = FooActions(); - - ${maybeInvokeBuilder('''Foo() - ..store = null - ..actions = theActions - ''')} - } - '''), - ); - }); - - test('and the type matches', () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theStore' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - main() { - final theStore = FooStore(); - final theActions = FooActions(); - - ${maybeInvokeBuilder('''Foo() - ..actions = theActions - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main() { - final theStore = FooStore(); - final theActions = FooActions(); - - ${maybeInvokeBuilder('''Foo()${expectedTodo('theStore')} - ..store = null - ..actions = theActions - ''')} - } - '''), - ); - }); - }); - - group('when a store var is available as a function argument', () { - test('unless the type does not match (uses null instead)', - () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theStore' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - main(BazFooStore theStore) { - final theActions = FooActions(); - - ${maybeInvokeBuilder(''' - Foo() - ..actions = theActions - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main(BazFooStore theStore) { - final theActions = FooActions(); - - ${maybeInvokeBuilder(''' - Foo() - ..store = null - ..actions = theActions - ''')} - } - '''), - ); - }); - - test('and the type matches', () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theStore' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - main(FooStore theStore) { - final theActions = FooActions(); - - ${maybeInvokeBuilder(''' - Foo() - ..actions = theActions - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main(FooStore theStore) { - final theActions = FooActions(); - - ${maybeInvokeBuilder(''' - Foo()${expectedTodo('theStore')} - ..store = null - ..actions = theActions - ''')} - } - '''), - ); - }); - }); - - group('when a store var is available as a class field', () { - test('unless the type does not match (uses null instead)', - () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theStore' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - class TheBaz { - final FooActions theActions; - final BazFooStore theStore; - TheBaz(this.theActions, this.theStore); - - someMethod() { - ${maybeInvokeBuilder(''' - Foo() - ..actions = theActions - ''')} - } - } - '''), - expectedOutput: withFluxComponentUsage(''' - class TheBaz { - final FooActions theActions; - final BazFooStore theStore; - TheBaz(this.theActions, this.theStore); - - someMethod() { - ${maybeInvokeBuilder(''' - Foo() - ..store = null - ..actions = theActions - ''')} - } - } - '''), - ); - }); - - test('and the type matches', () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theStore' isn't used.")), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - class TheFoo { - final FooActions theActions; - final FooStore theStore; - TheFoo(this.theActions, this.theStore); - - someMethod() { - ${maybeInvokeBuilder(''' - Foo() - ..actions = theActions - ''')} - } - } - '''), - expectedOutput: withFluxComponentUsage(''' - class TheFoo { - final FooActions theActions; - final FooStore theStore; - TheFoo(this.theActions, this.theStore); - - someMethod() { - ${maybeInvokeBuilder(''' - Foo()${expectedTodo('theStore')} - ..store = null - ..actions = theActions - ''')} - } - } - '''), - ); - }); - }); - - group('when a store var is available in function component props', - () { - test('unless the type does not match (uses null instead)', - () async { - await testSuggestor( - isExpectedError: (err) => err.message.contains('someFunction'), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - class FooConsumerProps = UiProps with FluxUiPropsMixin; - final FooConsumer = uiFunction( - (localProps) { - someFunction() { - return ${maybeInvokeBuilder('''Foo() - ..actions = FooActions() - ''')} - } - - return null; - }, - _\$FooConsumerConfig, // ignore: undefined_identifier - ); - '''), - expectedOutput: withFluxComponentUsage(''' - class FooConsumerProps = UiProps with FluxUiPropsMixin; - final FooConsumer = uiFunction( - (localProps) { - someFunction() { - return ${maybeInvokeBuilder('''Foo() - ..store = null - ..actions = FooActions() - ''')} - } - - return null; - }, - _\$FooConsumerConfig, // ignore: undefined_identifier - ); - '''), - ); - }); - - test('and the type matches', () async { - await testSuggestor( - isExpectedError: (err) => err.message.contains('someFunction'), - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - class FooConsumerProps = UiProps with FluxUiPropsMixin; - final FooConsumer = uiFunction( - (localProps) { - someFunction() { - return ${maybeInvokeBuilder('''Foo() - ..actions = localProps.actions - ''')} - } - - return null; - }, - _\$FooConsumerConfig, // ignore: undefined_identifier - ); - '''), - expectedOutput: withFluxComponentUsage(''' - class FooConsumerProps = UiProps with FluxUiPropsMixin; - final FooConsumer = uiFunction( - (localProps) { - someFunction() { - return ${maybeInvokeBuilder('''Foo()${expectedTodo('localProps.store')} - ..store = null - ..actions = localProps.actions - ''')} - } - - return null; - }, - _\$FooConsumerConfig, // ignore: undefined_identifier - ); - '''), - ); - }); - }); - - group('when a store var is available in class component props', () { - test('unless the type does not match (uses null instead)', - () async { - await testSuggestor( - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - // ignore: undefined_identifier - UiFactory FooConsumer = castUiFactory(_\$FooConsumer); - class FooConsumerProps = UiProps with FluxUiPropsMixin; - class FooConsumerComponent extends FluxUiComponent2 { - someMethod() { - return ${maybeInvokeBuilder('''Foo() - ..actions = FooActions() - ''')} - } - - @override - render() => null; - } - '''), - expectedOutput: withFluxComponentUsage(''' - // ignore: undefined_identifier - UiFactory FooConsumer = castUiFactory(_\$FooConsumer); - class FooConsumerProps = UiProps with FluxUiPropsMixin; - class FooConsumerComponent extends FluxUiComponent2 { - someMethod() { - return ${maybeInvokeBuilder('''Foo() - ..store = null - ..actions = FooActions() - ''')} - } - - @override - render() => null; - } - '''), - ); - }); - - test('and the type matches', () async { - await testSuggestor( - expectedPatchCount: 1, - input: withFluxComponentUsage(''' - // ignore: undefined_identifier - UiFactory FooConsumer = castUiFactory(_\$FooConsumer); - class FooConsumerProps = UiProps with FluxUiPropsMixin; - class FooConsumerComponent extends FluxUiComponent2 { - someMethod() { - return ${maybeInvokeBuilder('''Foo() - ..actions = props.actions - ''')} - } - - @override - render() => null; - } - '''), - expectedOutput: withFluxComponentUsage(''' - // ignore: undefined_identifier - UiFactory FooConsumer = castUiFactory(_\$FooConsumer); - class FooConsumerProps = UiProps with FluxUiPropsMixin; - class FooConsumerComponent extends FluxUiComponent2 { - someMethod() { - return ${maybeInvokeBuilder('''Foo()${expectedTodo('props.store')} - ..store = null - ..actions = props.actions - ''')} - } - - @override - render() => null; - } - '''), - ); - }); - }); - }); - - group('have no store or actions setter', () { - test('when no store or actions var is available in scope', () async { - await testSuggestor( - expectedPatchCount: 2, - input: withFluxComponentUsage(''' - main() { - ${maybeInvokeBuilder('''Foo() - ..id = '123' - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main() { - ${maybeInvokeBuilder('''Foo() - ..store = null - ..actions = null - ..id = '123' - ''')} - } - '''), - ); - }); - - group('when store and/or actions var(s) are available', () { - group('in top-level scope', () { - test('unless the type(s) do not match (uses null instead):', - () async { - await testSuggestor( - isExpectedError: (err) => err.message - .contains(RegExp(r"'(theStore|theActions)' isn't used.")), - expectedPatchCount: 2, - input: withFluxComponentUsage(''' - final theStore = BazFooStore(); - final theActions = BazFooActions(); - main() { - ${maybeInvokeBuilder('''Foo() - ..id = '123' - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - final theStore = BazFooStore(); - final theActions = BazFooActions(); - main() { - ${maybeInvokeBuilder('''Foo() - ..store = null - ..actions = null - ..id = '123' - ''')} - } - '''), - ); - }); - - group('and the type(s) match:', () { - test('store AND actions', () async { - await testSuggestor( - isExpectedError: (err) => err.message.contains( - RegExp(r"'(theStore|theActions)' isn't used.")), - expectedPatchCount: 2, - input: withFluxComponentUsage(''' - final theStore = FooStore(); - final theActions = FooActions(); - main() { - ${maybeInvokeBuilder('''Foo() - ..id = '123' - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - final theStore = FooStore(); - final theActions = FooActions(); - main() { - ${maybeInvokeBuilder('''Foo()${expectedTodo('theStore')} - ..store = null - ..actions = theActions - ..id = '123' - ''')} - } - '''), - ); - }); - - test('store only', () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theStore' isn't used.")), - expectedPatchCount: 2, - input: withFluxComponentUsage(''' - final theStore = FooStore(); - main() { - ${maybeInvokeBuilder('''Foo() - ..id = '123' - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - final theStore = FooStore(); - main() { - ${maybeInvokeBuilder('''Foo()${expectedTodo('theStore')} - ..store = null - ..actions = null - ..id = '123' - ''')} - } - '''), - ); - }); - - test('actions only', () async { - await testSuggestor( - isExpectedError: (err) => err.message - .contains(RegExp(r"'theActions' isn't used.")), - expectedPatchCount: 2, - input: withFluxComponentUsage(''' - final theActions = FooActions(); - main() { - ${maybeInvokeBuilder('''Foo() - ..id = '123' - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - final theActions = FooActions(); - main() { - ${maybeInvokeBuilder('''Foo() - ..store = null - ..actions = theActions - ..id = '123' - ''')} - } - '''), - ); - }); - }); - }); - - group('in block function scope', () { - test('unless the type(s) do not match (uses null instead):', - () async { - await testSuggestor( - isExpectedError: (err) => err.message - .contains(RegExp(r"'(theStore|theActions)' isn't used.")), - expectedPatchCount: 2, - input: withFluxComponentUsage(''' - main() { - final theStore = BazFooStore(); - final theActions = BazFooActions(); - - ${maybeInvokeBuilder('''Foo() - ..id = '123' - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main() { - final theStore = BazFooStore(); - final theActions = BazFooActions(); - - ${maybeInvokeBuilder('''Foo() - ..store = null - ..actions = null - ..id = '123' - ''')} - } - '''), - ); - }); - - group('and the type(s) match:', () { - test('store AND actions', () async { - await testSuggestor( - isExpectedError: (err) => err.message.contains( - RegExp(r"'(theStore|theActions)' isn't used.")), - expectedPatchCount: 2, - input: withFluxComponentUsage(''' - main() { - final theStore = FooStore(); - final theActions = FooActions(); - - ${maybeInvokeBuilder('''Foo() - ..id = '123' - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main() { - final theStore = FooStore(); - final theActions = FooActions(); - - ${maybeInvokeBuilder('''Foo()${expectedTodo('theStore')} - ..store = null - ..actions = theActions - ..id = '123' - ''')} - } - '''), - ); - }); - - test('store only', () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains(RegExp(r"'theStore' isn't used.")), - expectedPatchCount: 2, - input: withFluxComponentUsage(''' - main() { - final theStore = FooStore(); - - ${maybeInvokeBuilder('''Foo() - ..id = '123' - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main() { - final theStore = FooStore(); - - ${maybeInvokeBuilder('''Foo()${expectedTodo('theStore')} - ..store = null - ..actions = null - ..id = '123' - ''')} - } - '''), - ); - }); - - test('actions only', () async { - await testSuggestor( - isExpectedError: (err) => err.message - .contains(RegExp(r"'theActions' isn't used.")), - expectedPatchCount: 2, - input: withFluxComponentUsage(''' - main() { - final theActions = FooActions(); - - ${maybeInvokeBuilder('''Foo() - ..id = '123' - ''')} - } - '''), - expectedOutput: withFluxComponentUsage(''' - main() { - final theActions = FooActions(); - - ${maybeInvokeBuilder('''Foo() - ..store = null - ..actions = theActions - ..id = '123' - ''')} - } - '''), - ); - }); - }); - }); - - group('in function component props', () { - test('unless the types do not match (uses null instead):', - () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains('someFunction'), - expectedPatchCount: 2, - input: withFluxComponentUsage(''' - class FooConsumerProps = UiProps with FluxUiPropsMixin; - final FooConsumer = uiFunction( - (localProps) { - someFunction() { - return ${maybeInvokeBuilder('''Foo() - ..id = '123' - ''')} - } - - return null; - }, - _\$FooConsumerConfig, // ignore: undefined_identifier - ); - '''), - expectedOutput: withFluxComponentUsage(''' - class FooConsumerProps = UiProps with FluxUiPropsMixin; - final FooConsumer = uiFunction( - (localProps) { - someFunction() { - return ${maybeInvokeBuilder('''Foo() - ..store = null - ..actions = null - ..id = '123' - ''')} - } - - return null; - }, - _\$FooConsumerConfig, // ignore: undefined_identifier - ); - '''), - ); - }); - - test('and the types match:', () async { - await testSuggestor( - isExpectedError: (err) => - err.message.contains('someFunction'), - expectedPatchCount: 2, - input: withFluxComponentUsage(''' - class FooConsumerProps = UiProps with FluxUiPropsMixin; - final FooConsumer = uiFunction( - (localProps) { - someFunction() { - return ${maybeInvokeBuilder('''Foo() - ..id = '123' - ''')} - } - - return null; - }, - _\$FooConsumerConfig, // ignore: undefined_identifier - ); - '''), - expectedOutput: withFluxComponentUsage(''' - class FooConsumerProps = UiProps with FluxUiPropsMixin; - final FooConsumer = uiFunction( - (localProps) { - someFunction() { - return ${maybeInvokeBuilder('''Foo()${expectedTodo('localProps.store')} - ..store = null - ..actions = localProps.actions - ..id = '123' - ''')} - } - - return null; - }, - _\$FooConsumerConfig, // ignore: undefined_identifier - ); - '''), - ); - }); - }); - - group('in class component props', () { - test('unless the types do not match (uses null instead):', - () async { - await testSuggestor( - expectedPatchCount: 2, - input: withFluxComponentUsage(''' - // ignore: undefined_identifier - UiFactory FooConsumer = castUiFactory(_\$FooConsumer); - class FooConsumerProps = UiProps with FluxUiPropsMixin; - class FooConsumerComponent extends FluxUiComponent2 { - someMethod() { - return ${maybeInvokeBuilder('''Foo() - ..id = '123' - ''')} - } - - @override - render() => null; - } - '''), - expectedOutput: withFluxComponentUsage(''' - // ignore: undefined_identifier - UiFactory FooConsumer = castUiFactory(_\$FooConsumer); - class FooConsumerProps = UiProps with FluxUiPropsMixin; - class FooConsumerComponent extends FluxUiComponent2 { - someMethod() { - return ${maybeInvokeBuilder('''Foo() - ..store = null - ..actions = null - ..id = '123' - ''')} - } - - @override - render() => null; - } - '''), - ); - }); - - test('and the types match:', () async { - await testSuggestor( - expectedPatchCount: 2, - input: withFluxComponentUsage(''' - // ignore: undefined_identifier - UiFactory FooConsumer = castUiFactory(_\$FooConsumer); - class FooConsumerProps = UiProps with FluxUiPropsMixin; - class FooConsumerComponent extends FluxUiComponent2 { - someMethod() { - return ${maybeInvokeBuilder('''Foo() - ..id = '123' - ''')} - } - - @override - render() => null; - } - '''), - expectedOutput: withFluxComponentUsage(''' - // ignore: undefined_identifier - UiFactory FooConsumer = castUiFactory(_\$FooConsumer); - class FooConsumerProps = UiProps with FluxUiPropsMixin; - class FooConsumerComponent extends FluxUiComponent2 { - someMethod() { - return ${maybeInvokeBuilder('''Foo()${expectedTodo('props.store')} - ..store = null - ..actions = props.actions - ..id = '123' - ''')} - } - - @override - render() => null; - } - '''), - ); - }); - }); - }); - }); - }); - } - - sharedTests(invokeBuilder: false); - sharedTests(invokeBuilder: true); - }); -} - -String withFluxComponentUsage(String source, - {String? actionsName = 'FooActions', String? storeName = 'FooStore'}) { - String getActionsClasses() => actionsName == null - ? '' - : ''' -class $actionsName { - $actionsName(); -} - -class Baz$actionsName { - Baz$actionsName(); -} -'''; - - String getStoreClasses() => storeName == null - ? '' - : ''' -class $storeName { - $storeName(); -} - -class Baz$storeName { - Baz$storeName(); -} -'''; - - return withOverReactImport('''$source - -${getActionsClasses()} - -${getStoreClasses()} - -UiFactory Foo = castUiFactory(_\$Foo); // ignore: undefined_identifier - -class FooProps = UiProps with FluxUiPropsMixin<${actionsName ?? 'Null'}, ${storeName ?? 'Null'}>; - -class FooComponent extends FluxUiComponent2 { - @override - render() => null; -} - -UiFactory DynamicFoo = castUiFactory(_\$DynamicFoo); // ignore: undefined_identifier - -class DynamicFooProps = UiProps with FluxUiPropsMixin; - -class DynamicFooComponent extends FluxUiComponent2 { - @override - render() => null; -} - -UiFactory NotFoo = castUiFactory(_\$NotFoo); // ignore: undefined_identifier - -mixin NotFooPropsMixin on UiProps { - Baz$storeName store; - Baz$actionsName actions; -} - -class NotFooProps = UiProps with NotFooPropsMixin; - -class NotFooComponent extends UiComponent2 { - @override - render() => null; -}'''); -} - -String withMockPanelTitleComponents(String source) { - return withOverReactImport('''$source - -UiFactory<_PanelTitleProps> _PanelTitle = castUiFactory(_\$_PanelTitle); // ignore: undefined_identifier -class _PanelTitleProps = UiProps with FluxUiPropsMixin; -class LegacyPanelTitleComponent extends FluxUiComponent2<_PanelTitleProps> { - @override - render() => null; -} - -UiFactory PanelTitle = castUiFactory(_\$PanelTitle); // ignore: undefined_identifier -class PanelTitleProps = UiProps with FluxUiPropsMixin; -class PanelTitleComponent extends FluxUiComponent2 { - @override - render() => null; -} - -UiFactory PanelTitleV2 = castUiFactory(_\$PanelTitleV2); // ignore: undefined_identifier -class PanelTitleV2Props = UiProps with FluxUiPropsMixin; -class PanelTitleV2Component extends FluxUiComponent2 { - @override - render() => null; -} - '''); -} - -String withMockPanelToolbarComponents(String source) { - return withOverReactImport('''$source -UiFactory<_PanelToolbarProps> _PanelToolbar = castUiFactory(_\$_PanelToolbar); // ignore: undefined_identifier -class _PanelToolbarProps = UiProps with FluxUiPropsMixin; -class LegacyPanelToolbarComponent extends FluxUiComponent2<_PanelToolbarProps> { - @override - render() => null; -} - -UiFactory PanelToolbar = castUiFactory(_\$PanelToolbar); // ignore: undefined_identifier -class PanelToolbarProps = UiProps with FluxUiPropsMixin; -class PanelToolbarComponent extends FluxUiComponent2 { - @override - render() => null; -} - '''); -} diff --git a/test/dart3_suggestors/null_safety_prep/use_ref_init_migration_test.dart b/test/dart3_suggestors/null_safety_prep/use_ref_init_migration_test.dart deleted file mode 100644 index b4fa8552..00000000 --- a/test/dart3_suggestors/null_safety_prep/use_ref_init_migration_test.dart +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2024 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/dart3_suggestors/null_safety_prep/use_ref_init_migration.dart'; -import 'package:test/test.dart'; - -import '../../resolved_file_context.dart'; -import '../../util.dart'; -import '../../util/component_usage_migrator_test.dart'; - -void main() { - final resolvedContext = SharedAnalysisContext.overReact; - - // 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('UseRefInitMigration', () { - late SuggestorTester testSuggestor; - - setUp(() { - testSuggestor = getSuggestorTester( - UseRefInitMigration(), - resolvedContext: resolvedContext, - ); - }); - - test( - 'leaves useRef function invocations alone when the argument list is empty', - () async { - await testSuggestor( - expectedPatchCount: 0, - input: withOverReactImport(''' - final Foo = uiFunction( - (props) { - final foo = useRef(); - print(foo); - return null; - }, - UiFactoryConfig(displayName: 'Foo'), - ); - '''), - ); - }); - - test('replaces useRef usages with useRefInit when an argument is passed', - () async { - await testSuggestor( - expectedPatchCount: 1, - input: withOverReactImport(''' - final Foo = uiFunction( - (props) { - final foo = useRef('bar'); - return (Dom.div()..id = foo.current)(); - }, - UiFactoryConfig(displayName: 'Foo'), - ); - '''), - expectedOutput: withOverReactImport(''' - final Foo = uiFunction( - (props) { - final foo = useRefInit('bar'); - return (Dom.div()..id = foo.current)(); - }, - UiFactoryConfig(displayName: 'Foo'), - ); - '''), - ); - }); - - test( - 'replaces useRef usages with useRefInit when an argument is passed', - () async { - await testSuggestor( - expectedPatchCount: 1, - input: withOverReactImport(''' - final Foo = uiFunction( - (props) { - final foo = useRef('bar'); - return (Dom.div()..id = foo.current)(); - }, - UiFactoryConfig(displayName: 'Foo'), - ); - '''), - expectedOutput: withOverReactImport(''' - final Foo = uiFunction( - (props) { - final foo = useRefInit('bar'); - return (Dom.div()..id = foo.current)(); - }, - UiFactoryConfig(displayName: 'Foo'), - ); - '''), - ); - }); - - test('removes unnecessary null arguments', () async { - await testSuggestor( - expectedPatchCount: 2, - input: withOverReactImport(''' - useTestHook() { - final foo = useRef(null); - final bar = useRef(null); - return [foo, bar]; - } - '''), - expectedOutput: withOverReactImport(''' - useTestHook() { - final foo = useRef(); - final bar = useRef(); - return [foo, bar]; - } - '''), - ); - }); - }); -} diff --git a/tool/dart_dev/config.dart b/tool/dart_dev/config.dart index 885feb00..02ab0c31 100644 --- a/tool/dart_dev/config.dart +++ b/tool/dart_dev/config.dart @@ -6,5 +6,5 @@ final config = { ..exclude = [ Glob('test/test_fixtures/**'), ], - 'analyze': AnalyzeTool()..analyzerArgs = ['--no-fatal-warnings'], + 'analyze': AnalyzeTool(), };