Skip to content

Commit eee0116

Browse files
Copilotjackfirth
andauthored
Allow specifying desired analyzers in refactoring rules (#717)
Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: jackfirth <[email protected]>
1 parent 4fb7893 commit eee0116

File tree

10 files changed

+70
-17
lines changed

10 files changed

+70
-17
lines changed

base.rkt

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,13 @@
139139
(define-refactoring-rule id:id
140140
#:description description
141141
(~optional (~seq #:uses-universal-tagged-syntax? uses-universal-tagged-syntax?))
142+
(~optional (~seq #:analyzers analyzers))
142143
parse-option:syntax-parse-option ...
143144
pattern
144145
pattern-directive:syntax-parse-pattern-directive ...
145146
replacement)
146147
#:declare description (expr/c #'string?)
148+
#:declare analyzers (expr/c #'(sequence/c expansion-analyzer?))
147149

148150
#:attr partial-match-log-statement
149151
(and (not (empty? (attribute pattern-directive)))
@@ -159,9 +161,7 @@
159161
#:name 'id
160162
#:description (string->immutable-string description.c)
161163
#:uses-universal-tagged-syntax? (~? uses-universal-tagged-syntax? #false)
162-
#:analyzers (set identifier-usage-analyzer
163-
ignored-result-values-analyzer
164-
variable-mutability-analyzer)
164+
#:analyzers (for/set ([analyzer (~? analyzers.c '())]) analyzer)
165165
#:transformer
166166
(λ (stx)
167167
(syntax-parse stx
@@ -175,6 +175,7 @@
175175
(define-syntax-parse-rule
176176
(define-definition-context-refactoring-rule id:id
177177
#:description (~var description (expr/c #'string?))
178+
(~optional (~seq #:analyzers (~var analyzers (expr/c #'(sequence/c expansion-analyzer?)))))
178179
parse-option:syntax-parse-option ...
179180
splicing-pattern
180181
pattern-directive:syntax-parse-pattern-directive ...
@@ -227,6 +228,7 @@
227228

228229
(define-refactoring-rule id
229230
#:description description
231+
(~? (~@ #:analyzers analyzers))
230232
(~var expression expression-matching-id)
231233
expression.refactored)))
232234

@@ -275,7 +277,8 @@
275277

276278
(module+ test
277279
(require rackunit
278-
resyntax/private/analyzer)
280+
resyntax/private/analyzer
281+
resyntax/private/syntax-property-bundle)
279282

280283
(test-case "refactoring-rule stores analyzers"
281284
(define-refactoring-rule test-rule
@@ -285,18 +288,36 @@
285288

286289
(check-true (refactoring-rule? test-rule))
287290
(check-true (set? (refactoring-rule-analyzers test-rule)))
288-
(check-equal? (set-count (refactoring-rule-analyzers test-rule)) 3)
289-
(check-true (for/and ([analyzer (in-set (refactoring-rule-analyzers test-rule))])
290-
(expansion-analyzer? analyzer))))
291+
;; Without #:analyzers, should have empty set (breaking change from previous behavior
292+
;; where rules had 3 default analyzers - identifier-usage, ignored-result-values, and
293+
;; variable-mutability analyzers)
294+
(check-equal? (set-count (refactoring-rule-analyzers test-rule)) 0))
295+
296+
(test-case "refactoring-rule with explicit analyzers"
297+
(define test-analyzer (make-expansion-analyzer (λ (stx) (syntax-property-bundle)) #:name 'test))
298+
(define-refactoring-rule test-rule-with-analyzers
299+
#:description "test rule with analyzers"
300+
#:analyzers (list test-analyzer)
301+
pattern
302+
replacement)
303+
304+
(check-true (refactoring-rule? test-rule-with-analyzers))
305+
(check-equal? (set-count (refactoring-rule-analyzers test-rule-with-analyzers)) 1)
306+
(check-true (set-member? (refactoring-rule-analyzers test-rule-with-analyzers) test-analyzer)))
291307

292308
(test-case "refactoring-suite combines analyzers from rules"
309+
(define analyzer1 (make-expansion-analyzer (λ (stx) (syntax-property-bundle)) #:name 'analyzer1))
310+
(define analyzer2 (make-expansion-analyzer (λ (stx) (syntax-property-bundle)) #:name 'analyzer2))
311+
293312
(define-refactoring-rule rule1
294313
#:description "rule 1"
314+
#:analyzers (list analyzer1)
295315
pattern1
296316
replacement1)
297317

298318
(define-refactoring-rule rule2
299319
#:description "rule 2"
320+
#:analyzers (list analyzer2)
300321
pattern2
301322
replacement2)
302323

@@ -305,33 +326,42 @@
305326
(check-true (refactoring-suite? suite))
306327
(check-equal? (length (refactoring-suite-rules suite)) 2)
307328
(check-true (set? (refactoring-suite-analyzers suite)))
308-
;; All rules have the same analyzers, so the combined set should have 3 unique analyzers
309-
(check-equal? (set-count (refactoring-suite-analyzers suite)) 3)
310-
(check-true (for/and ([analyzer (in-set (refactoring-suite-analyzers suite))])
311-
(expansion-analyzer? analyzer))))
329+
;; Should have 2 unique analyzers from the two rules. Sets automatically deduplicate
330+
;; analyzers, so if both rules used the same analyzer, the count would be 1.
331+
(check-equal? (set-count (refactoring-suite-analyzers suite)) 2)
332+
(check-true (set-member? (refactoring-suite-analyzers suite) analyzer1))
333+
(check-true (set-member? (refactoring-suite-analyzers suite) analyzer2)))
312334

313335
(test-case "nested suites combine analyzers correctly"
336+
(define analyzer1 (make-expansion-analyzer (λ (stx) (syntax-property-bundle)) #:name 'analyzer1))
337+
314338
(define-refactoring-rule inner-rule
315339
#:description "inner rule"
340+
#:analyzers (list analyzer1)
316341
inner-pattern
317342
inner-replacement)
318343

319344
(define inner-suite (refactoring-suite #:rules (list inner-rule)))
320345

321346
(define-refactoring-rule outer-rule
322347
#:description "outer rule"
348+
#:analyzers (list analyzer1)
323349
outer-pattern
324350
outer-replacement)
325351

326352
(define outer-suite (refactoring-suite #:rules (list outer-rule inner-rule)))
327353

328-
(check-equal? (set-count (refactoring-suite-analyzers inner-suite)) 3)
329-
;; Both rules have the same analyzers, so deduplicated should still be 3
330-
(check-equal? (set-count (refactoring-suite-analyzers outer-suite)) 3))
354+
(check-equal? (set-count (refactoring-suite-analyzers inner-suite)) 1)
355+
;; Both rules have the same analyzer, so deduplicated should still be 1
356+
(check-equal? (set-count (refactoring-suite-analyzers outer-suite)) 1))
331357

332358
(test-case "define-refactoring-suite with nested suites preserves analyzers"
359+
(define analyzer-a (make-expansion-analyzer (λ (stx) (syntax-property-bundle)) #:name 'analyzer-a))
360+
(define analyzer-b (make-expansion-analyzer (λ (stx) (syntax-property-bundle)) #:name 'analyzer-b))
361+
333362
(define-refactoring-rule rule-a
334363
#:description "Rule A"
364+
#:analyzers (list analyzer-a)
335365
pattern-a
336366
replacement-a)
337367

@@ -340,6 +370,7 @@
340370

341371
(define-refactoring-rule rule-b
342372
#:description "Rule B"
373+
#:analyzers (list analyzer-b)
343374
pattern-b
344375
replacement-b)
345376

@@ -349,7 +380,7 @@
349380

350381
;; Suite B should have both rules
351382
(check-equal? (length (refactoring-suite-rules suite-b)) 2)
352-
;; And should have 3 analyzers (deduplicated from both rules)
353-
(check-equal? (set-count (refactoring-suite-analyzers suite-b)) 3)
383+
;; And should have 2 unique analyzers (one from each rule)
384+
(check-equal? (set-count (refactoring-suite-analyzers suite-b)) 2)
354385
(check-true (for/and ([analyzer (in-set (refactoring-suite-analyzers suite-b))])
355386
(expansion-analyzer? analyzer)))))

default-recommendations/conditional-shortcuts.rkt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
(require (for-syntax racket/base)
1313
racket/list
1414
resyntax/base
15+
resyntax/default-recommendations/analyzers/ignored-result-values
1516
resyntax/default-recommendations/private/boolean
1617
resyntax/default-recommendations/private/exception
1718
resyntax/default-recommendations/private/if-arm
@@ -229,6 +230,7 @@
229230

230231
(define-refactoring-rule ignored-and-to-when
231232
#:description "This `and` expression's result is ignored. Using `when` makes this clearer."
233+
#:analyzers (list ignored-result-values-analyzer)
232234
#:literals (and)
233235
(and condition:expr body:expr)
234236
#:when (syntax-property this-syntax 'expression-result)
@@ -238,6 +240,7 @@
238240

239241
(define-refactoring-rule explicit-cond-else-void
240242
#:description "Add an explicit `[else (void)]` clause to make the default behavior clear."
243+
#:analyzers (list ignored-result-values-analyzer)
241244
#:literals (cond else void)
242245
(cond-id:cond (~and clause (~not [else . _])) ...)
243246
#:when (equal? (syntax-property this-syntax 'expression-result) 'ignored)

default-recommendations/definition-shortcuts.rkt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
racket/match
1515
rebellion/private/static-name
1616
resyntax/base
17+
resyntax/default-recommendations/analyzers/identifier-usage
1718
syntax/parse)
1819

1920

@@ -33,6 +34,7 @@
3334

3435
(define-definition-context-refactoring-rule inline-unnecessary-define
3536
#:description "This variable is returned immediately and can be inlined."
37+
#:analyzers (list identifier-usage-analyzer)
3638
#:literals (define)
3739
(~seq body-before ... (~and definition (define id1:id expr)) id2:id)
3840
#:when (free-identifier=? #'id1 #'id2)

default-recommendations/dict-suggestions.rkt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
(require racket/dict
1313
racket/set
1414
resyntax/base
15+
resyntax/default-recommendations/analyzers/identifier-usage
1516
syntax/parse)
1617

1718

@@ -45,6 +46,7 @@
4546

4647
(define-refactoring-rule in-dict-to-in-dict-keys
4748
#:description "This `in-dict` can be replaced with `in-dict-keys` since the value is not used."
49+
#:analyzers (list identifier-usage-analyzer)
4850
#:literals (in-dict)
4951
(for-id:id (clause-before ... [(key:id value:id) (in-dict dict-expr)] clause-after ...) body ...)
5052
#:when ((literal-set->predicate simple-for-loops) (attribute for-id))
@@ -54,6 +56,7 @@
5456

5557
(define-refactoring-rule in-dict-to-in-dict-values
5658
#:description "This `in-dict` can be replaced with `in-dict-values` since the key is not used."
59+
#:analyzers (list identifier-usage-analyzer)
5760
#:literals (in-dict)
5861
(for-id:id (clause-before ... [(key:id value:id) (in-dict dict-expr)] clause-after ...) body ...)
5962
#:when ((literal-set->predicate simple-for-loops) (attribute for-id))

default-recommendations/function-definition-shortcuts.rkt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
guard
1414
racket/list
1515
resyntax/base
16+
resyntax/default-recommendations/analyzers/identifier-usage
1617
resyntax/default-recommendations/private/lambda-by-any-name
1718
resyntax/default-recommendations/private/list-function
1819
resyntax/default-recommendations/private/syntax-lines
@@ -144,6 +145,7 @@
144145
(define-refactoring-rule empty-checked-rest-args-to-optional-arg
145146
#:description
146147
"This function definition uses rest arguments in a way equivalent to using an optional argument."
148+
#:analyzers (list identifier-usage-analyzer)
147149
#:literals (define if)
148150

149151
(define (f arg ... . rest-args:id)

default-recommendations/let-replacement/let-replacement.rkt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
racket/set
1515
rebellion/private/static-name
1616
resyntax/base
17+
resyntax/default-recommendations/analyzers/identifier-usage
1718
resyntax/default-recommendations/private/definition-context
1819
resyntax/default-recommendations/private/syntax-identifier-sets
1920
resyntax/default-recommendations/let-replacement/private/let-binding
@@ -28,6 +29,7 @@
2829
(define-definition-context-refactoring-rule let-to-define
2930
#:description
3031
"Internal definitions are recommended instead of `let` expressions, to reduce nesting."
32+
#:analyzers (list identifier-usage-analyzer)
3133
(~seq leading-body ... let-expression:refactorable-let-expression)
3234
#:with (replacement ...)
3335
(if (empty? (attribute leading-body))
@@ -40,7 +42,8 @@
4042
#:description "This `let` expression can be pulled up into multiple `define` expressions."
4143
#:literals (define let)
4244
(~seq body-before ...
43-
(~and original-definition (define id:id (let ([nested-id:id nested-expr:expr] ...) expr:expr)))
45+
(~and original-definition
46+
(define id:id (let ([nested-id:id nested-expr:expr] ...) expr:expr)))
4447
body-after ...)
4548
#:when (for/and ([nested-expr (in-list (attribute nested-expr))])
4649
(identifier-binding-unchanged-in-context? (attribute id) nested-expr))

default-recommendations/list-shortcuts.rkt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
racket/sequence
1717
racket/set
1818
resyntax/base
19+
resyntax/default-recommendations/analyzers/ignored-result-values
1920
resyntax/default-recommendations/private/lambda-by-any-name
2021
resyntax/default-recommendations/private/literal-constant
2122
resyntax/default-recommendations/private/syntax-identifier-sets
@@ -131,6 +132,7 @@
131132
(define-refactoring-rule ignored-map-to-for-each
132133
#:description
133134
"The result of this `map` expression is unused. Make that explicit with `for-each` instead."
135+
#:analyzers (list ignored-result-values-analyzer)
134136
#:literals (map)
135137
(map proc list ...)
136138
#:when (equal? (syntax-property this-syntax 'expression-result) 'ignored)

default-recommendations/loops/for-loop-shortcuts.rkt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
racket/list
1414
racket/set
1515
resyntax/base
16+
resyntax/default-recommendations/analyzers/identifier-usage
1617
resyntax/default-recommendations/loops/private/syntax-classes
1718
resyntax/default-recommendations/private/boolean
1819
resyntax/default-recommendations/private/lambda-by-any-name
@@ -242,6 +243,7 @@ return just that result."
242243
#:description
243244
"The `or` expression in this `for` loop can be replaced by a filtering clause, letting you use\
244245
`define` instead of `let` in the loop body."
246+
#:analyzers (list identifier-usage-analyzer)
245247
#:literals (for/and for*/and or)
246248
((~and loop-id (~or for/and for*/and))
247249
(~and original-clauses (clause ...))
@@ -270,6 +272,7 @@ return just that result."
270272

271273
(define-refactoring-rule in-hash-to-in-hash-keys
272274
#:description "This `in-hash` can be replaced with `in-hash-keys` since the value is not used."
275+
#:analyzers (list identifier-usage-analyzer)
273276
#:literals (in-hash)
274277
(for-id:id (clause-before ... [(key:id value:id) (in-hash hash-expr)] clause-after ...) body ...)
275278
#:when ((literal-set->predicate simple-for-loops) (attribute for-id))

default-recommendations/match-shortcuts.rkt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
racket/match
1414
racket/set
1515
resyntax/base
16+
resyntax/default-recommendations/analyzers/identifier-usage
1617
resyntax/default-recommendations/private/lambda-by-any-name
1718
resyntax/default-recommendations/private/syntax-identifier-sets
1819
resyntax/default-recommendations/private/syntax-lines
@@ -249,6 +250,7 @@
249250
#:description "These list element variable definitions can be expressed more succinctly with \
250251
`match-define`. Note that the suggested replacement raises an error if the list contains more \
251252
elements than expected."
253+
#:analyzers (list identifier-usage-analyzer)
252254
#:literals (define)
253255
(~seq body-before ... (~and definition (define v:id ref-expr:list-ref-expr)) ...+ body-after ...+)
254256
#:do [(define num-vars (length (attribute v)))]

default-recommendations/unused-binding-suggestions.rkt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
(require racket/list
1313
resyntax/base
14+
resyntax/default-recommendations/analyzers/identifier-usage
1415
resyntax/default-recommendations/private/pure-expression
1516
syntax/parse)
1617

@@ -27,6 +28,7 @@
2728

2829
(define-definition-context-refactoring-rule unused-definition
2930
#:description "This definition is not used."
31+
#:analyzers (list identifier-usage-analyzer)
3032
(~seq before ... definition:side-effect-free-definition first-after remaining-after ...)
3133
#:when (equal? (syntax-property (attribute definition.id) 'usage-count) 0)
3234
(before ...

0 commit comments

Comments
 (0)