Skip to content

Commit cb63cb3

Browse files
committed
Add support for shfmt printer flags
Add support for the following `shfmt` printer flags to control formatting: * `-bn`, `--binary-next-line` * `-ci`, `--case-indent` * `-fn`, `--func-next-line` * `-sr`, `--space-redirects` The `-i`, `--indent` flag is set appropriately based upon editor formatting options. Support for `-kp`, `--keep-padding` was not added as it has been deprecated and will be removed in the next major release of `shfmt`.
1 parent 5bf1795 commit cb63cb3

File tree

3 files changed

+250
-4
lines changed

3 files changed

+250
-4
lines changed

server/src/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,7 @@ export default class BashServer {
833833
return null
834834
}
835835

836-
return await this.formatter.format(document, params.options)
836+
return await this.formatter.format(document, params.options, this.config.shfmt)
837837
} catch (err) {
838838
logger.error(`Error while formatting: ${err}`)
839839
}

server/src/shfmt/__tests__/index.test.ts

Lines changed: 240 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,17 @@ async function getFormattingResult({
1919
document,
2020
executablePath = 'shfmt',
2121
formatOptions,
22+
shfmtConfig,
2223
}: {
2324
document: TextDocument
2425
executablePath?: string
2526
formatOptions?: FormattingOptions
27+
shfmtConfig?: Record<string, string | boolean>
2628
}): Promise<[Awaited<ReturnType<Formatter['format']>>, Formatter]> {
2729
const formatter = new Formatter({
2830
executablePath,
2931
})
30-
const result = await formatter.format(document, formatOptions)
32+
const result = await formatter.format(document, formatOptions, shfmtConfig)
3133
return [result, formatter]
3234
}
3335

@@ -183,4 +185,241 @@ describe('formatter', () => {
183185
]
184186
`)
185187
})
188+
189+
it('should format with operators at the start of the line when binaryNextLine is true', async () => {
190+
const [result] = await getFormattingResult({
191+
document: FIXTURE_DOCUMENT.SHFMT,
192+
formatOptions: { tabSize: 2, insertSpaces: true },
193+
shfmtConfig: { binaryNextLine: true },
194+
})
195+
expect(result).toMatchInlineSnapshot(`
196+
[
197+
{
198+
"newText": "#!/bin/bash
199+
set -ueo pipefail
200+
201+
if [ -z "$arg" ]; then
202+
echo indent
203+
fi
204+
205+
echo binary \\
206+
&& echo next line
207+
208+
case "$arg" in
209+
a)
210+
echo case indent
211+
;;
212+
esac
213+
214+
echo space redirects >/dev/null
215+
216+
function next() {
217+
echo line
218+
}
219+
",
220+
"range": {
221+
"end": {
222+
"character": 2147483647,
223+
"line": 2147483647,
224+
},
225+
"start": {
226+
"character": 0,
227+
"line": 0,
228+
},
229+
},
230+
},
231+
]
232+
`)
233+
})
234+
235+
it('should format with case patterns indented when caseIndent is true', async () => {
236+
const [result] = await getFormattingResult({
237+
document: FIXTURE_DOCUMENT.SHFMT,
238+
formatOptions: { tabSize: 2, insertSpaces: true },
239+
shfmtConfig: { caseIndent: true },
240+
})
241+
expect(result).toMatchInlineSnapshot(`
242+
[
243+
{
244+
"newText": "#!/bin/bash
245+
set -ueo pipefail
246+
247+
if [ -z "$arg" ]; then
248+
echo indent
249+
fi
250+
251+
echo binary &&
252+
echo next line
253+
254+
case "$arg" in
255+
a)
256+
echo case indent
257+
;;
258+
esac
259+
260+
echo space redirects >/dev/null
261+
262+
function next() {
263+
echo line
264+
}
265+
",
266+
"range": {
267+
"end": {
268+
"character": 2147483647,
269+
"line": 2147483647,
270+
},
271+
"start": {
272+
"character": 0,
273+
"line": 0,
274+
},
275+
},
276+
},
277+
]
278+
`)
279+
})
280+
281+
it('should format with function opening braces on a separate line when funcNextLine is true', async () => {
282+
const [result] = await getFormattingResult({
283+
document: FIXTURE_DOCUMENT.SHFMT,
284+
formatOptions: { tabSize: 2, insertSpaces: true },
285+
shfmtConfig: { funcNextLine: true },
286+
})
287+
expect(result).toMatchInlineSnapshot(`
288+
[
289+
{
290+
"newText": "#!/bin/bash
291+
set -ueo pipefail
292+
293+
if [ -z "$arg" ]; then
294+
echo indent
295+
fi
296+
297+
echo binary &&
298+
echo next line
299+
300+
case "$arg" in
301+
a)
302+
echo case indent
303+
;;
304+
esac
305+
306+
echo space redirects >/dev/null
307+
308+
function next()
309+
{
310+
echo line
311+
}
312+
",
313+
"range": {
314+
"end": {
315+
"character": 2147483647,
316+
"line": 2147483647,
317+
},
318+
"start": {
319+
"character": 0,
320+
"line": 0,
321+
},
322+
},
323+
},
324+
]
325+
`)
326+
})
327+
328+
it('should format with redirect operators followed by a space when spaceRedirects is true', async () => {
329+
const [result] = await getFormattingResult({
330+
document: FIXTURE_DOCUMENT.SHFMT,
331+
formatOptions: { tabSize: 2, insertSpaces: true },
332+
shfmtConfig: { spaceRedirects: true },
333+
})
334+
expect(result).toMatchInlineSnapshot(`
335+
[
336+
{
337+
"newText": "#!/bin/bash
338+
set -ueo pipefail
339+
340+
if [ -z "$arg" ]; then
341+
echo indent
342+
fi
343+
344+
echo binary &&
345+
echo next line
346+
347+
case "$arg" in
348+
a)
349+
echo case indent
350+
;;
351+
esac
352+
353+
echo space redirects > /dev/null
354+
355+
function next() {
356+
echo line
357+
}
358+
",
359+
"range": {
360+
"end": {
361+
"character": 2147483647,
362+
"line": 2147483647,
363+
},
364+
"start": {
365+
"character": 0,
366+
"line": 0,
367+
},
368+
},
369+
},
370+
]
371+
`)
372+
})
373+
374+
it('should format with all options enabled when multiple config settings are combined', async () => {
375+
const [result] = await getFormattingResult({
376+
document: FIXTURE_DOCUMENT.SHFMT,
377+
formatOptions: { tabSize: 2, insertSpaces: true },
378+
shfmtConfig: {
379+
binaryNextLine: true,
380+
caseIndent: true,
381+
funcNextLine: true,
382+
spaceRedirects: true,
383+
},
384+
})
385+
expect(result).toMatchInlineSnapshot(`
386+
[
387+
{
388+
"newText": "#!/bin/bash
389+
set -ueo pipefail
390+
391+
if [ -z "$arg" ]; then
392+
echo indent
393+
fi
394+
395+
echo binary \\
396+
&& echo next line
397+
398+
case "$arg" in
399+
a)
400+
echo case indent
401+
;;
402+
esac
403+
404+
echo space redirects > /dev/null
405+
406+
function next()
407+
{
408+
echo line
409+
}
410+
",
411+
"range": {
412+
"end": {
413+
"character": 2147483647,
414+
"line": 2147483647,
415+
},
416+
"start": {
417+
"character": 0,
418+
"line": 0,
419+
},
420+
},
421+
},
422+
]
423+
`)
424+
})
186425
})

server/src/shfmt/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,23 @@ export class Formatter {
2727
public async format(
2828
document: TextDocument,
2929
formatOptions?: LSP.FormattingOptions | null,
30+
shfmtConfig?: Record<string, string | boolean> | null,
3031
): Promise<TextEdit[]> {
3132
if (!this._canFormat) {
3233
return []
3334
}
3435

35-
return this.executeFormat(document, formatOptions)
36+
return this.executeFormat(document, formatOptions, shfmtConfig)
3637
}
3738

3839
private async executeFormat(
3940
document: TextDocument,
4041
formatOptions?: LSP.FormattingOptions | null,
42+
shfmtConfig?: Record<string, string | boolean> | null,
4143
): Promise<TextEdit[]> {
4244
const documentText = document.getText()
4345

44-
const result = await this.runShfmt(documentText, formatOptions)
46+
const result = await this.runShfmt(documentText, formatOptions, shfmtConfig)
4547

4648
if (!this._canFormat) {
4749
return []
@@ -61,9 +63,14 @@ export class Formatter {
6163
private async runShfmt(
6264
documentText: string,
6365
formatOptions?: LSP.FormattingOptions | null,
66+
shfmtConfig?: Record<string, string | boolean> | null,
6467
): Promise<string> {
6568
const indentation: number = formatOptions?.insertSpaces ? formatOptions.tabSize : 0
6669
const args: string[] = [`--indent=${indentation}`]
70+
if (shfmtConfig?.binaryNextLine) args.push('--binary-next-line')
71+
if (shfmtConfig?.caseIndent) args.push('--case-indent')
72+
if (shfmtConfig?.funcNextLine) args.push('--func-next-line')
73+
if (shfmtConfig?.spaceRedirects) args.push('--space-redirects')
6774

6875
logger.debug(`Shfmt: running "${this.executablePath} ${args.join(' ')}"`)
6976

0 commit comments

Comments
 (0)