Skip to content

Commit

Permalink
Merge pull request #58 from Workiva/CPLAT-6563-migrate-consumer-overlays
Browse files Browse the repository at this point in the history
CPLAT-6563 Codemod to migrate consumer overlays to new APIs, fixing deprecations
  • Loading branch information
rmconsole4-wk authored Oct 1, 2019
2 parents f037f7d + e70b098 commit bef425f
Show file tree
Hide file tree
Showing 6 changed files with 359 additions and 1 deletion.
15 changes: 15 additions & 0 deletions bin/react16_consumer_overlay_update.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2019 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/react16_consumer_overlay_update.dart';
1 change: 0 additions & 1 deletion lib/src/component2_suggestors/component2_utilities.dart
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ bool fullyUpgradableToComponent2(ClassDeclaration classNode) {
/// * Generic parameters on component class
/// * `@AbstractProps` in the same file
bool canBeExtendedFrom(ClassDeclaration classNode) {
var a = classNode.typeParameters;
if (classNode != null &&
(classNode.abstractKeyword != null ||
classNode.typeParameters != null ||
Expand Down
43 changes: 43 additions & 0 deletions lib/src/executables/react16_consumer_overlay_update.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2019 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:codemod/codemod.dart';
import 'package:over_react_codemod/src/react16_suggestors/consumer_overlay_migrator.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:react16_consumer_overlay_update
pub run dart_dev format (If you format this repository).
Then, review the the changes, address any FIXMEs, and commit.
""";

void main(List<String> args) {
final query = FileQuery.dir(
pathFilter: isDartFile,
recursive: true,
);

exitCode = runInteractiveCodemodSequence(
query,
[
ConsumerOverlayMigrator(),
],
args: args,
defaultYes: true,
changesRequiredOutput: _changesRequiredOutput,
);
}
63 changes: 63 additions & 0 deletions lib/src/react16_suggestors/consumer_overlay_migrator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2019 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:over_react_codemod/src/component2_suggestors/component2_utilities.dart';

import '../constants.dart';

/// Suggestor that migrates consumer overlay prop names in component usages.
class ConsumerOverlayMigrator extends GeneralizingAstVisitor
with AstVisitingSuggestorMixin
implements Suggestor {
@override
visitCascadeExpression(CascadeExpression node) {
super.visitCascadeExpression(node);

final containingClass = node.thisOrAncestorOfType<ClassDeclaration>();

var extendsName = containingClass?.extendsClause?.superclass?.name;
if (extendsName == null) {
return;
}

String reactImportName =
getImportNamespace(containingClass, 'package:react/react.dart');

// Check if usage is in a component class.
if (extendsName.name != '$reactImportName.Component' &&
extendsName.name != '$reactImportName.Component2' &&
!containingClass.metadata.any((m) =>
overReact16ComponentAnnotationNamesToMigrate
.contains(m.name.name) ||
overReact16Component2AnnotationNames.contains(m.name.name))) {
return;
}

// Update consumer overlay props.
for (AssignmentExpression expression
in node.cascadeSections.whereType<AssignmentExpression>()) {
final leftHandSide = expression.leftHandSide;

if (leftHandSide.toSource() == '..overlay' ||
leftHandSide.toSource() == '..isOverlay') {
yieldPatch(leftHandSide.end, leftHandSide.end, '2');
} else if (leftHandSide.toSource() == '..useLegacyPositioning') {
yieldPatch(expression.offset, expression.end, '');
}
}
}
}
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,5 @@ executables:
react16_upgrade:
react16_dependency_override_update:
react16_ci_precheck:
react16_consumer_overlay_update:
component2_upgrade:
237 changes: 237 additions & 0 deletions test/react16_suggestors/consumer_overlay_migrator_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
// Copyright 2019 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/react16_suggestors/consumer_overlay_migrator.dart';
import 'package:test/test.dart';

import '../util.dart';

main() {
group('ConsumerOverlayMigrator', () {
final testSuggestor = getSuggestorTester(ConsumerOverlayMigrator());

test('empty file', () {
testSuggestor(expectedPatchCount: 0, input: '');
});

test('no matches', () {
testSuggestor(
expectedPatchCount: 0,
input: '''
library foo;
var a = 'b';
class Foo {}
''',
);
});

test('`overlay` prop', () {
testSuggestor(
expectedPatchCount: 1,
input: '''
@Component()
class FooComponent extends UiComponent<FooProps> {
@override
render() {
return (OverlayTrigger()
..overlay = _renderIndicator()
)(_renderMainContent());
}
}
''',
expectedOutput: '''
@Component()
class FooComponent extends UiComponent<FooProps> {
@override
render() {
return (OverlayTrigger()
..overlay2 = _renderIndicator()
)(_renderMainContent());
}
}
''',
);
});

test('`isOverlay` prop', () {
testSuggestor(
expectedPatchCount: 1,
input: '''
import 'package:react/react.dart' as react;
class FooComponent extends react.Component<FooProps> {
@override
Map getDefaultProps() => (newProps()
..isFontSizeControl = false
..isOverlay = false
..initiallyOpen = false
);
}
''',
expectedOutput: '''
import 'package:react/react.dart' as react;
class FooComponent extends react.Component<FooProps> {
@override
Map getDefaultProps() => (newProps()
..isFontSizeControl = false
..isOverlay2 = false
..initiallyOpen = false
);
}
''',
);
});

test('`useLegacyPositioning` prop', () {
testSuggestor(
expectedPatchCount: 1,
input: '''
import 'package:react/react.dart' as react;
class FooComponent extends react.Component2<FooProps> {
@override
render() {
return (OverlayTrigger()
..addProps(props.overlayTriggerProps)
..useLegacyPositioning = false
..ref = ((ref) => overlayTriggerRef = ref)
)(_renderMainContent());
}
}
''',
expectedOutput: '''
import 'package:react/react.dart' as react;
class FooComponent extends react.Component2<FooProps> {
@override
render() {
return (OverlayTrigger()
..addProps(props.overlayTriggerProps)
..ref = ((ref) => overlayTriggerRef = ref)
)(_renderMainContent());
}
}
''',
);
});

test('`overlay`, `isOverlay`, and `useLegacyPositioning` props', () {
testSuggestor(
expectedPatchCount: 3,
input: '''
@Component2()
class FooComponent extends SomeOtherClass<FooProps> {
@override
Map getDefaultProps() => (newProps()
..isFontSizeControl = false
..isOverlay = false
..initiallyOpen = false
);
@override
render() {
return (OverlayTrigger()
..addProps(props.overlayTriggerProps)
..trigger = OverlayTriggerType.MANUAL
..useLegacyPositioning = false
..overlay = _renderIndicator()
..repositionOverlay = repositionOverlayOverTrigger
..ref = ((ref) => overlayTriggerRef = ref)
)(_renderMainContent());
}
}
''',
expectedOutput: '''
@Component2()
class FooComponent extends SomeOtherClass<FooProps> {
@override
Map getDefaultProps() => (newProps()
..isFontSizeControl = false
..isOverlay2 = false
..initiallyOpen = false
);
@override
render() {
return (OverlayTrigger()
..addProps(props.overlayTriggerProps)
..trigger = OverlayTriggerType.MANUAL
..overlay2 = _renderIndicator()
..repositionOverlay = repositionOverlayOverTrigger
..ref = ((ref) => overlayTriggerRef = ref)
)(_renderMainContent());
}
}
''',
);
});

test('already updated props', () {
testSuggestor(
expectedPatchCount: 0,
input: '''
@Component2()
class FooComponent extends UiComponent2<FooProps> {
@override
Map getDefaultProps() => (newProps()
..isFontSizeControl = false
..isOverlay2 = false
..initiallyOpen = false
);
@override
render() {
return (OverlayTrigger()
..addProps(props.overlayTriggerProps)
..trigger = OverlayTriggerType.MANUAL
..overlay2 = _renderIndicator()
..repositionOverlay = repositionOverlayOverTrigger
..ref = ((ref) => overlayTriggerRef = ref)
)(_renderMainContent());
}
}
''',
);
});

test('not in component class', () {
testSuggestor(
expectedPatchCount: 0,
input: '''
class FooClass extends SomeOtherClass {
@override
Map getDefaultProps() => (newProps()
..isFontSizeControl = false
..isOverlay = false
..initiallyOpen = false
);
@override
render() {
return (OverlayTrigger()
..addProps(props.overlayTriggerProps)
..trigger = OverlayTriggerType.MANUAL
..useLegacyPositioning = false
..overlay = _renderIndicator()
..repositionOverlay = repositionOverlayOverTrigger
..ref = ((ref) => overlayTriggerRef = ref)
)(_renderMainContent());
}
}
''',
);
});
});
}

0 comments on commit bef425f

Please sign in to comment.