• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jackfirth / resyntax / #77

30 Oct 2025 03:41AM UTC coverage: 93.533%. Remained the same
#77

push

cover

jackfirth
Tweak `resyntax fix` JSON output

The autofixer already adds the "Automated Resyntax fixes" bit.

0 of 2 new or added lines in 1 file covered. (0.0%)

14507 of 15510 relevant lines covered (93.53%)

0.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

29.34
/cli.rkt
1
#lang racket/base
1✔
2

3

4
(require fancy-app
1✔
5
         json
1✔
6
         racket/cmdline
1✔
7
         racket/format
1✔
8
         racket/logging
1✔
9
         racket/match
1✔
10
         racket/path
1✔
11
         racket/port
1✔
12
         rebellion/base/comparator
1✔
13
         rebellion/base/range
1✔
14
         rebellion/collection/entry
1✔
15
         rebellion/collection/hash
1✔
16
         rebellion/collection/list
1✔
17
         rebellion/collection/multiset
1✔
18
         rebellion/collection/range-set
1✔
19
         rebellion/collection/vector/builder
1✔
20
         rebellion/streaming/reducer
1✔
21
         rebellion/streaming/transducer
1✔
22
         rebellion/type/enum
1✔
23
         rebellion/type/record
1✔
24
         resyntax
1✔
25
         resyntax/default-recommendations
1✔
26
         resyntax/private/file-group
1✔
27
         resyntax/private/github
1✔
28
         resyntax/private/refactoring-result
1✔
29
         resyntax/private/source
1✔
30
         resyntax/private/string-indent
1✔
31
         resyntax/private/syntax-replacement)
1✔
32

33

34
;@----------------------------------------------------------------------------------------------------
35

36

37
(define-enum-type resyntax-output-format (plain-text github-pull-request-review git-commit-message json))
1✔
38
(define-enum-type resyntax-fix-method (modify-files create-multiple-git-commits))
1✔
39
(define-record-type resyntax-analyze-options (targets suite output-format output-destination))
1✔
40

41

42
(define-record-type resyntax-fix-options
1✔
43
  (targets
1✔
44
   suite
1✔
45
   fix-method
1✔
46
   output-format
1✔
47
   max-fixes
1✔
48
   max-modified-files
1✔
49
   max-modified-lines
1✔
50
   max-pass-count))
1✔
51

52

53
(define all-lines (range-set (unbounded-range #:comparator natural<=>)))
1✔
54

55

56
(define (resyntax-analyze-parse-command-line)
1✔
57
  (define targets (make-vector-builder))
×
58
  (define suite default-recommendations)
×
59
  (define output-format plain-text)
×
60
  (define output-destination 'console)
×
61

62
  (command-line
1✔
63
   #:program "resyntax analyze"
×
64

65
   #:multi
1✔
66

67
   ("--file"
1✔
68
    filepath
1✔
69
    "A file to analyze."
×
70
    (vector-builder-add targets (single-file-group filepath all-lines)))
×
71

72
   ("--directory"
1✔
73
    dirpath
1✔
74
    "A directory to anaylze, including subdirectories."
×
75
    (vector-builder-add targets (directory-file-group dirpath)))
×
76

77
   ("--package"
1✔
78
    pkgname
1✔
79
    "An installed package to analyze."
×
80
    (vector-builder-add targets (package-file-group pkgname)))
×
81

82
   ("--local-git-repository"
1✔
83
    repopath baseref
1✔
84
    "A Git repository to search for modified files to analyze. The repopath argument is a directory
×
85
path to the root of a Git repository, and the baseref argument is a Git reference (in the form \
×
86
\"remotename/branchname\") to use as the base state of the repository. Any files that have been \
×
87
changed relative to baseref are analyzed."
×
88
    (vector-builder-add targets (git-repository-file-group repopath baseref)))
×
89

90
   #:once-each
1✔
91

92
   ("--refactoring-suite"
1✔
93
    modpath
1✔
94
    suite-name
1✔
95
    "The refactoring suite to analyze code with."
×
96
    (define parsed-modpath (read (open-input-string modpath)))
×
97
    (define parsed-suite-name (read (open-input-string suite-name)))
×
98
    (set! suite (dynamic-require parsed-modpath parsed-suite-name)))
×
99

100
   ("--output-to-file"
1✔
101
    outputpath
1✔
102
    "Store results in a file instead of printing them to the console."
×
103
    (set! output-destination (simple-form-path outputpath)))
×
104

105
   ("--output-as-github-review"
1✔
106
    "Report results by leaving a GitHub review on the pull request currently being analyzed, as \
×
107
determined by the GITHUB_REPOSITORY and GITHUB_REF environment variables."
×
108
    (set! output-format github-pull-request-review)))
×
109
  
110
  (resyntax-analyze-options
×
111
   #:targets (build-vector targets)
×
112
   #:suite suite
×
113
   #:output-format output-format
×
114
   #:output-destination output-destination))
×
115

116

117
(define (resyntax-fix-parse-command-line)
1✔
118
  (define targets (make-vector-builder))
×
119
  (define suite default-recommendations)
×
120
  (define (add-target! target)
×
121
    (vector-builder-add targets target))
×
122
  (define fix-method modify-files)
×
123
  (define output-format plain-text)
×
124
  (define max-fixes +inf.0)
×
125
  (define max-pass-count 10)
×
126
  (define max-modified-files +inf.0)
×
127
  (define max-modified-lines +inf.0)
×
128

129
  (command-line
1✔
130
   #:program "resyntax fix"
×
131

132
   #:multi
1✔
133

134
   ("--file" filepath "A file to fix." (add-target! (single-file-group filepath all-lines)))
×
135

136
   ("--directory"
1✔
137
    dirpath
1✔
138
    "A directory to fix, including subdirectories."
×
139
    (add-target! (directory-file-group dirpath)))
×
140
   
141
   ("--package"
1✔
142
    pkgname
1✔
143
    "An installed package to fix."
×
144
    (add-target! (package-file-group pkgname)))
×
145
   
146
   ("--local-git-repository"
1✔
147
    repopath baseref
1✔
148
    "A Git repository to search for modified files to fix. The repopath argument is a directory
×
149
path to the root of a Git repository, and the baseref argument is a Git reference (in the form \
×
150
\"remotename/branchname\") to use as the base state of the repository. Any files that have been \
×
151
changed relative to baseref are analyzed and fixed."
×
152
    (add-target! (git-repository-file-group repopath baseref)))
×
153

154
   #:once-each
1✔
155

156
   ("--create-multiple-commits"
1✔
157
    "Modify files by creating a series of individual Git commits."
×
158
    (set! fix-method create-multiple-git-commits))
×
159

160
   ("--output-as-commit-message"
1✔
161
    "Report results in the form of a Git commit message printed to stdout."
×
162
    (set! output-format git-commit-message))
×
163

164
   ("--output-as-json"
1✔
165
    "Report results in the form of a JSON object printed to stdout."
×
166
    (set! output-format json))
×
167

168
   ("--refactoring-suite"
1✔
169
    modpath
1✔
170
    suite-name
1✔
171
    "The refactoring suite to analyze code with."
×
172
    (define parsed-modpath (read (open-input-string modpath)))
×
173
    (define parsed-suite-name (read (open-input-string suite-name)))
×
174
    (set! suite (dynamic-require parsed-modpath parsed-suite-name)))
×
175

176
   ("--max-pass-count"
1✔
177
    passcount
1✔
178
    "The maximum number of times Resyntax will fix each file. By default, Resyntax runs at most 10 \
×
179
passes over each file (or fewer, if no fixes would be made by additional passes). Multiple passes \
×
180
are needed when applying a fix unlocks further fixes."
×
181
    (set! max-pass-count (string->number passcount)))
×
182

183
   ("--max-fixes"
1✔
184
    fixlimit
1✔
185
    "The maximum number of fixes to apply. If not specified, all fixes found will be applied."
×
186
    (set! max-fixes (string->number fixlimit)))
×
187

188
   ("--max-modified-files"
1✔
189
    modifiedlimit
1✔
190
    "The maximum number of files to modify. If not specified, fixes will be applied to all files."
×
191
    (set! max-modified-files (string->number modifiedlimit)))
×
192

193
   ("--max-modified-lines"
1✔
194
    modifiedlines
1✔
195
    "The maximum number of lines to modify. If not specified, no line limit is applied."
×
196
    (set! max-modified-lines (string->number modifiedlines))))
×
197

198
  (resyntax-fix-options #:targets (build-vector targets)
×
199
                        #:suite suite
×
200
                        #:fix-method fix-method
×
201
                        #:output-format output-format
×
202
                        #:max-fixes max-fixes
×
203
                        #:max-modified-files max-modified-files
×
204
                        #:max-modified-lines max-modified-lines
×
205
                        #:max-pass-count max-pass-count))
×
206

207

208
(define (resyntax-run)
1✔
209
  (command-line
1✔
210
   #:program "resyntax"
×
211

212
   #:usage-help
1✔
213
   "\n<command> is one of
×
214

215
\tanalyze
×
216
\tfix
×
217

218
For help on these, use 'analyze --help' or 'fix --help'."
×
219

220
   #:ps "\nSee https://docs.racket-lang.org/resyntax/index.html for details."
×
221
   #:args (command . leftover-args)
1✔
222
   (define leftover-arg-vector (vector->immutable-vector (list->vector leftover-args)))
×
223

224
   (define (call-command command-thunk)
×
225
     (parameterize ([current-command-line-arguments leftover-arg-vector])
×
226
       (with-logging-to-port (current-error-port)
×
227
         command-thunk
×
228
         #:logger (current-logger)
×
229
         'info 'resyntax
×
230
         'error)))
×
231

232
   (match command
×
233
     ["analyze" (call-command resyntax-analyze-run)]
×
234
     ["fix" (call-command resyntax-fix-run)])))
×
235

236

237
(define (resyntax-analyze-run)
1✔
238
  (define options (resyntax-analyze-parse-command-line))
×
239
  (define sources (file-groups-resolve (resyntax-analyze-options-targets options)))
×
240
  (define analysis
×
241
    (resyntax-analyze-all sources
×
242
                          #:suite (resyntax-analyze-options-suite options)
×
243
                          #:max-passes 1))
×
244
  (define results
×
245
    (transduce (resyntax-analysis-all-results analysis)
×
246
               (append-mapping in-hash-values)
×
247
               (append-mapping refactoring-result-set-results)
×
248
               #:into into-list))
×
249

250
  (define (display-results)
×
251
    (match (resyntax-analyze-options-output-format options)
×
252
      [(== plain-text)
×
253
       (for ([result (in-list results)])
×
254
         (define path
×
255
           (file-source-path
×
256
            (syntax-replacement-source (refactoring-result-syntax-replacement result))))
×
257
         (define line (refactoring-result-original-line result))
×
258
         (define column (refactoring-result-original-column result))
×
259
         (printf "resyntax: ~a:~a:~a [~a]\n" path line column (refactoring-result-rule-name result))
×
260
         (printf "\n\n~a\n" (string-indent (refactoring-result-message result) #:amount 2))
×
261
         (define old-code (refactoring-result-original-code result))
×
262
         (define new-code (refactoring-result-new-code result))
×
263
         (printf "\n\n~a\n\n\n~a\n\n\n"
×
264
                 (string-indent (~a old-code) #:amount 2)
×
265
                 (string-indent (~a new-code) #:amount 2)))]
×
266
      [(== github-pull-request-review)
×
267
       (define req (refactoring-results->github-review results #:file-count (hash-count sources)))
×
268
       (write-json (github-review-request-jsexpr req))]))
×
269

270
  (match (resyntax-analyze-options-output-destination options)
×
271
    ['console
×
272
     (printf "resyntax: --- displaying results ---\n")
×
273
     (display-results)]
×
274
    [(? path? output-path)
×
275
     (printf "resyntax: --- writing results to file ---\n")
×
276
     (with-output-to-file output-path display-results #:mode 'text)]))
×
277

278

279
(define (resyntax-fix-run)
1✔
280
  (define options (resyntax-fix-parse-command-line))
×
281
  (define fix-method (resyntax-fix-options-fix-method options))
×
282
  (define output-format (resyntax-fix-options-output-format options))
×
283
  (define sources (file-groups-resolve (resyntax-fix-options-targets options)))
×
284
  (define max-modified-files (resyntax-fix-options-max-modified-files options))
×
285
  (define max-modified-lines (resyntax-fix-options-max-modified-lines options))
×
286
  (define analysis
×
287
    (resyntax-analyze-all sources
×
288
                          #:suite (resyntax-fix-options-suite options)
×
289
                          #:max-fixes (resyntax-fix-options-max-fixes options)
×
290
                          #:max-passes (resyntax-fix-options-max-pass-count options)
×
291
                          #:max-modified-sources max-modified-files
×
292
                          #:max-modified-lines max-modified-lines))
×
293
  (match fix-method
×
294
    [(== modify-files)
×
295
     (resyntax-analysis-write-file-changes! analysis)]
×
296
    [(== create-multiple-git-commits)
×
297
     (resyntax-analysis-commit-fixes! analysis)])
×
298
  (match output-format
×
299
    [(== git-commit-message)
×
300
     (resyntax-fix-print-git-commit-message analysis)]
×
301
    [(== json)
×
302
     (resyntax-fix-print-json analysis)]
×
303
    [(== plain-text)
×
304
     (resyntax-fix-print-plain-text-summary analysis)]))
×
305

306

307
(define (resyntax-fix-print-git-commit-message analysis)
1✔
308
  (define total-fixes (resyntax-analysis-total-fixes analysis))
×
309
  (define total-files (resyntax-analysis-total-sources-modified analysis))
×
310
  (define fix-counts-by-rule
×
311
    (transduce (in-hash-entries (multiset-frequencies (resyntax-analysis-rules-applied analysis)))
×
312
               (sorting #:key entry-value #:descending? #true)
×
313
               #:into into-list))
×
314
  (define issue-string (if (> total-fixes 1) "issues" "issue"))
×
315
  (define file-string (if (> total-files 1) "files" "file"))
×
316
  (if (zero? total-fixes)
×
317
      (printf "Resyntax found no issues.\n")
×
318
      (printf "Resyntax fixed ~a ~a in ~a ~a.\n\n" total-fixes issue-string total-files file-string))
×
319
  (for ([rule+count (in-list fix-counts-by-rule)])
×
320
    (match-define (entry rule count) rule+count)
×
321
    (define occurrence-string (if (> count 1) "occurrences" "occurrence"))
×
322
    (printf "  * Fixed ~a ~a of `~a`\n" count occurrence-string rule))
×
323
  (unless (zero? total-fixes)
×
324
    (newline)))
×
325

326

327
(define (resyntax-fix-print-plain-text-summary analysis)
1✔
328
  (printf "resyntax: --- summary ---\n\n")
×
329
  (define total-fixes (resyntax-analysis-total-fixes analysis))
×
330
  (define total-files (resyntax-analysis-total-sources-modified analysis))
×
331
  (define message
×
332
    (cond
×
333
      [(zero? total-fixes) "No issues found."]
×
334
      [(equal? total-fixes 1) "Fixed 1 issue in 1 file."]
×
335
      [(equal? total-files 1) (format "Fixed ~a issues in 1 file." total-fixes)]
×
336
      [else (format "Fixed ~a issues in ~a files." total-fixes total-files)]))
×
337
  (printf "  ~a\n\n" message)
×
338
  (define rules-applied (resyntax-analysis-rules-applied analysis))
×
339
  (transduce (in-hash-entries (multiset-frequencies rules-applied))
×
340
             (sorting #:key entry-value #:descending? #true)
×
341
             (mapping
×
342
              (λ (e)
×
343
                (match-define (entry rule-name rule-fixes) e)
×
344
                (define message
×
345
                  (if (equal? rule-fixes 1)
×
346
                      (format "Fixed 1 occurrence of ~a" rule-name)
×
347
                      (format "Fixed ~a occurrences of ~a" rule-fixes rule-name)))
×
348
                (format "  * ~a\n" message)))
×
349
             #:into (into-for-each display))
×
350
  (when (positive? total-fixes)
×
351
    (newline)))
×
352

353

354
(define (resyntax-fix-print-json analysis)
1✔
355
  (define total-fixes (resyntax-analysis-total-fixes analysis))
×
356
  (define total-files (resyntax-analysis-total-sources-modified analysis))
×
357
  (define fix-counts-by-rule
×
358
    (transduce (in-hash-entries (multiset-frequencies (resyntax-analysis-rules-applied analysis)))
×
359
               (sorting #:key entry-value #:descending? #true)
×
360
               #:into into-list))
×
361
  
362
  ;; Build commit message
363
  (define commit-message
×
364
    (with-output-to-string
×
365
      (λ ()
×
366
        (define issue-string (if (> total-fixes 1) "issues" "issue"))
×
367
        (define file-string (if (> total-files 1) "files" "file"))
×
368
        (if (zero? total-fixes)
×
369
            (printf "Resyntax found no issues.")
×
NEW
370
            (printf "Resyntax fixed ~a ~a in ~a ~a." 
×
371
                    total-fixes issue-string total-files file-string))
×
372
        (unless (zero? total-fixes)
×
373
          (printf "\n")
×
374
          (for ([rule+count (in-list fix-counts-by-rule)])
×
375
            (match-define (entry rule count) rule+count)
×
376
            (define occurrence-string (if (> count 1) "occurrences" "occurrence"))
×
377
            (printf "\n  * Fixed ~a ~a of `~a`" count occurrence-string rule))))))
×
378
  
379
  ;; Output JSON
380
  (write-json
×
NEW
381
   (hasheq 'commit_message_body commit-message
×
382
           'fix_count total-fixes))
×
383
  (newline))
×
384

385

386
(module+ main
387
  (resyntax-run))
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc