Skip to content

Commit

Permalink
Merge branch 'master' of github.com:Workiva/over_react_codemod into I…
Browse files Browse the repository at this point in the history
…NTL-1793
  • Loading branch information
bender-wk committed Apr 25, 2024
2 parents 3c6aa7a + 3d427d5 commit e626446
Show file tree
Hide file tree
Showing 23 changed files with 2,256 additions and 91 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/dart_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,9 @@ jobs:
echo 'Running dart pub get in test fixtures beforehand to prevent concurrent `dart pub get`s in tests from failing'
(cd test/test_fixtures/over_react_project && dart pub get)
dart test --exclude-tags=wsd
- name: Create SBOM Release Asset
uses: anchore/sbom-action@v0
with:
path: ./
format: cyclonedx-json
31 changes: 0 additions & 31 deletions Dockerfile

This file was deleted.

15 changes: 15 additions & 0 deletions bin/null_safety_migrator_companion.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// 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';
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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';
Expand Down Expand Up @@ -74,8 +75,8 @@ class CallbackRefHintSuggestor extends RecursiveAstVisitor<void>
final param = rhs.parameters?.parameters.first;
if (param is SimpleFormalParameter) {
final type = param.type;
if (type != null && !_hintAlreadyExists(type)) {
yieldPatch(nullabilityHint, type.end, type.end);
if (type != null && !nullableHintAlreadyExists(type)) {
yieldPatch(nullableHint, type.end, type.end);
}
}

Expand Down Expand Up @@ -109,9 +110,9 @@ class CallbackRefHintSuggestor extends RecursiveAstVisitor<void>
.tryCast<VariableDeclarationList>()
?.type;
if (varType != null &&
!_hintAlreadyExists(varType) &&
!nullableHintAlreadyExists(varType) &&
varType.toSource() != 'dynamic') {
yieldPatch(nullabilityHint, varType.end, varType.end);
yieldPatch(nullableHint, varType.end, varType.end);
}
}
}
Expand All @@ -123,9 +124,9 @@ class CallbackRefHintSuggestor extends RecursiveAstVisitor<void>
final refCasts = allDescendantsOfType<AsExpression>(rhs.body).where(
(expression) =>
expression.expression.toSource() == refParamName &&
!_hintAlreadyExists(expression.type));
!nullableHintAlreadyExists(expression.type));
for (final cast in refCasts) {
yieldPatch(nullabilityHint, cast.type.end, cast.type.end);
yieldPatch(nullableHint, cast.type.end, cast.type.end);
}
}
}
Expand All @@ -143,14 +144,3 @@ class CallbackRefHintSuggestor extends RecursiveAstVisitor<void>
result.unit.visitChildren(this);
}
}

/// Whether the nullability hint already exists after [type].
bool _hintAlreadyExists(TypeAnnotation type) {
// The nullability hint will follow the type so we need to check the next token to find the comment if it exists.
return type.endToken.next?.precedingComments
?.value()
.contains(nullabilityHint) ??
false;
}

const nullabilityHint = '/*?*/';
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// 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.dart';
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_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<FooProps> {
/// @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<FooProps> {
/// @override
/// get defaultProps => (newProps()
/// ..defaultedNullable = null
/// ..defaultedNonNullable = 2.1
/// );
///
/// // ...
/// }
/// ```
class ClassComponentRequiredDefaultPropsMigrator
extends ClassComponentRequiredFieldsMigrator<PropAssignment> {
ClassComponentRequiredDefaultPropsMigrator([Version? sdkVersion])
: super('defaultProps', 'getDefaultProps', sdkVersion);

@override
Future<void> visitCascadeExpression(CascadeExpression node) async {
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 = node.cascadeSections
.whereType<AssignmentExpression>()
.where((assignment) => assignment.leftHandSide is PropertyAccess)
.map((assignment) => PropAssignment(assignment))
.where((prop) => prop.node.writeElement?.displayName != null);

patchFieldDeclarations(getAllProps, cascadedDefaultProps, node);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// 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_props.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<FooProps> {
/// @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<FooProps> {
/// @override
/// get initialState => (newState()
/// ..defaultedNullable = null
/// ..defaultedNonNullable = 2.1
/// );
///
/// // ...
/// }
/// ```
class ClassComponentRequiredInitialStateMigrator
extends ClassComponentRequiredFieldsMigrator<StateAssignment> {
ClassComponentRequiredInitialStateMigrator([Version? sdkVersion])
: super('initialState', 'getInitialState', sdkVersion);

@override
Future<void> visitCascadeExpression(CascadeExpression node) async {
super.visitCascadeExpression(node);

final isInitialState = [relevantGetterName, relevantMethodName].contains(
node.thisOrAncestorOfType<MethodDeclaration>()?.declaredElement?.name);

// If this cascade is not assigning values to defaultProps, bail.
if (!isInitialState) return;

final cascadedInitialState = node.cascadeSections
.whereType<AssignmentExpression>()
.where((assignment) => assignment.leftHandSide is PropertyAccess)
.map((assignment) => StateAssignment(assignment))
.where((prop) => prop.node.writeElement?.displayName != null);

patchFieldDeclarations(getAllState, cascadedInitialState, node);
}
}
Loading

0 comments on commit e626446

Please sign in to comment.