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

Conversation

sydneyjodon-wk
Copy link
Contributor

@sydneyjodon-wk sydneyjodon-wk commented Jan 30, 2020

Motivation

To support CPLAT-9205, we need a way to tell whether something is a public API.

  • Don't worry about generating the semver report in this ticket. We can either run semver_audit or download the appropriate report in the repo_commander script. And then the codemod can assume that it exists at a "report.json" file or something

  • Use a local semver_audit report to tell whether a component class is part of the public API. What the report looks like:

    {
        "exports": {
           "lib/web_skin_dart.dart/ButtonProps": {
              "type": "class",
              "grammar": {
                  "name": "ButtonProps",
                  "meta": ["@Props()"]
              }
           }
        }
    }
  • Add a function (likely encapsulated in a class, or whatever makes sense):

    List<String> getPublicExportLocations(CompilationUnitDeclaration node) { ... }
    • Return a List instead of a bool so that so it can output helpful messages saying why a component wasn't codemodded, and also in case the codemod wants to perform conditional logic based on the name of exports.
  • Make it possible to call this function repeatedly re-parsing the semver report (which can be upwards of 10MB for some repos)

    • E.g.,
      var helper = SemverHelper.fromReport(reportJson);
      ...
      var suggestors = [
        Suggestor1(semverHelper: helper),
        Suggestor2(semverHelper: helper),
      ];

Changes

  • Write SemverHelper class with getPublicExportLocations utility.
  • Write tests.
  • Create simple report.json mock to help with testing codemod suggestors in other tickets.
  • Add tests to existing migrators.

Release Notes

Review

See CONTRIBUTING.md for more details on review types (+1 / QA +1 / +10) and code review process.

Please review: @aaronlademann-wf @joebingham-wk @greglittlefield-wf

QA Checklist

  • Tests were updated and provide good coverage of the changeset and other affected code
  • Manual testing was performed if needed

Merge Checklist

While we perform many automated checks before auto-merging, some manual checks are needed:

  • A Client Platform member has reviewed these changes
  • There are no unaddressed comments - this check can be automated if reviewers use the "Request Changes" feature
  • For release PRs - Version metadata in Rosie comment is correct

@aviary3-wk
Copy link

Security Insights

No security relevant content was detected by automated scans.

Action Items

  • Review PR for security impact; comment "security review required" if needed or unsure
  • Verify aviary.yaml coverage of security relevant code

Questions or Comments? Reach out on Slack: #support-infosec.

Copy link
Contributor

@greglittlefield-wf greglittlefield-wf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote some comments without realizing that this wasn't ready for review yet 🤦‍♂ Sorry about that!

I figured I'd post the comments anyways, but take them with a grain of salt!

lib/src/boilerplate_suggestors/boilerplate_utilities.dart Outdated Show resolved Hide resolved
lib/src/boilerplate_suggestors/boilerplate_utilities.dart Outdated Show resolved Hide resolved
setUpAll(() async {
final helper = SemverHelper();
await helper.fromReport(
'/Users/sydneyjodon/Documents/GitHub/over_react_codemod/lib/src/boilerplate_suggestors/test.json');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a version of the report tracked in Git somehow, as opposed to this hard-coded API.

To make testing easier, perhaps having a constructor that takes in the map directly would be helpful. That way, you can embed the JSON in the test, and also choose how to load the report in actual usage.

SemverHelper.fromReport(this.exportList);
// in the test for SemverHelper
final helper = SemverHelper({'exports': ...});
// in usage:
final helper = SemverHelper(jsonDecode(await File('report.json').readAsString()));

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, a test for a Suggestor would be a case where we'd want to use the mock for SemverHelper as opposed to a real instance, to make test setup easier (by not having to set up the whole exports map).

lib/src/boilerplate_suggestors/boilerplate_utilities.dart Outdated Show resolved Hide resolved
@sydneyjodon-wk sydneyjodon-wk changed the base branch from master to CPLAT-9205-new-boilerplate-codemod February 4, 2020 18:12
@sydneyjodon-wk sydneyjodon-wk changed the base branch from CPLAT-9205-new-boilerplate-codemod to master February 4, 2020 21:01
…PublicExportLocations-utility-function

# Conflicts:
#	lib/src/boilerplate_suggestors/annotations_remover.dart
#	lib/src/boilerplate_suggestors/boilerplate_utilities.dart
#	lib/src/executables/boilerplate_upgrade.dart
#	test/boilerplate_suggestors/boilerplate_utilities_test.dart
@sydneyjodon-wk sydneyjodon-wk changed the base branch from master to CPLAT-9205-new-boilerplate-codemod February 4, 2020 21:24
@sydneyjodon-wk sydneyjodon-wk marked this pull request as ready for review February 4, 2020 23:57
Copy link
Contributor

@joebingham-wk joebingham-wk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a couple early thoughts! :)

@override
visitClassDeclaration(ClassDeclaration node) {
super.visitClassDeclaration(node);

if (!shouldMigrateAdvancedPropsAndStateClass(node)) return;
if (!shouldMigrateAdvancedPropsAndStateClass(node, helper)) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This suggestion builds on code that isn't here yet (it would come from Aaron's branches), but how would you feel about integrating the helper into an isPublic function? I think the implementation could be something like:

boilerplate_utilities.dart

// Create a helper that the utilities can see
SemverHelper semverHelper;

bool isPublic(ClassDeclaration node) {
  assert(semverHelper != null);
  return semverHelper.getPublicExportLocations(node) != null;
}

bool shouldMigratePropsAndStateClass(
    ClassDeclaration node) {
  return isAssociatedWithComponent2(node) &&
      isAPropsOrStateClass(node) && !isPublic(node);

boilerplate_upgrade.dart

semverHelper = SemverHelper(jsonDecode(
      await File('lib/src/boilerplate_suggestors/report.json').readAsString()));

The main thing I'm trying to prevent is passing around the same helper instance through all the suggestors and utilities, but if that seems like a good goal then I'm open to whatever implementation works best!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main thing I'm trying to prevent is passing around the same helper instance through all the suggestors and utilities, but if that seems like a good goal then I'm open to whatever implementation works best!

Passing around the instance is actually what I'd prefer, as opposed to a global variable.

Advantages:

  • Eliminates the risk of different areas of the code unintentionally interfering with each other's data (including in tests)
    -Make it easier to reason about where this is used, and force us to be intentional about where we pass it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could definitely see that, and normally I wouldn't advocate for a global variable, but my main thought is that the function which answers the question of whether a node is public or not is at the utility level, and it doesn't seem like the suggestor should need to be concerned with how it answers that question (outside of passing in the node).

Also, because this helper should receive the report for the relevant library it's running on, it seems like it makes sense for it to operate independently since any instantiation for an executable should be passing it the same report. For tests that does break down, but then the testing environment could have its own instance when needed. If you feel strongly, Greg, I think that generally global variables are bad, this is just a weird case to me 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel pretty strongly here that moving away from a top-level variable would improve the modularity of this code, as well as the predictability of how data is passed around and used.

it seems like it makes sense for it to operate independently since any instantiation for an executable should be passing it the same report

That's true, but may not always be true. For example, what if we wanted this utility to behave slightly differently for different suggestors? Or if we wanted to run multiple sets of suggestors on different directories concurrently? Having global variables would make it much more difficult to implement these changes. To be fair, this is the weakest argument of the ones I've presented, but it is something to keep in mind.


I'm happy to make a PR with those changes, especially since some work was already done to move it more toward top-level variables.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving away from a global variable sounds good to me. Thanks for explaining your logic, Greg! And sorry for making ya change it in the first place Sydney 😅

final className = node?.name?.name;
String classKey;

if (className == null || _exportList == null) return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like we may want this to be a louder failure. Have we seen the class name be null before? I'd be curious to see what that code looks like haha

If these can be null though, I'd expect it to throw an Exception since we need the list and class name to really verify if the class is exported, and returning null could be a false negative

lib/src/boilerplate_suggestors/boilerplate_utilities.dart Outdated Show resolved Hide resolved
aaronlademann-wf and others added 6 commits February 5, 2020 12:33
…ortLocations-utility-function

# Conflicts:
#	test/boilerplate_suggestors/simple_props_and_state_class_migrator_test.dart
…od' into CPLAT-9205-new-boilerplate-codemod/CPLAT-9308-getPublicExportLocations-utility-function
…PublicExportLocations-utility-function

# Conflicts:
#	lib/src/boilerplate_suggestors/boilerplate_utilities.dart
…PublicExportLocations-utility-function

# Conflicts:
#	lib/src/executables/boilerplate_upgrade.dart
…PublicExportLocations-utility-function

# Conflicts:
#	test/boilerplate_suggestors/boilerplate_utilities_test.dart
Copy link
Contributor

@greglittlefield-wf greglittlefield-wf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+10, assuming that #74 (comment) is addressed in your other PR #80

@greglittlefield-wf greglittlefield-wf merged commit 2d47b96 into CPLAT-9205-new-boilerplate-codemod Feb 17, 2020
@greglittlefield-wf greglittlefield-wf deleted the CPLAT-9308-getPublicExportLocations-utility-function branch February 17, 2020 14:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants