From 7af94a9171dc52b84a783a8d90f1fda296972798 Mon Sep 17 00:00:00 2001 From: Jack Firth Date: Sun, 9 Mar 2025 04:43:45 -0700 Subject: [PATCH] Add `format-string-to-format-symbol` rule --- .../syntax-shortcuts-test.rkt | 25 +++++++ default-recommendations/syntax-shortcuts.rkt | 65 ++++++++++++++++++- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/default-recommendations/syntax-shortcuts-test.rkt b/default-recommendations/syntax-shortcuts-test.rkt index 577cad69..548b1824 100644 --- a/default-recommendations/syntax-shortcuts-test.rkt +++ b/default-recommendations/syntax-shortcuts-test.rkt @@ -28,3 +28,28 @@ test: "syntax-e on a single format-id argument is removable" test: "format-id call without any syntax-e unwrapped arguments not refactorable" - (format-id #'foo "~a.~a.~a" #'bar #'baz #'blah) + + +test: "making a symbol with format can be simplified to format-symbol" +- (string->symbol (format "make-~a" "foo")) +- (format-symbol "make-~a" "foo") + + +test: "making a symbol with format from a symbol can be simplified to format-symbol" +- (string->symbol (format "make-~a" (symbol->string 'foo))) +- (format-symbol "make-~a" 'foo) + + +test: "making a symbol with format from an identifier can be simplified to format-symbol" +- (string->symbol (format "make-~a" (symbol->string (syntax-e #'foo)))) +- (format-symbol "make-~a" #'foo) + + +test: "making a symbol with format from a keyword can be simplified to format-symbol" +- (string->symbol (format "make-~a" (keyword->string '#:foo))) +- (format-symbol "make-~a" '#:foo) + + +test: "making a symbol with format from a keyword syntax object can be simplified to format-symbol" +- (string->symbol (format "make-~a" (keyword->string (syntax-e #'#:foo)))) +- (format-symbol "make-~a" #'#:foo) diff --git a/default-recommendations/syntax-shortcuts.rkt b/default-recommendations/syntax-shortcuts.rkt index 5c10d942..2ff8ce1e 100644 --- a/default-recommendations/syntax-shortcuts.rkt +++ b/default-recommendations/syntax-shortcuts.rkt @@ -9,7 +9,8 @@ [syntax-shortcuts refactoring-suite?])) -(require racket/syntax +(require racket/string + racket/syntax rebellion/private/static-name resyntax/base syntax/parse) @@ -43,5 +44,65 @@ (format-id lctx fmt arg.simplified ...)) +(define-syntax-class format-symbol-argument + #:attributes (simplified) + #:literals (syntax-e keyword->string symbol->string) + + (pattern (syntax-e inner:format-symbol-argument) #:attr simplified (attribute inner.simplified)) + + (pattern (keyword->string inner:format-symbol-argument) + #:attr simplified (attribute inner.simplified)) + + (pattern (symbol->string inner:format-symbol-argument) + #:attr simplified (attribute inner.simplified)) + + (pattern simplified:expr)) + + +;; The format-symbol function only allows ~a placeholders. Rather a fancy generic utilty that finds +;; all placeholders, we just explicitly list out all the other ones and check one-by-one whether any +;; of them are contained in the template string. That's easier to implement and the performance +;; doesn't matter at all since template strings are almost always short. +(define disallowed-format-symbol-placeholders + (list "~n" + "~%" + "~s" + "~S" + "~v" + "~V" + "~.a" + "~.A" + "~.s" + "~.S" + "~.v" + "~.V" + "~e" + "~E" + "~c" + "~C" + "~b" + "~B" + "~o" + "~O" + "~x" + "~X" + "~ " + "~\n" + "~\t")) + + +(define-refactoring-rule format-string-to-format-symbol + #:description + "This `format` expression can be simplified to an equivalent `format-symbol` expression." + #:literals (format string->symbol) + + (string->symbol (format template:str arg:format-symbol-argument ...)) + #:when (for/and ([disallowed (in-list disallowed-format-symbol-placeholders)]) + (not (string-contains? (syntax-e #'template) disallowed))) + + (format-symbol template (~replacement arg.simplified #:original arg) ...)) + + (define-refactoring-suite syntax-shortcuts - #:rules (syntax-e-in-format-id-unnecessary)) + #:rules (format-string-to-format-symbol + syntax-e-in-format-id-unnecessary))