@@ -230,6 +230,107 @@ refactoring rules.
230230 (void)))}
231231
232232
233+ @subsection{Exercising Fine Control Over Comments}
234+
235+
236+ Writing a rule with @racket[define-refactoring-rule] is usually enough for Resyntax to handle
237+ commented code without issue, but in certain cases more precise control is desired. For instance,
238+ consider the @racketidfont{nested-or-to-flat-or} rule from earlier:
239+
240+ @(racketblock
241+ (define-refactoring-rule nested-or-to-flat-or
242+ #:description "This nested `or` expression can be flattened. "
243+ #:literals (or )
244+ (or a (or b c))
245+ (or a b c)))
246+
247+ As-is, this rule will @emph{fail} to refactor the following code:
248+
249+ @(racketblock
250+ (or (foo ... )
251+ (code:comment @#,elem{If that doesn't work, fall back to other approaches})
252+ (or (bar ... )
253+ (baz ... ))))
254+
255+ Resyntax rejects the rule because applying it would produce this code, which loses the comment:
256+
257+ @(racketblock
258+ (or (foo ... )
259+ (bar ... )
260+ (baz ... )))
261+
262+ Resyntax is unable to preserve the comment automatically. Resyntax can preserve some comments without
263+ programmer effort, but only in specific circumstances:
264+
265+ @itemlist[
266+ @item{Comments @emph{within} expressions that the rule left unchanged are preserved. If the comment
267+ were inside @racket[(foo ... )], @racket[(bar ... )], or @racket[(baz ... )], it would have been kept.}
268+
269+ @item{Comments @emph{between} unchanged expressions are similarly preserved. If the comment were
270+ between @racket[(bar ... )] and @racket[(baz ... )], it would have been kept.}]
271+
272+ To fix this issue, rule authors can inject some extra markup into their suggested replacements using
273+ @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl " )]{template metafunctions} provided by Resyntax. In
274+ the case of @racketidfont{nested-or-to-flat-or}, we can use the @racket[~splicing-replacement]
275+ metafunction to indicate that the nested @racket[or ] expression should be considered @emph{replaced}
276+ by its nested subterms:
277+
278+ @(racketblock
279+ (define-refactoring-rule nested-or-to-flat-or
280+ #:description "This nested `or` expression can be flattened. "
281+ #:literals (or )
282+ (or a (~and nested-or (or b c)))
283+ #:with (nested-subterm ... ) #'(~splicing-replacement (b c) #:original nested-or)
284+ (or a nested-subterm ... )))
285+
286+ This adds @tech[#:doc '(lib "scribblings/reference/reference.scrbl " )]{syntax properties} to the nested
287+ subterms that allow Resyntax to preserve the comment, producing this output:
288+
289+ @(racketblock
290+ (or (foo ... )
291+ (code:comment @#,elem{If that doesn't work, fall back to other approaches})
292+ (bar ... )
293+ (baz ... )))
294+
295+ When Resyntax sees that the @racket[(bar ... )] nested subterm comes immediately after the
296+ @racket[(foo ... )] subterm, it notices that @racket[(bar ... )] has been annotated with replacement
297+ properties. Then Resyntax observes that @racket[(bar ... )] is the first expression of a sequence of
298+ expressions that replaces the @racket[or ] expression which originally followed @racket[(foo ... )].
299+ Based on this observation, Resyntax decides to preserve whatever text was originally between
300+ @racket[(foo ... )] and the nested @racket[or ] expression. This mechanism, exposed via
301+ @racket[~replacement] and @racket[~splicing-replacement], offers a means for refactoring rules to
302+ guide Resyntax's internal comment preservation system when the default behavior is not sufficient.
303+
304+ @defform[#:kind "template metafunction "
305+ (~replacement replacement-form original)
306+ #:grammar
307+ ([original
308+ (code:line #:original original-form)
309+ (code:line #:original-splice (original-form ... ))])]{
310+ A @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl " )]{template metafunction} for use in
311+ @tech{refactoring rules}. The result of the metafunction is just the @racket[#'replacement-form ]
312+ syntax object, except with some
313+ @tech[#:doc '(lib "scribblings/reference/reference.scrbl " )]{syntax properties} added. Those
314+ properties inform Resyntax that this syntax object should be considered a replacement for
315+ @racket[original-form] (or in the splicing case , for the unparenthesized sequence
316+ @racket[original-form ... ]). Resyntax uses this information to preserve comments and formatting near
317+ the original form(s).}
318+
319+ @defform[#:kind "template metafunction "
320+ (~splicing-replacement (replacement-form ... ) original)
321+ #:grammar
322+ ([original
323+ (code:line #:original original-form)
324+ (code:line #:original-splice (original-form ... ))])]{
325+ A @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl " )]{template metafunction} for use in
326+ @tech{refactoring rules}. The result of the metafunction is the syntax object
327+ @racket[#'(replacement-form ... )], except with some
328+ @tech[#:doc '(lib "scribblings/reference/reference.scrbl " )]{syntax properties} added. Those
329+ properties inform Resyntax that the replacement syntax objects --- as an unparenthesized sequence ---
330+ should be considered a replacement for @racket[original-form] (or @racket[original-form ... ]).
331+ Resyntax uses this information to preserve comments and formatting near the original form(s).}
332+
333+
233334@subsection{Resyntax's Default Rules}
234335@defmodule[resyntax/default-recommendations]
235336
0 commit comments