6

What are the best Ruby automatic mass refactoring tools out there?

For instance, I want to be able to replace double quoted strings that don't have any special content (#{}, \n, ') with single quoted one on an entire project (because in the early days it had no standard, and now Hound CI keeps barking whenever I touch old code...)

Please consider the above functionality just as an example, and submit your suggestion even if it does not do the quote refactoring, but does other similar types or refactoring.

  • Does it have to be Ruby specific, or would a regex command for plain text files work? – Tymric Sep 26 '14 at 13:58
  • 2
    Ruby specific ;) It is too hard / impossible to come up with regexes that take care of all edge cases. – Ciro Santilli 郝海东冠状病六四事件法轮功 Sep 26 '14 at 14:22
0

OP notes: Ruby specific: It is too hard / impossible to come up with regexes that take care of all edge cases.

It isn't Ruby-specific. Regexes don't cut it (even PCRE) when processing real programming languages. Refactoring tools for virtually any language must fully parse that language accurately in order to enable accurate refactorings. (Nobody wants inaccurate refactorings).

The DMS Software Reengineering Toolkit is designed to do this for many languages. DMS lets one define languages (usually quite a bit work in its own right, usually because the languages are complicated), and then write source-to-source transformation rules that can carry out automated changes to parsed source code of that language.

The desired changes are expressed as transformation rules that look like string transforms because they are expressed as surface text, but are actually interpreted as tree patterns and operate on the underlying ASTs resulting from DMS source code parses. This ensures accurate matches and revisions. Often such rules improve precision by refering to langauge concepts such as named terminals and nonterminals as defined by the DMS grammar for that language.

DMS has a strong draft Ruby parser in place for DMS, undergoing polishing. (Ruby is a tough language to parse especially if you want to parse the regexes it allows; DMS's Ruby parser does this).

OP's request would require the following DMS rewrite rule:

 squotify_dull_dquoted_strings(s:STRING): STRING -> STRING
      s -> Squotify(s) if ContainsOnlySquotedContent(s1);

This rule takes advantage of DMS's representation of "simple" Ruby strings as a terminal item containing the string content, and a hint about the preferred quoting style. (Interpolated strings are not represented as simple strings). DMS's Ruby prettyprinter, when printing such a simple string, will print it with the hinted quote iff the string content is valid for that quote.

So, a squoted string 'abc' will get printed as 'abc'. A simple string containing a control character but a squote hint, can't get printed legally that way, and the DMS prettyprinter will then print it as a dquoted string with an appropriate escape for the control character. This approach lets a tool manufacture a "simple" string with arbitrary characters without paying much attention to quoting conventions, and such simple strings will just get printed in a usable form. This kind of approach makes it easier to write complex transforms.

So what the transformation does is match simple STRINGs, and replace it by a simple string with a squote hint. The prettyprinter does the rest. Just to avoid doing this at all if inappropriate I've added an additional predicate to check that the string is appropriately squoatble. The programmer has to provide this additional predicate as a DMS Parlanse function. He can take advantage of the fact that the prettyprinter already contains this predicate.

More complex dquoted strings containing various fragments or interpolations aren't captured as simple strings, and thus can't be matched/modified by this rule.

But because complex strings and regexes are captured in detail, you can do rewrites on them. Consider a rule to simplify silly interpolated strings:

   remove_interpolation_with_string_constant(sf: STRING_FRAGMENT, s:STRING)
           :STRING_body -> STRING_body
      " \sf #{ \s } " ->  make_STRING_FRAGMENT(concatenate(sf,s));

The dquote character here is a DMS meta-quote, not a Ruby quote, and is used to distinguish DMS Rule-rewrite syntax from the underlying concrete syntax of the target language. Read the link reference carefully for more details on this.

This takes silly Ruby interpolated strings of the form of:

     "abc#{ "def" }"

(where abc is a STRING_FRAGMENT) and replaces them by:

     "abcdef"

Adding another rule:

    normal_single_fragment_string(sf: STRING_FRAGMENT): literal -> literal
    " \" \sf \" " -> make_STRING(\sf);

will convert an interpolated string containing one a single string fragment as its content, into a simple string, at which our first rule might apply. (The \" represents the Ruby dquote character surrounding the fragment).

DMS rewrite rules can express any local tree transform fairly easily.
For example, one can of course write rules on higher level syntax constructs:

     cleanup_if_trivial_then_nontrivial_else(e: EXPR, c1:COMPOUND_STMT)
         :PRIMARY->PRIMARY
      " if \e then
        else
          \c1
        end"
      ->
      " if !\e then
          \c1
        end
      "

More complex forms require sets of co-operating rules. What DMS does not offer for Ruby yet is the ability to interpret the scopes of names so one has to be careful writing transforms that move identifiers in and out of scopes.

It is important to understand DMS is not an interactive tool. You write the transforms, then it applies those transforms to source code as a batch run. This is designed for massive changes.

Since this is my company's product, I'll make no claim this is the "recommended" Ruby refactoring tool, just that it exists and satisifies OP's request.

| improve this answer | |
0

For the simple string quoting changes, I would use RuboCop, which is a style checker/linter foremost, but also provides automatic corrections for many of the issues it finds. In fact, Hound CI uses RuboCop under the hood!

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.