Skip to content

Commit c180d29

Browse files
Copilotntotten
andcommitted
Add custom executable support for Prettier
Co-authored-by: ntotten <282782+ntotten@users.noreply.github.com>
1 parent fd35374 commit c180d29

5 files changed

Lines changed: 455 additions & 3 deletions

File tree

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
"prettier.resolveGlobalModules",
133133
"prettier.ignorePath",
134134
"prettier.prettierPath",
135+
"prettier.customExecutable",
135136
"prettier.configPath",
136137
"prettier.useEditorConfig",
137138
"prettier.resolveGlobalModules",
@@ -214,6 +215,11 @@
214215
"markdownDescription": "%ext.config.prettierPath%",
215216
"scope": "resource"
216217
},
218+
"prettier.customExecutable": {
219+
"type": "string",
220+
"markdownDescription": "%ext.config.customExecutable%",
221+
"scope": "resource"
222+
},
217223
"prettier.configPath": {
218224
"type": "string",
219225
"markdownDescription": "%ext.config.configPath%",

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"ext.config.parser": "Override the parser. You shouldn't have to change this setting.",
2222
"ext.config.parserDeprecationMessage": "This setting is no longer supported. Use a prettier configuration file instead.",
2323
"ext.config.prettierPath": "Path to the `prettier` module, eg: `./node_modules/prettier`.",
24+
"ext.config.customExecutable": "Use a custom executable to run Prettier. Specify the full command including arguments, with `${prettier}` as a placeholder for the Prettier path and `${file}` for the file to format. Example: `docker compose exec -T app node_modules/.bin/prettier`. When specified, takes precedence over `prettier.prettierPath`.",
2425
"ext.config.printWidth": "Fit code within this line limit.",
2526
"ext.config.proseWrap": "(Markdown) wrap prose over multiple lines.",
2627
"ext.config.quoteProps": "Change when properties in objects are quoted.\nValid options:\n- `\"as-needed\"` - Only add quotes around object properties where required.\n- `\"consistent\"` - If at least one property in an object requires quotes, quote all properties.\n- `\"preserve\"` - Respect the input use of quotes in object properties.",

src/ModuleResolverNode.ts

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
getWorkspaceRelativePath,
3232
} from "./utils/workspace.js";
3333
import { PrettierDynamicInstance } from "./PrettierDynamicInstance.js";
34+
import { PrettierExecutableInstance } from "./PrettierExecutableInstance.js";
3435

3536
const minPrettierVersion = "1.13.0";
3637

@@ -100,9 +101,17 @@ export class ModuleResolver implements ModuleResolverInterface {
100101
return getBundledPrettier();
101102
}
102103

103-
const { prettierPath, resolveGlobalModules } = getWorkspaceConfig(
104-
Uri.file(fileName),
105-
);
104+
const { prettierPath, customExecutable, resolveGlobalModules } =
105+
getWorkspaceConfig(Uri.file(fileName));
106+
107+
// If custom executable is specified, use it with highest priority
108+
if (customExecutable) {
109+
return this.getCustomExecutableInstance(
110+
fileName,
111+
customExecutable,
112+
prettierPath,
113+
);
114+
}
106115

107116
// Look for local module
108117
let modulePath: string | undefined;
@@ -160,6 +169,67 @@ export class ModuleResolver implements ModuleResolverInterface {
160169
return prettierInstance;
161170
}
162171

172+
private async getCustomExecutableInstance(
173+
fileName: string,
174+
customExecutable: string,
175+
prettierPath: string | undefined,
176+
): Promise<PrettierInstance | undefined> {
177+
// Determine the prettier path for the custom executable
178+
let resolvedPrettierPath: string;
179+
180+
if (prettierPath) {
181+
// Use the explicitly provided prettierPath
182+
const absolutePath = path.isAbsolute(prettierPath)
183+
? prettierPath
184+
: path.join(
185+
workspace.getWorkspaceFolder(Uri.file(fileName))?.uri.fsPath ?? "",
186+
prettierPath,
187+
);
188+
resolvedPrettierPath = absolutePath;
189+
} else {
190+
// Try to find prettier module automatically
191+
const foundPath = await this.findPrettierModule(fileName);
192+
if (foundPath) {
193+
resolvedPrettierPath = foundPath;
194+
} else {
195+
// Fall back to just "prettier" and let the custom executable resolve it
196+
resolvedPrettierPath = "prettier";
197+
}
198+
}
199+
200+
const cacheKey = `custom:${customExecutable}:${resolvedPrettierPath}`;
201+
202+
// Check cache
203+
let prettierInstance = this.path2Module.get(cacheKey);
204+
if (prettierInstance) {
205+
return prettierInstance;
206+
}
207+
208+
// Create new instance using PrettierExecutableInstance
209+
prettierInstance = new PrettierExecutableInstance(
210+
customExecutable,
211+
resolvedPrettierPath,
212+
);
213+
214+
// Import/validate the executable
215+
try {
216+
await prettierInstance.import();
217+
this.loggingService.logInfo(
218+
`Using custom executable: ${customExecutable} with prettier at ${resolvedPrettierPath}`,
219+
);
220+
} catch (error) {
221+
this.loggingService.logError(
222+
`Failed to initialize custom executable: ${customExecutable}`,
223+
error,
224+
);
225+
return undefined;
226+
}
227+
228+
this.path2Module.set(cacheKey, prettierInstance);
229+
230+
return prettierInstance;
231+
}
232+
163233
private async getModuleFromPrettierPath(
164234
fileName: string,
165235
prettierPath: string,

0 commit comments

Comments
 (0)