Skip to content

Commit 6cf5552

Browse files
Merge pull request #297 from Workiva/public-defaulted-props
FED-2994 Update class component defaults codemod to take public-ness into account
2 parents 8fef280 + 99d10cb commit 6cf5552

File tree

8 files changed

+127
-5
lines changed

8 files changed

+127
-5
lines changed

lib/src/dart3_suggestors/null_safety_prep/class_component_required_default_props.dart

+7-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import 'package:analyzer/dart/ast/ast.dart';
2121
import 'package:over_react_codemod/src/vendor/over_react_analyzer_plugin/get_all_props.dart';
2222
import 'package:pub_semver/pub_semver.dart';
2323

24+
import '../required_props/codemod/recommender.dart';
2425
import 'utils/class_component_required_fields.dart';
2526

2627
/// Suggestor to assist with preparations for null-safety by adding
@@ -76,7 +77,10 @@ import 'utils/class_component_required_fields.dart';
7677
/// ```
7778
class ClassComponentRequiredDefaultPropsMigrator
7879
extends ClassComponentRequiredFieldsMigrator<PropAssignment> {
79-
ClassComponentRequiredDefaultPropsMigrator([Version? sdkVersion])
80+
final PropRequirednessRecommender? _propRequirednessRecommender;
81+
82+
ClassComponentRequiredDefaultPropsMigrator(
83+
[Version? sdkVersion, this._propRequirednessRecommender])
8084
: super('defaultProps', 'getDefaultProps', sdkVersion);
8185

8286
@override
@@ -105,6 +109,7 @@ class ClassComponentRequiredDefaultPropsMigrator
105109
.map((assignment) => PropAssignment(assignment))
106110
.where((prop) => prop.node.writeElement?.displayName != null);
107111

108-
patchFieldDeclarations(getAllProps, cascadedDefaultProps, node);
112+
patchFieldDeclarations(
113+
getAllProps, cascadedDefaultProps, node, _propRequirednessRecommender);
109114
}
110115
}

lib/src/dart3_suggestors/null_safety_prep/utils/class_component_required_fields.dart

+11-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import 'package:analyzer/dart/ast/visitor.dart';
2525
import 'package:pub_semver/pub_semver.dart';
2626

2727
import '../../../util/class_suggestor.dart';
28+
import '../../required_props/codemod/recommender.dart';
2829
import '../analyzer_plugin_utils.dart';
2930

3031
/// A class shared by the suggestors that manage defaultProps/initialState.
@@ -52,7 +53,8 @@ abstract class ClassComponentRequiredFieldsMigrator<
5253
void patchFieldDeclarations(
5354
Iterable<FieldElement> Function(InterfaceElement) getAll,
5455
Iterable<Assignment> cascadedDefaultPropsOrInitialState,
55-
CascadeExpression node) {
56+
CascadeExpression node,
57+
[PropRequirednessRecommender? _propRequirednessRecommender]) {
5658
for (final field in cascadedDefaultPropsOrInitialState) {
5759
final isDefaultedToNull =
5860
field.node.rightHandSide.staticType!.isDartCoreNull;
@@ -66,6 +68,14 @@ abstract class ClassComponentRequiredFieldsMigrator<
6668
// The field declaration is likely in another file which our logic currently doesn't handle.
6769
// In this case, don't add an entry to `fieldData`.
6870
if (fieldDeclaration == null) continue;
71+
final element = fieldDeclaration.declaredElement;
72+
73+
// Don't set as required if the prop is publicly exported.
74+
if (_propRequirednessRecommender != null && element is FieldElement) {
75+
final isPublic = _propRequirednessRecommender
76+
.isPropsPublicForMixingIn(element.enclosingElement);
77+
if (isPublic) continue;
78+
}
6979

7080
fieldData.add(DefaultedOrInitializedDeclaration(
7181
fieldDeclaration, fieldEl, isDefaultedToNull));

lib/src/dart3_suggestors/required_props/bin/codemod.dart

+11
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import 'package:over_react_codemod/src/util/args.dart';
2424
import 'package:over_react_codemod/src/util/command_runner.dart';
2525
import 'package:over_react_codemod/src/util/package_util.dart';
2626

27+
import '../../null_safety_prep/class_component_required_default_props.dart';
2728
import '../codemod/recommender.dart';
2829
import '../collect/aggregated_data.sg.dart';
2930

@@ -148,6 +149,16 @@ class CodemodCommand extends Command {
148149
parsedArgs.argValueAsNumber(_Options.publicMaxAllowedSkipRate),
149150
);
150151

152+
exitCode = await runInteractiveCodemodSequence(
153+
dartPaths,
154+
[
155+
ClassComponentRequiredDefaultPropsMigrator(null, recommender),
156+
],
157+
defaultYes: true,
158+
args: codemodArgs,
159+
additionalHelpOutput: argParser.usage,
160+
);
161+
151162
exitCode = await runInteractiveCodemodSequence(
152163
dartPaths,
153164
[

lib/src/dart3_suggestors/required_props/codemod/recommender.dart

+3
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ class PropRequirednessRecommender {
7878
?[propsId];
7979
}
8080

81+
bool isPropsPublicForMixingIn(Element propsElement) =>
82+
_getMixinResult(propsElement)?.visibility.isPublicForMixingIn ?? false;
83+
8184
SkipRateOptionalReason? _getMixinSkipRateReason(MixinResult mixinResults) {
8285
final skipRate = mixinResults.usageSkipRate;
8386

lib/src/executables/null_safety_migrator_companion.dart

-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import 'dart:io';
1616

1717
import 'package:args/args.dart';
1818
import 'package:codemod/codemod.dart';
19-
import 'package:over_react_codemod/src/dart3_suggestors/null_safety_prep/class_component_required_default_props.dart';
2019
import 'package:over_react_codemod/src/dart3_suggestors/null_safety_prep/class_component_required_initial_state.dart';
2120
import 'package:over_react_codemod/src/util.dart';
2221

@@ -43,7 +42,6 @@ void main(List<String> args) async {
4342
dartPaths,
4443
aggregate([
4544
CallbackRefHintSuggestor(),
46-
ClassComponentRequiredDefaultPropsMigrator(),
4745
ClassComponentRequiredInitialStateMigrator(),
4846
]),
4947
defaultYes: true,

test/executables/required_props_collect_and_codemod_test.dart

+18
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,24 @@ mixin TestPrivateProps on UiProps {
8686
String/*?*/ set20percent;
8787
$noDataTodoComment
8888
String/*?*/ set0percent;
89+
}''')),
90+
d.file('test_class_component_defaults.dart', contains('''
91+
mixin TestPrivatePropsMixin on UiProps {
92+
String/*?*/ notDefaultedOptional;
93+
/*late*/ String notDefaultedAlwaysSet;
94+
/*late*/ String/*?*/ defaultedNullable;
95+
/*late*/ num/*!*/ defaultedNonNullable;
96+
}
97+
98+
mixin SomeOtherPropsMixin on UiProps {
99+
/*late*/ num/*!*/ anotherDefaultedNonNullable;
100+
}''')),
101+
d.file('test_class_component_defaults.dart', contains('''
102+
mixin TestPublic2PropsMixin on UiProps {
103+
String/*?*/ notDefaultedOptional;
104+
/*late*/ String notDefaultedAlwaysSet;
105+
String/*?*/ defaultedNullable;
106+
num/*?*/ defaultedNonNullable;
89107
}''')),
90108
d.file('test_private_dynamic.dart', contains('''
91109
// TODO(orcm.required_props): This codemod couldn't reliably determine requiredness for these props

test/test_fixtures/required_props/test_package/lib/entrypoint.dart

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export 'src/test_class_component_defaults.dart' show TestPublic2, TestPublic2PropsMixin;
12
export 'src/test_public.dart';
23
export 'src/test_public_dynamic.dart';
34
export 'src/test_public_multiple_components.dart';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import 'package:over_react/over_react.dart';
2+
3+
part 'test_class_component_defaults.over_react.g.dart';
4+
5+
mixin TestPrivatePropsMixin on UiProps {
6+
String notDefaultedOptional;
7+
String notDefaultedAlwaysSet;
8+
String defaultedNullable;
9+
num defaultedNonNullable;
10+
}
11+
12+
mixin SomeOtherPropsMixin on UiProps {
13+
num anotherDefaultedNonNullable;
14+
}
15+
16+
class TestPrivateProps = UiProps
17+
with TestPrivatePropsMixin, SomeOtherPropsMixin;
18+
19+
UiFactory<TestPrivateProps> TestPrivate =
20+
castUiFactory(_$TestPrivate); // ignore: undefined_identifier
21+
22+
class TestPrivateComponent extends UiComponent2<TestPrivateProps> {
23+
@override
24+
get defaultProps => (newProps()
25+
..defaultedNullable = null
26+
..defaultedNonNullable = 2.1
27+
..anotherDefaultedNonNullable = 1.1
28+
);
29+
30+
@override
31+
render() {}
32+
}
33+
34+
mixin TestPublic2PropsMixin on UiProps {
35+
String notDefaultedOptional;
36+
String notDefaultedAlwaysSet;
37+
String defaultedNullable;
38+
num defaultedNonNullable;
39+
}
40+
41+
class TestPublic2Props = UiProps
42+
with TestPublic2PropsMixin, SomeOtherPropsMixin;
43+
44+
UiFactory<TestPublic2Props> TestPublic2 =
45+
castUiFactory(_$TestPublic2); // ignore: undefined_identifier
46+
47+
class TestPublic2Component extends UiComponent2<TestPublic2Props> {
48+
@override
49+
get defaultProps => (newProps()
50+
..defaultedNullable = null
51+
..defaultedNonNullable = 2.1
52+
..anotherDefaultedNonNullable = 1.1
53+
);
54+
55+
@override
56+
render() {}
57+
}
58+
59+
usages() {
60+
(TestPrivate()..notDefaultedAlwaysSet = 'abc')();
61+
(TestPrivate()
62+
..notDefaultedOptional = 'abc'
63+
..notDefaultedAlwaysSet = 'abc'
64+
..defaultedNullable = 'abc'
65+
..defaultedNonNullable = 1
66+
..anotherDefaultedNonNullable = 2
67+
)();
68+
(TestPublic2()..notDefaultedAlwaysSet = 'abc')();
69+
(TestPublic2()
70+
..notDefaultedAlwaysSet = 'abc'
71+
..notDefaultedOptional = 'abc'
72+
..defaultedNullable = 'abc'
73+
..defaultedNonNullable = 1
74+
..anotherDefaultedNonNullable = 2
75+
)();
76+
}

0 commit comments

Comments
 (0)