-
Notifications
You must be signed in to change notification settings - Fork 11.6k
Description
Verify there isn't already a bug report for this.
- I have verified there isn't already an open bug report for this.
Describe the bug
When overriding a built-in formatter's extensions without also specifying command, the entire override is silently discarded and the built-in defaults (including all original extensions) remain active.
For example, this config intends to prevent prettier from formatting .md files:
"formatter": {
"prettier": {
"extensions": [".js", ".jsx", ".ts", ".tsx", ".css", ".scss", ".json", ".yaml", ".yml"]
}
}But prettier still formats .md files because the override is never applied.
Root cause
In packages/opencode/src/format/index.ts, user config is merged via:
const result: Formatter.Info = mergeDeep(formatters[name] ?? {}, {
command: [],
extensions: [],
...item,
})
if (result.command.length === 0) continueThe command: [] stub is spread before ...item. Since the user only provided extensions, command stays as []. mergeDeep uses replace semantics for arrays (not deep merge), so the empty array replaces the built-in command. The command.length === 0 guard then fires, skipping the override entirely. The original built-in formatter config (with all default extensions) remains.
Expected behavior
Partial overrides of built-in formatters should preserve unspecified fields. Overriding only extensions should keep the built-in command intact.
Workaround
Explicitly include the command in the override:
"formatter": {
"prettier": {
"command": ["npx", "prettier", "--write", "$FILE"],
"extensions": [".js", ".jsx", ".ts", ".tsx", ".css", ".scss", ".json", ".yaml", ".yml"]
}
}OpenCode version
Latest (verified against current dev branch source)