Skip to content

Commit 864964c

Browse files
committed
refactor(format): update formatter interface to return command from enabled()
- Remove command property from Info interface - Change enabled() to return string[] | undefined instead of boolean - Update all formatters to return command array from enabled() - Use Npm.which for bun-based formatters (prettier, oxfmt, biome) - Simplify dependencies checks with || operator
1 parent ec3ae17 commit 864964c

File tree

3 files changed

+104
-89
lines changed

3 files changed

+104
-89
lines changed

packages/opencode/src/format/formatter.ts

Lines changed: 78 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { text } from "node:stream/consumers"
2+
import { Npm } from "@/npm"
23
import { Instance } from "../project/instance"
34
import { Filesystem } from "../util/filesystem"
45
import { Process } from "../util/process"
@@ -7,33 +8,33 @@ import { Flag } from "@/flag/flag"
78

89
export interface Info {
910
name: string
10-
command: string[]
1111
environment?: Record<string, string>
1212
extensions: string[]
13-
enabled(): Promise<boolean>
13+
enabled(): Promise<string[] | false>
1414
}
1515

1616
export const gofmt: Info = {
1717
name: "gofmt",
18-
command: ["gofmt", "-w", "$FILE"],
1918
extensions: [".go"],
2019
async enabled() {
21-
return which("gofmt") !== null
20+
const match = which("gofmt")
21+
if (!match) return false
22+
return [match, "-w", "$FILE"]
2223
},
2324
}
2425

2526
export const mix: Info = {
2627
name: "mix",
27-
command: ["mix", "format", "$FILE"],
2828
extensions: [".ex", ".exs", ".eex", ".heex", ".leex", ".neex", ".sface"],
2929
async enabled() {
30-
return which("mix") !== null
30+
const match = which("mix")
31+
if (!match) return false
32+
return [match, "format", "$FILE"]
3133
},
3234
}
3335

3436
export const prettier: Info = {
3537
name: "prettier",
36-
command: ["bun", "x", "prettier", "--write", "$FILE"],
3738
environment: {
3839
BUN_BE_BUN: "1",
3940
},
@@ -72,16 +73,17 @@ export const prettier: Info = {
7273
dependencies?: Record<string, string>
7374
devDependencies?: Record<string, string>
7475
}>(item)
75-
if (json.dependencies?.prettier) return true
76-
if (json.devDependencies?.prettier) return true
76+
if (json.dependencies?.prettier || json.devDependencies?.prettier) {
77+
const bin = await Npm.which("prettier")
78+
if (bin) return [bin, "--write", "$FILE"]
79+
}
7780
}
7881
return false
7982
},
8083
}
8184

8285
export const oxfmt: Info = {
8386
name: "oxfmt",
84-
command: ["bun", "x", "oxfmt", "$FILE"],
8587
environment: {
8688
BUN_BE_BUN: "1",
8789
},
@@ -94,16 +96,17 @@ export const oxfmt: Info = {
9496
dependencies?: Record<string, string>
9597
devDependencies?: Record<string, string>
9698
}>(item)
97-
if (json.dependencies?.oxfmt) return true
98-
if (json.devDependencies?.oxfmt) return true
99+
if (json.dependencies?.oxfmt || json.devDependencies?.oxfmt) {
100+
const bin = await Npm.which("oxfmt")
101+
if (bin) return [bin, "$FILE"]
102+
}
99103
}
100104
return false
101105
},
102106
}
103107

104108
export const biome: Info = {
105109
name: "biome",
106-
command: ["bun", "x", "@biomejs/biome", "check", "--write", "$FILE"],
107110
environment: {
108111
BUN_BE_BUN: "1",
109112
},
@@ -140,7 +143,8 @@ export const biome: Info = {
140143
for (const config of configs) {
141144
const found = await Filesystem.findUp(config, Instance.directory, Instance.worktree)
142145
if (found.length > 0) {
143-
return true
146+
const bin = await Npm.which("@biomejs/biome")
147+
if (bin) return [bin, "check", "--write", "$FILE"]
144148
}
145149
}
146150
return false
@@ -149,35 +153,39 @@ export const biome: Info = {
149153

150154
export const zig: Info = {
151155
name: "zig",
152-
command: ["zig", "fmt", "$FILE"],
153156
extensions: [".zig", ".zon"],
154157
async enabled() {
155-
return which("zig") !== null
158+
const match = which("zig")
159+
if (!match) return false
160+
return [match, "fmt", "$FILE"]
156161
},
157162
}
158163

159164
export const clang: Info = {
160165
name: "clang-format",
161-
command: ["clang-format", "-i", "$FILE"],
162166
extensions: [".c", ".cc", ".cpp", ".cxx", ".c++", ".h", ".hh", ".hpp", ".hxx", ".h++", ".ino", ".C", ".H"],
163167
async enabled() {
164168
const items = await Filesystem.findUp(".clang-format", Instance.directory, Instance.worktree)
165-
return items.length > 0
169+
if (items.length > 0) {
170+
const match = which("clang-format")
171+
if (match) return [match, "-i", "$FILE"]
172+
}
173+
return false
166174
},
167175
}
168176

169177
export const ktlint: Info = {
170178
name: "ktlint",
171-
command: ["ktlint", "-F", "$FILE"],
172179
extensions: [".kt", ".kts"],
173180
async enabled() {
174-
return which("ktlint") !== null
181+
const match = which("ktlint")
182+
if (!match) return false
183+
return [match, "-F", "$FILE"]
175184
},
176185
}
177186

178187
export const ruff: Info = {
179188
name: "ruff",
180-
command: ["ruff", "format", "$FILE"],
181189
extensions: [".py", ".pyi"],
182190
async enabled() {
183191
if (!which("ruff")) return false
@@ -187,9 +195,9 @@ export const ruff: Info = {
187195
if (found.length > 0) {
188196
if (config === "pyproject.toml") {
189197
const content = await Filesystem.readText(found[0])
190-
if (content.includes("[tool.ruff]")) return true
198+
if (content.includes("[tool.ruff]")) return ["ruff", "format", "$FILE"]
191199
} else {
192-
return true
200+
return ["ruff", "format", "$FILE"]
193201
}
194202
}
195203
}
@@ -198,7 +206,7 @@ export const ruff: Info = {
198206
const found = await Filesystem.findUp(dep, Instance.directory, Instance.worktree)
199207
if (found.length > 0) {
200208
const content = await Filesystem.readText(found[0])
201-
if (content.includes("ruff")) return true
209+
if (content.includes("ruff")) return ["ruff", "format", "$FILE"]
202210
}
203211
}
204212
return false
@@ -207,7 +215,6 @@ export const ruff: Info = {
207215

208216
export const rlang: Info = {
209217
name: "air",
210-
command: ["air", "format", "$FILE"],
211218
extensions: [".R"],
212219
async enabled() {
213220
const airPath = which("air")
@@ -226,132 +233,141 @@ export const rlang: Info = {
226233
const firstLine = output.split("\n")[0]
227234
const hasR = firstLine.includes("R language")
228235
const hasFormatter = firstLine.includes("formatter")
229-
return hasR && hasFormatter
230-
} catch (error) {
236+
if (hasR && hasFormatter) return ["air", "format", "$FILE"]
237+
} catch {
231238
return false
232239
}
240+
return false
233241
},
234242
}
235243

236244
export const uvformat: Info = {
237245
name: "uv",
238-
command: ["uv", "format", "--", "$FILE"],
239246
extensions: [".py", ".pyi"],
240247
async enabled() {
241248
if (await ruff.enabled()) return false
242249
if (which("uv") !== null) {
243250
const proc = Process.spawn(["uv", "format", "--help"], { stderr: "pipe", stdout: "pipe" })
244251
const code = await proc.exited
245-
return code === 0
252+
if (code === 0) return ["uv", "format", "--", "$FILE"]
246253
}
247254
return false
248255
},
249256
}
250257

251258
export const rubocop: Info = {
252259
name: "rubocop",
253-
command: ["rubocop", "--autocorrect", "$FILE"],
254260
extensions: [".rb", ".rake", ".gemspec", ".ru"],
255261
async enabled() {
256-
return which("rubocop") !== null
262+
const match = which("rubocop")
263+
if (!match) return false
264+
return [match, "--autocorrect", "$FILE"]
257265
},
258266
}
259267

260268
export const standardrb: Info = {
261269
name: "standardrb",
262-
command: ["standardrb", "--fix", "$FILE"],
263270
extensions: [".rb", ".rake", ".gemspec", ".ru"],
264271
async enabled() {
265-
return which("standardrb") !== null
272+
const match = which("standardrb")
273+
if (!match) return false
274+
return [match, "--fix", "$FILE"]
266275
},
267276
}
268277

269278
export const htmlbeautifier: Info = {
270279
name: "htmlbeautifier",
271-
command: ["htmlbeautifier", "$FILE"],
272280
extensions: [".erb", ".html.erb"],
273281
async enabled() {
274-
return which("htmlbeautifier") !== null
282+
const match = which("htmlbeautifier")
283+
if (!match) return false
284+
return [match, "$FILE"]
275285
},
276286
}
277287

278288
export const dart: Info = {
279289
name: "dart",
280-
command: ["dart", "format", "$FILE"],
281290
extensions: [".dart"],
282291
async enabled() {
283-
return which("dart") !== null
292+
const match = which("dart")
293+
if (!match) return false
294+
return [match, "format", "$FILE"]
284295
},
285296
}
286297

287298
export const ocamlformat: Info = {
288299
name: "ocamlformat",
289-
command: ["ocamlformat", "-i", "$FILE"],
290300
extensions: [".ml", ".mli"],
291301
async enabled() {
292302
if (!which("ocamlformat")) return false
293303
const items = await Filesystem.findUp(".ocamlformat", Instance.directory, Instance.worktree)
294-
return items.length > 0
304+
if (items.length > 0) return ["ocamlformat", "-i", "$FILE"]
305+
return false
295306
},
296307
}
297308

298309
export const terraform: Info = {
299310
name: "terraform",
300-
command: ["terraform", "fmt", "$FILE"],
301311
extensions: [".tf", ".tfvars"],
302312
async enabled() {
303-
return which("terraform") !== null
313+
const match = which("terraform")
314+
if (!match) return false
315+
return [match, "fmt", "$FILE"]
304316
},
305317
}
306318

307319
export const latexindent: Info = {
308320
name: "latexindent",
309-
command: ["latexindent", "-w", "-s", "$FILE"],
310321
extensions: [".tex"],
311322
async enabled() {
312-
return which("latexindent") !== null
323+
const match = which("latexindent")
324+
if (!match) return false
325+
return [match, "-w", "-s", "$FILE"]
313326
},
314327
}
315328

316329
export const gleam: Info = {
317330
name: "gleam",
318-
command: ["gleam", "format", "$FILE"],
319331
extensions: [".gleam"],
320332
async enabled() {
321-
return which("gleam") !== null
333+
const match = which("gleam")
334+
if (!match) return false
335+
return [match, "format", "$FILE"]
322336
},
323337
}
324338

325339
export const shfmt: Info = {
326340
name: "shfmt",
327-
command: ["shfmt", "-w", "$FILE"],
328341
extensions: [".sh", ".bash"],
329342
async enabled() {
330-
return which("shfmt") !== null
343+
const match = which("shfmt")
344+
if (!match) return false
345+
return [match, "-w", "$FILE"]
331346
},
332347
}
333348

334349
export const nixfmt: Info = {
335350
name: "nixfmt",
336-
command: ["nixfmt", "$FILE"],
337351
extensions: [".nix"],
338352
async enabled() {
339-
return which("nixfmt") !== null
353+
const match = which("nixfmt")
354+
if (!match) return false
355+
return [match, "$FILE"]
340356
},
341357
}
342358

343359
export const rustfmt: Info = {
344360
name: "rustfmt",
345-
command: ["rustfmt", "$FILE"],
346361
extensions: [".rs"],
347362
async enabled() {
348-
return which("rustfmt") !== null
363+
const match = which("rustfmt")
364+
if (!match) return false
365+
return [match, "$FILE"]
349366
},
350367
}
351368

352369
export const pint: Info = {
353370
name: "pint",
354-
command: ["./vendor/bin/pint", "$FILE"],
355371
extensions: [".php"],
356372
async enabled() {
357373
const items = await Filesystem.findUp("composer.json", Instance.directory, Instance.worktree)
@@ -360,36 +376,38 @@ export const pint: Info = {
360376
require?: Record<string, string>
361377
"require-dev"?: Record<string, string>
362378
}>(item)
363-
if (json.require?.["laravel/pint"]) return true
364-
if (json["require-dev"]?.["laravel/pint"]) return true
379+
if (json.require?.["laravel/pint"] || json["require-dev"]?.["laravel/pint"]) return ["./vendor/bin/pint", "$FILE"]
365380
}
366381
return false
367382
},
368383
}
369384

370385
export const ormolu: Info = {
371386
name: "ormolu",
372-
command: ["ormolu", "-i", "$FILE"],
373387
extensions: [".hs"],
374388
async enabled() {
375-
return which("ormolu") !== null
389+
const match = which("ormolu")
390+
if (!match) return false
391+
return [match, "-i", "$FILE"]
376392
},
377393
}
378394

379395
export const cljfmt: Info = {
380396
name: "cljfmt",
381-
command: ["cljfmt", "fix", "--quiet", "$FILE"],
382397
extensions: [".clj", ".cljs", ".cljc", ".edn"],
383398
async enabled() {
384-
return which("cljfmt") !== null
399+
const match = which("cljfmt")
400+
if (!match) return false
401+
return [match, "fix", "--quiet", "$FILE"]
385402
},
386403
}
387404

388405
export const dfmt: Info = {
389406
name: "dfmt",
390-
command: ["dfmt", "-i", "$FILE"],
391407
extensions: [".d"],
392408
async enabled() {
393-
return which("dfmt") !== null
409+
const match = which("dfmt")
410+
if (!match) return false
411+
return [match, "-i", "$FILE"]
394412
},
395413
}

0 commit comments

Comments
 (0)