Skip to content

Commit 220e3e9

Browse files
authored
refactor: make formatter config opt-in (#22997)
1 parent f135c0b commit 220e3e9

9 files changed

Lines changed: 316 additions & 147 deletions

File tree

packages/opencode/src/config/formatter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ export const Entry = z.object({
99
extensions: z.array(z.string()).optional(),
1010
})
1111

12-
export const Info = z.union([z.literal(false), z.record(z.string(), Entry)])
12+
export const Info = z.union([z.boolean(), z.record(z.string(), Entry)])
1313
export type Info = z.infer<typeof Info>

packages/opencode/src/config/lsp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const Entry = z.union([
1818
}),
1919
])
2020

21-
export const Info = z.union([z.literal(false), z.record(z.string(), Entry)]).refine(
21+
export const Info = z.union([z.boolean(), z.record(z.string(), Entry)]).refine(
2222
(data) => {
2323
if (typeof data === "boolean") return true
2424
const serverIds = new Set(Object.values(LSPServer).map((server) => server.id))

packages/opencode/src/format/index.ts

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -41,39 +41,6 @@ export const layer = Layer.effect(
4141
const commands: Record<string, string[] | false> = {}
4242
const formatters: Record<string, Formatter.Info> = {}
4343

44-
const cfg = yield* config.get()
45-
46-
if (cfg.formatter !== false) {
47-
for (const item of Object.values(Formatter)) {
48-
formatters[item.name] = item
49-
}
50-
for (const [name, item] of Object.entries(cfg.formatter ?? {})) {
51-
// Ruff and uv are both the same formatter, so disabling either should disable both.
52-
if (["ruff", "uv"].includes(name) && (cfg.formatter?.ruff?.disabled || cfg.formatter?.uv?.disabled)) {
53-
// TODO combine formatters so shared backends like Ruff/uv don't need linked disable handling here.
54-
delete formatters.ruff
55-
delete formatters.uv
56-
continue
57-
}
58-
if (item.disabled) {
59-
delete formatters[name]
60-
continue
61-
}
62-
const info = mergeDeep(formatters[name] ?? {}, {
63-
extensions: [],
64-
...item,
65-
})
66-
67-
formatters[name] = {
68-
...info,
69-
name,
70-
enabled: async () => info.command ?? false,
71-
}
72-
}
73-
} else {
74-
log.info("all formatters are disabled")
75-
}
76-
7744
async function getCommand(item: Formatter.Info) {
7845
let cmd = commands[item.name]
7946
if (cmd === false || cmd === undefined) {
@@ -149,6 +116,48 @@ export const layer = Layer.effect(
149116
})
150117
}
151118

119+
const cfg = yield* config.get()
120+
121+
if (!cfg.formatter) {
122+
log.info("all formatters are disabled")
123+
log.info("init")
124+
return {
125+
formatters,
126+
isEnabled,
127+
formatFile,
128+
}
129+
}
130+
131+
for (const item of Object.values(Formatter)) {
132+
formatters[item.name] = item
133+
}
134+
135+
if (cfg.formatter !== true) {
136+
for (const [name, item] of Object.entries(cfg.formatter)) {
137+
const builtIn = Formatter[name as keyof typeof Formatter]
138+
139+
// Ruff and uv are both the same formatter, so disabling either should disable both.
140+
if (["ruff", "uv"].includes(name) && (cfg.formatter.ruff?.disabled || cfg.formatter.uv?.disabled)) {
141+
// TODO combine formatters so shared backends like Ruff/uv don't need linked disable handling here.
142+
delete formatters.ruff
143+
delete formatters.uv
144+
continue
145+
}
146+
if (item.disabled) {
147+
delete formatters[name]
148+
continue
149+
}
150+
const info = mergeDeep(builtIn ?? { extensions: [] }, item)
151+
152+
formatters[name] = {
153+
...info,
154+
name,
155+
extensions: info.extensions ?? [],
156+
enabled: builtIn && !info.command ? builtIn.enabled : async () => info.command ?? false,
157+
}
158+
}
159+
}
160+
152161
log.info("init")
153162

154163
return {

packages/opencode/src/lsp/lsp.ts

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export const layer = Layer.effect(
167167

168168
const servers: Record<string, LSPServer.Info> = {}
169169

170-
if (cfg.lsp === false) {
170+
if (!cfg.lsp) {
171171
log.info("all LSPs are disabled")
172172
} else {
173173
for (const server of Object.values(LSPServer)) {
@@ -176,25 +176,27 @@ export const layer = Layer.effect(
176176

177177
filterExperimentalServers(servers)
178178

179-
for (const [name, item] of Object.entries(cfg.lsp ?? {})) {
180-
const existing = servers[name]
181-
if (item.disabled) {
182-
log.info(`LSP server ${name} is disabled`)
183-
delete servers[name]
184-
continue
185-
}
186-
servers[name] = {
187-
...existing,
188-
id: name,
189-
root: existing?.root ?? (async () => Instance.directory),
190-
extensions: item.extensions ?? existing?.extensions ?? [],
191-
spawn: async (root) => ({
192-
process: lspspawn(item.command[0], item.command.slice(1), {
193-
cwd: root,
194-
env: { ...process.env, ...item.env },
179+
if (cfg.lsp !== true) {
180+
for (const [name, item] of Object.entries(cfg.lsp)) {
181+
const existing = servers[name]
182+
if (item.disabled) {
183+
log.info(`LSP server ${name} is disabled`)
184+
delete servers[name]
185+
continue
186+
}
187+
servers[name] = {
188+
...existing,
189+
id: name,
190+
root: existing?.root ?? (async () => Instance.directory),
191+
extensions: item.extensions ?? existing?.extensions ?? [],
192+
spawn: async (root) => ({
193+
process: lspspawn(item.command[0], item.command.slice(1), {
194+
cwd: root,
195+
env: { ...process.env, ...item.env },
196+
}),
197+
initialization: item.initialization,
195198
}),
196-
initialization: item.initialization,
197-
}),
199+
}
198200
}
199201
}
200202

packages/opencode/test/config/config.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,42 @@ test("loads JSON config file", async () => {
142142
})
143143
})
144144

145+
test("loads formatter boolean config", async () => {
146+
await using tmp = await tmpdir({
147+
init: async (dir) => {
148+
await writeConfig(dir, {
149+
$schema: "https://opencode.ai/config.json",
150+
formatter: true,
151+
})
152+
},
153+
})
154+
await Instance.provide({
155+
directory: tmp.path,
156+
fn: async () => {
157+
const config = await load()
158+
expect(config.formatter).toBe(true)
159+
},
160+
})
161+
})
162+
163+
test("loads lsp boolean config", async () => {
164+
await using tmp = await tmpdir({
165+
init: async (dir) => {
166+
await writeConfig(dir, {
167+
$schema: "https://opencode.ai/config.json",
168+
lsp: true,
169+
})
170+
},
171+
})
172+
await Instance.provide({
173+
directory: tmp.path,
174+
fn: async () => {
175+
const config = await load()
176+
expect(config.lsp).toBe(true)
177+
},
178+
})
179+
})
180+
145181
test("loads project config from Git Bash and MSYS2 paths on Windows", async () => {
146182
// Git Bash and MSYS2 both use /<drive>/... paths on Windows.
147183
await check((dir) => {

0 commit comments

Comments
 (0)