Resyntax derives its suggestions from refactoring rules, which can be grouped into a refactoring suite. Resyntax ships with a default refactoring suite consisting of many rules that cover various scenarios related to Racket’s standard libraries. However, you may also define your own refactoring suite and rules using the forms below. Knowledge of Racket macros, and of syntax-parse in particular, is especially useful for understanding how to create effective refactoring rules.
procedure
v:any/c
procedure
v:any/c
syntax
#:descriptiondescriptionparse-option...syntax-patternpattern-directive...template)description : string?
#:description"This nested `or` expression can be flattened."
Like syntax-parse and define-syntax-parse-rule , pattern directives can be used to aid in defining rules. Here is a rule that uses the #:when directive to only refactor or expressions that have a duplicate condition:
syntax
#:descriptiondescriptionparse-option...syntax-patternpattern-directive...template)description : string?
#:description"These definitions can be simplified with `match-define`."(match-define(pointxy)pt)
Note that by default Resyntax will try to reformat the entire context. To reformat just the forms being modified, a few additional steps are required. First, use ~replacement (or ~splicing-replacement ) to annotate which subpart of the context is being replaced:
#:description"These definitions can be simplified with `match-define`."#:original-splice(x-defy-def))
This ensures that Resyntax will preserve any comments at the end of body-before... and the beginning of body-after... . However, that alone doesn’t prevent Resyntax from reformatting the whole context. To do that, use the ~focus-replacement-on metafunction, which tells Resyntax that if only the focused forms are changed, Resyntax should "shrink" the replacement it generates down to just those forms and not reformat anything in the replacement syntax object that’s outside of the focused syntax:
#:description"These definitions can be simplified with `match-define`."#:original-splice(x-defy-def)))
syntax
( define-refactoring-suite idrules-listsuites-list)
rules-list =| #:rules(rule...)suites-list =| #:suites(suite...)rule : refactoring-rule?suite : refactoring-suite?
Writing a rule with define-refactoring-rule is usually enough for Resyntax to handle commented code without issue, but in certain cases more precise control is desired. For instance, consider the nested-or-to-flat-or rule from earlier:
#:description"This nested `or` expression can be flattened."
As-is, this rule will fail to refactor the following code:
;If that doesn’t work, fall back to other approaches
Resyntax rejects the rule because applying it would produce this code, which loses the comment:
Resyntax is unable to preserve the comment automatically. Resyntax can preserve some comments without programmer effort, but only in specific circumstances:
Comments within expressions that the rule left unchanged are preserved. If the comment were inside (foo... ), (bar... ), or (baz... ), it would have been kept.
Comments between unchanged expressions are similarly preserved. If the comment were between (bar... ) and (baz... ), it would have been kept.
To fix this issue, rule authors can inject some extra markup into their suggested replacements using template metafunctions provided by Resyntax. In the case of nested-or-to-flat-or, we can use the ~splicing-replacement metafunction to indicate that the nested or expression should be considered replaced by its nested subterms:
#:description"This nested `or` expression can be flattened."
This adds syntax properties to the nested subterms that allow Resyntax to preserve the comment, producing this output:
;If that doesn’t work, fall back to other approaches
When Resyntax sees that the (bar... ) nested subterm comes immediately after the (foo... ) subterm, it notices that (bar... ) has been annotated with replacement properties. Then Resyntax observes that (bar... ) is the first expression of a sequence of expressions that replaces the or expression which originally followed (foo... ). Based on this observation, Resyntax decides to preserve whatever text was originally between (foo... ) and the nested or expression. This mechanism, exposed via ~replacement and ~splicing-replacement , offers a means for refactoring rules to guide Resyntax’s internal comment preservation system when the default behavior is not sufficient.
template metafunction
( ~replacement replacement-formoriginal)
original = #:originaloriginal-form| #:original-splice(original-form...)
template metafunction
original)original = #:originaloriginal-form| #:original-splice(original-form...)
template metafunction
( ~focus-replacement-on replacement-form)
If you’d like to add a new refactoring rule to Resyntax, there are a few guidelines to keep in mind:
Refactoring rules should be safe. Resyntax shouldn’t break users’ code, and it shouldn’t require careful review to determine whether a suggestion from Resyntax is safe to apply. It’s better for a rule to never make suggestions than to occasionally make broken suggestions.
Refactoring rules can be shown to many different developers in a wide variety of different contexts. Therefore, it’s important that Resyntax’s default recommendations have some degree of consensus among the Racket community. Highly divisive suggestions that many developers disagree with are not a good fit for Resyntax. Technology is social before it is technical: discussing your rule with the Racket community prior to developing it is encouraged, especially if it’s likely to affect a lot of code. If necessary, consider narrowing the focus of your rule to just the cases that everybody agrees are clear improvements.
Refactoring rules should explain themselves. The description of a refactoring rule (as specified with the #:description option) should state why the new code is an improvement over the old code. Refactoring rule descriptions are shown to Resyntax users at the command line, in GitHub pull request review comments, and in Git commit messages. The description is the only means you have of explaining to a potentially confused stranger why Resyntax wants to change their code, so make sure you use it!
Refactoring rules should focus on cleaning up real-world code. A refactoring rule that suggests improvements to hypothetical code that no human would write in the first place is not useful. Try to find examples of code "in the wild" that the rule would improve. The best candidates for new rules tend to be rules that help Racketeers clean up and migrate old Scheme code that doesn’t take advantage of Racket’s unique features and extensive standard library.
Refactoring rules should try to preserve the intended behavior of the refactored code, but not necessarily the actual behavior. For instance, a rule that changes how code handles some edge case is acceptable if the original behavior of the code was likely confusing or surprising to the developer who wrote it. This is a judgment call that requires understanding what the original code communicates clearly and what it doesn’t. A rule’s #:description is an excellent place to draw attention to potentially surprising behavior changes.
Refactoring rules should be self-contained, meaning they can operate locally on a single expression. Refactoring rules that require whole-program analysis are not a good fit for Resyntax, nor are rules that require global knowledge of the whole codebase.