Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CPLAT-9308 Codemod Utility to tell if Something is Public API #74

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
53d0862
Create SemverHelper class
sydneyjodon-wk Jan 30, 2020
4900548
Create utility function
sydneyjodon-wk Jan 30, 2020
070c5f9
Format
sydneyjodon-wk Jan 30, 2020
723a406
Add SemverHelper constructor
sydneyjodon-wk Feb 4, 2020
cadf6ae
Add doc comment
sydneyjodon-wk Feb 4, 2020
d806385
Add tests
sydneyjodon-wk Feb 4, 2020
c5e7e74
Merge branch 'CPLAT-9205-new-boilerplate-codemod' into CPLAT-9308-get…
sydneyjodon-wk Feb 4, 2020
7027deb
Merge base branch in
sydneyjodon-wk Feb 4, 2020
900a557
Update migrator tests
sydneyjodon-wk Feb 4, 2020
7c1f44e
Remove unused imports
sydneyjodon-wk Feb 4, 2020
457deb6
Merge CPLAT-9205-new-boilerplate-codemod into CPLAT-9308-getPublicExp…
aaronlademann-wf Feb 5, 2020
f9d81d9
Merge remote-tracking branch 'origin/CPLAT-9205-new-boilerplate-codem…
aaronlademann-wf Feb 5, 2020
8abec86
Merge branch 'CPLAT-9205-new-boilerplate-codemod' into CPLAT-9308-get…
sydneyjodon-wk Feb 6, 2020
c61a5c4
Address reviewer feedback
sydneyjodon-wk Feb 6, 2020
d53c197
Remove duplicate import
sydneyjodon-wk Feb 6, 2020
82e39f7
Add doc comment
sydneyjodon-wk Feb 6, 2020
c5430ab
Merge branch 'CPLAT-9205-new-boilerplate-codemod' into CPLAT-9308-get…
sydneyjodon-wk Feb 6, 2020
7d844b0
Fix errors from merge
sydneyjodon-wk Feb 6, 2020
5783dcb
Address reviewer feedback
sydneyjodon-wk Feb 11, 2020
23dc002
Merge branch 'CPLAT-9205-new-boilerplate-codemod' into CPLAT-9308-get…
sydneyjodon-wk Feb 11, 2020
8775714
Address reviewer feedback
sydneyjodon-wk Feb 11, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions lib/src/boilerplate_suggestors/boilerplate_utilities.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,37 @@ import 'package:over_react_codemod/src/util.dart';
typedef YieldPatch = void Function(
int startingOffset, int endingOffset, String replacement);

@visibleForTesting
bool isPublicForTest = false;
SemverHelper semverHelper;

// Stub while <https://jira.atl.workiva.net/browse/CPLAT-9308> is in progress
bool isPublic(ClassDeclaration node) => isPublicForTest;
/// Returns whether or not [node] is publicly exported.
bool isPublic(ClassDeclaration node) {
assert(semverHelper != null);
return semverHelper.getPublicExportLocations(node).isNotEmpty;
}

class SemverHelper {
final Map _exportList;

SemverHelper(Map jsonReport)
: _exportList = jsonReport['exports'],
assert(jsonReport['exports'] != null);

/// Returns a list of locations where [node] is publicly exported.
///
/// If [node] is not publicly exported, returns an empty list.
List<String> getPublicExportLocations(ClassDeclaration node) {
final className = node.name.name;
final List<String> locations = List();

_exportList.forEach((key, value) {
if (value['type'] == 'class' && value['grammar']['name'] == className) {
locations.add(key);
}
});

return locations;
}
}

/// Returns the annotation node associated with the provided [classNode]
/// that matches the provided [annotationName], if one exists.
Expand Down Expand Up @@ -57,7 +83,6 @@ bool shouldMigratePropsAndStateMixin(ClassDeclaration classNode) =>
bool shouldMigratePropsAndStateClass(ClassDeclaration node) {
return isAssociatedWithComponent2(node) &&
isAPropsOrStateClass(node) &&
// Stub while <https://jira.atl.workiva.net/browse/CPLAT-9308> is in progress
!isPublic(node);
}

Expand Down
7 changes: 6 additions & 1 deletion lib/src/executables/boilerplate_upgrade.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:convert';
import 'dart:io';

import 'package:codemod/codemod.dart';
Expand All @@ -32,7 +33,7 @@ const _changesRequiredOutput = '''
Then, review the the changes, address any FIXMEs, and commit.
''';

void main(List<String> args) {
Future<void> main(List<String> args) async {
final query = FileQuery.dir(
pathFilter: (path) {
return isDartFile(path) && !isGeneratedDartFile(path);
Expand All @@ -42,6 +43,10 @@ void main(List<String> args) {

final classToMixinConverter = ClassToMixinConverter();

// TODO: determine file path of semver report
semverHelper = SemverHelper(jsonDecode(
await File('lib/src/boilerplate_suggestors/report.json').readAsString()));

// General plan:
// - Things that need to be accomplished (very simplified)
// 1. Make props / state class a mixin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:convert';

import 'package:over_react_codemod/src/boilerplate_suggestors/advanced_props_and_state_class_migrator.dart';
import 'package:over_react_codemod/src/boilerplate_suggestors/boilerplate_utilities.dart';
import 'package:test/test.dart';

import '../util.dart';
import 'boilerplate_utilities_test.dart';

void main() {
group('AdvancedPropsAndStateClassMigrator', () {
final converter = ClassToMixinConverter();
final testSuggestor =
getSuggestorTester(AdvancedPropsAndStateClassMigrator(converter));

setUpAll(() {
semverHelper = SemverHelper(jsonDecode(reportJson));
});

tearDown(() {
converter.setConvertedClassNames({});
});
Expand Down Expand Up @@ -65,6 +72,43 @@ void main() {

expect(converter.convertedClassNames, isEmpty);
});

test('advanced classes are public API', () {
testSuggestor(
expectedPatchCount: 0,
input: r'''
@Factory()
UiFactory<BarProps> Bar =
// ignore: undefined_identifier
$Bar;

@Props()
class BarProps extends ADifferentPropsClass {
String foo;
int bar;
}

@State()
class BarState extends ADifferentStateClass {
String foo;
int bar;
}

@Component2()
class BarComponent extends UiStatefulComponent2<BarProps, BarState> {
@override
render() {
return Dom.ul()(
Dom.li()('Foo: ', props.foo),
Dom.li()('Bar: ', props.bar),
);
}
}
''',
);

expect(converter.convertedClassNames, isEmpty);
});
});

group('operates when the classes are advanced', () {
Expand Down
94 changes: 94 additions & 0 deletions test/boilerplate_suggestors/boilerplate_utilities_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,60 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:convert';

import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:over_react_codemod/src/boilerplate_suggestors/boilerplate_utilities.dart';
import 'package:test/test.dart';

const reportJson = r'''{
"exports": {
"lib/web_skin_dart.dart/ButtonProps": {
"type": "class",
"grammar": {
"name": "ButtonProps",
"meta": ["@Props()"]
}
},
"lib/web_skin_dart.dart/BarProps": {
"type": "class",
"grammar": {
"name": "BarProps",
"meta": ["@Props()"]
}
},
"lib/web_skin_dart.dart/BarState": {
"type": "class",
"grammar": {
"name": "BarState",
"meta": ["@State()"]
}
},
"lib/web_skin_dart.dart/BarPropsMixin": {
"type": "class",
"grammar": {
"name": "BarPropsMixin",
"meta": ["@Props()"]
}
},
"lib/web_skin_dart.dart/BarStateMixin": {
"type": "class",
"grammar": {
"name": "BarStateMixin",
"meta": ["@State()"]
}
},
"lib/another_file.dart/ButtonProps": {
"type": "class",
"grammar": {
"name": "ButtonProps",
"meta": ["@Props()"]
}
}
}
}''';

void main() {
group('Boilerplate Utilities', () {
group('isPropsUsageSimple', () {
Expand Down Expand Up @@ -267,5 +316,50 @@ void main() {
});
});
});

group('isPublic() and getPublicExportLocations()', () {
setUpAll(() {
semverHelper = SemverHelper(jsonDecode(reportJson));
});

test('if props class is not in export list', () {
final input = '''
@Props()
class _\$FooProps extends UiProps{
String foo;
int bar;
}
''';

CompilationUnit unit = parseString(content: input).unit;
expect(unit.declarations.whereType<ClassDeclaration>().length, 1);

unit.declarations.whereType<ClassDeclaration>().forEach((classNode) {
expect(semverHelper.getPublicExportLocations(classNode), isEmpty);
expect(isPublic(classNode), false);
});
});

test('if props class is in export list', () {
final input = '''
@Props()
class ButtonProps extends UiProps{
String foo;
int bar;
}
''';

CompilationUnit unit = parseString(content: input).unit;
expect(unit.declarations.whereType<ClassDeclaration>().length, 1);

unit.declarations.whereType<ClassDeclaration>().forEach((classNode) {
expect(semverHelper.getPublicExportLocations(classNode), [
'lib/web_skin_dart.dart/ButtonProps',
'lib/another_file.dart/ButtonProps'
]);
expect(isPublic(classNode), true);
});
});
});
});
}
22 changes: 12 additions & 10 deletions test/boilerplate_suggestors/props_mixin_migrator_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:convert';

import 'package:over_react_codemod/src/boilerplate_suggestors/boilerplate_utilities.dart';
import 'package:over_react_codemod/src/boilerplate_suggestors/props_mixins_migrator.dart';
import 'package:test/test.dart';

import '../util.dart';
import 'boilerplate_utilities_test.dart';

main() {
group('PropsMixinMigrator', () {
final converter = ClassToMixinConverter();
final testSuggestor = getSuggestorTester(PropsMixinMigrator(converter));

setUpAll(() {
semverHelper = SemverHelper(jsonDecode(reportJson));
});

tearDown(() {
converter.setConvertedClassNames({});
});
Expand Down Expand Up @@ -297,33 +304,28 @@ main() {
});

test('is deprecated if the class is part of the public API', () {
addTearDown(() {
isPublicForTest = false;
});
isPublicForTest = true;

testSuggestor(
expectedPatchCount: 5,
input: '''
/// Some doc comment
@${typeStr}Mixin()
abstract class Foo${typeStr}Mixin implements Ui${typeStr} {
abstract class Bar${typeStr}Mixin implements Ui${typeStr} {
// To ensure the codemod regression checking works properly, please keep this
// field at the top of the class!
// ignore: undefined_identifier, undefined_class, const_initialized_with_non_constant_value
static const ${typeStr}Meta meta = _\$metaForFoo${typeStr}Mixin;
static const ${typeStr}Meta meta = _\$metaForBar${typeStr}Mixin;
String foo;
}
''',
expectedOutput: '''
/// Some doc comment
mixin Foo${typeStr}Mixin on Ui${typeStr} {
mixin Bar${typeStr}Mixin on Ui${typeStr} {
// To ensure the codemod regression checking works properly, please keep this
// field at the top of the class!
// ignore: undefined_identifier, undefined_class, const_initialized_with_non_constant_value
@Deprecated('Use `propsMeta.forMixin(Foo${typeStr}Mixin)` instead.')
static const ${typeStr}Meta meta = _\$metaForFoo${typeStr}Mixin;
@Deprecated('Use `propsMeta.forMixin(Bar${typeStr}Mixin)` instead.')
static const ${typeStr}Meta meta = _\$metaForBar${typeStr}Mixin;
String foo;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:convert';

import 'package:over_react_codemod/src/boilerplate_suggestors/boilerplate_utilities.dart';
import 'package:over_react_codemod/src/boilerplate_suggestors/simple_props_and_state_class_migrator.dart';
import 'package:test/test.dart';

import '../util.dart';
import 'boilerplate_utilities_test.dart';

main() {
group('SimplePropsAndStateClassMigrator', () {
final converter = ClassToMixinConverter();
final testSuggestor =
getSuggestorTester(SimplePropsAndStateClassMigrator(converter));

setUpAll(() {
semverHelper = SemverHelper(jsonDecode(reportJson));
});

tearDown(() {
converter.setConvertedClassNames({});
});
Expand Down Expand Up @@ -116,7 +123,36 @@ main() {
expect(converter.convertedClassNames, isEmpty);
});

// TODO add a test for when the class is publicly exported
test('when the props class is publicly exported', () {
testSuggestor(
expectedPatchCount: 0,
input: '''
@Factory()
UiFactory<BarProps> Bar =
// ignore: undefined_identifier
\$Bar;
@Props()
class BarProps extends UiProps {
String foo;
int bar;
}
@Component2()
class BarComponent extends UiComponent2<BarProps> {
@override
render() {
return Dom.ul()(
Dom.li()('Foo: ', props.foo),
Dom.li()('Bar: ', props.bar),
);
}
}
''',
);

expect(converter.convertedClassNames, isEmpty);
});

group('the classes are not simple', () {
test('and there are both a props and a state class', () {
Expand Down
Loading