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

Support generating DSL RBI by path #968

Merged
merged 2 commits into from
Feb 17, 2023
Merged

Conversation

jeffcarbs
Copy link
Contributor

@jeffcarbs jeffcarbs commented Jun 3, 2022

Motivation

We often want to generate RBI for every model, worker, etc inside a namespace. Currently the only way to do that involves crawling ObjectSpace in a console or doing grep/sed magic to get the set of constants. We wanted an easier way to do this.

Another future-facing use-case is being able to run tapioca dsl --verify some/specific/path.

  • Sidenote: At the moment, --verify fails unless it's called with no arguments since it thinks there's stale RBI, but that's something that can be fixed separately.

Implementation

I thought it would be most ergonomic to be able to pass directory/filenames as args to the tapioca dsl but currently it's assumed that any non-flag args passed will be class names. Luckily, ruby class names and file names follow disjoint patterns:

  • class names must start with a capital letter or colon
  • path names typically (99.9% of path names I've ever seen) start with a lower case letter or slash.

Given that, we use that heuristic to split the passed args into either class names or paths and then treat them appropriately downstream. We then use Tapioca::Static::SymbolLoader.symbols_from_paths to get all the class names from the given paths.

Wanted to call out a slight difference in the error handling for paths. tapioca dsl SomeClass SomeOtherClass currently fails if none of those classes have processable DSL. This makes sense since you're specifically giving classes for which you want RBI generated. For paths, on the other hand you may not know if there's RBI to generate and I think that OK so we actually exist successfully if you've given paths but there's no constants to process. The one exception is if you've given use no paths that actually exist, in which case we'll exit with an error which should prevent typos like tapioca dsl app/mode.

Tests

See tests

Copy link
Collaborator

@Morriar Morriar left a comment

Choose a reason for hiding this comment

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

I like where this is going ❤️

Comment on lines 233 to 251
sig { params(paths: T::Array[String]).returns(T::Array[String]) }
def contants_from_paths(paths)
output = T.unsafe(self).sorbet("--no-config", "--quiet", "--print=symbol-table-json", *paths).out
constants_from_symbol_table_json(JSON.parse(output))
end

sig { params(symbol_table_json: T::Hash[String, T.untyped], prefix: T.nilable(String)).returns(T::Array[String]) }
def constants_from_symbol_table_json(symbol_table_json, prefix = nil)
kind = symbol_table_json.fetch("kind")
name = symbol_table_json.fetch("name")
return [] unless kind == "CLASS_OR_MODULE" && name.fetch("kind") == "CONSTANT"

name = name.fetch("name")
children = symbol_table_json.fetch("children", [])

prefixed_name = name == "<root>" ? prefix : [prefix, name].compact.join("::")
[prefixed_name, *children.flat_map { |c| constants_from_symbol_table_json(c, prefixed_name) }].compact
end

Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call. I actually read through that code a bit more and realized it was going exactly what I was trying to do so we can just switch to using it directly. Will push up a commit shortly.

spec/tapioca/cli/dsl_spec.rb Show resolved Hide resolved
@Morriar Morriar mentioned this pull request Jun 13, 2022
@jeffcarbs jeffcarbs requested a review from a team as a code owner July 14, 2022 18:33
Copy link
Collaborator

@Morriar Morriar left a comment

Choose a reason for hiding this comment

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

There are a few conflicts with main and #968 (comment) hasn't been addressed.

Copy link
Member

@paracycle paracycle left a comment

Choose a reason for hiding this comment

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

I am still not super convinced about passing a mix of constants/paths on the command line, but I think I would like us to see how that works in the wild first.

I have a few comments on top of the earlier ones from @Morriar, otherwise, this looks good to me, thank you.

@@ -35,6 +35,11 @@ def gem_symbols(gem)
symbols_from_paths(gem.files)
end

sig { params(paths: T::Array[String]).returns(T::Set[String]) }
def dsl_symbols(paths)
Copy link
Member

Choose a reason for hiding this comment

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

Sorry about the backwards/forwards here but can we expose the symbols_from_paths as a public method instead of adding a dsl_symbols method. I have plans to use the same functionality in another context, so I think being able to get a list of symbols that correspond to a list of files is good functionality.

lib/tapioca/commands/dsl.rb Show resolved Hide resolved
spec/tapioca/cli/dsl_spec.rb Show resolved Hide resolved
@jeffcarbs
Copy link
Contributor Author

@paracycle @Morriar - I'm picking this PR back up. I rebased from the latest main branch and updated some things based on the conversation above.

Regarding whether to exit with an error/success, I think I wound up a nice middle-ground:

  • if you gave us paths that actually exist and there just happen to be no processable constants, that will exit successfully
    • This is necessary to support running this as a pre-commit hook which would just pass the paths to any files that have changed. It's possible/likely that the files given just happen to not have any constants to process and that shouldn't be an error.
  • if you didn't give us at least one valid path and there are no processable constants, that will exit with error.
    • This should hopefully be enough to catch typos where you gave us a non-existent path (e.g. a typo).

@paracycle paracycle merged commit c29e582 into Shopify:main Feb 17, 2023
@shopify-shipit shopify-shipit bot temporarily deployed to production February 21, 2023 12:01 Inactive
@jeffcarbs jeffcarbs deleted the dsl-by-path branch February 23, 2023 18:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants