Skip to content

Commit

Permalink
Merge pull request #273 from Workiva/batch/fedx/FED-2094_useRefInit_m…
Browse files Browse the repository at this point in the history
…igration

FED-2094 Add suggestor that replaces useRef with useRefInit
  • Loading branch information
rmconsole3-wf authored Mar 6, 2024
2 parents 9912a6f + 9031bd7 commit f7bccf0
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 0 deletions.
15 changes: 15 additions & 0 deletions bin/null_safety_prep.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_prep.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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';

/// 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 ref = useRef(someNonNulLValue);
/// // After
/// final ref = useRefInit(someNonNulLValue);
/// ```
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') {
yieldPatch('useRefInit', possibleInvocation.function.offset,
possibleInvocation.function.end);
}
}
}
}
45 changes: 45 additions & 0 deletions lib/src/executables/null_safety_prep.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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/ignoreable.dart';
import 'package:over_react_codemod/src/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:null_safety_prep
""";

void main(List<String> args) async {
final parser = ArgParser.allowAnything();

final parsedArgs = parser.parse(args);
final dartPaths = allDartPathsExceptHiddenAndGenerated();

exitCode = await runInteractiveCodemod(
dartPaths,
aggregate([
UseRefInitMigration(),
].map((s) => ignoreable(s))),
defaultYes: true,
args: parsedArgs.rest,
additionalHelpOutput: parser.usage,
changesRequiredOutput: _changesRequiredOutput,
);
}
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ dev_dependencies:
executables:
dart2_9_upgrade:
dependency_validator_ignore:
null_safety_prep:
mui_migration:
required_flux_props:
rmui_preparation:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// 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<UiProps>(
(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<UiProps>(
(props) {
final foo = useRef('bar');
return (Dom.div()..id = foo.current)();
},
UiFactoryConfig(displayName: 'Foo'),
);
'''),
expectedOutput: withOverReactImport('''
final Foo = uiFunction<UiProps>(
(props) {
final foo = useRefInit('bar');
return (Dom.div()..id = foo.current)();
},
UiFactoryConfig(displayName: 'Foo'),
);
'''),
);
});

test(
'replaces useRef<Generic> usages with useRefInit<Generic> when an argument is passed',
() async {
await testSuggestor(
expectedPatchCount: 1,
input: withOverReactImport('''
final Foo = uiFunction<UiProps>(
(props) {
final foo = useRef<String>('bar');
return (Dom.div()..id = foo.current)();
},
UiFactoryConfig(displayName: 'Foo'),
);
'''),
expectedOutput: withOverReactImport('''
final Foo = uiFunction<UiProps>(
(props) {
final foo = useRefInit<String>('bar');
return (Dom.div()..id = foo.current)();
},
UiFactoryConfig(displayName: 'Foo'),
);
'''),
);
});
});
}

0 comments on commit f7bccf0

Please sign in to comment.