Skip to content

Commit 0c7dd3c

Browse files
committed
Add support for reading shfmt config options from .editorconfig
If we have any `shfmt`-specific options in `.editorconfig`, use the config in `.editorconfig` and ignore the language server config (this is similar to `shfmt`'s approach of using either `.editorconfig` or command line flags, but not both). Indentation always comes via the editor - if someone is using `.editorconfig` then the expectation is that they will have configured their editor's indentation in this way too.
1 parent e038a25 commit 0c7dd3c

File tree

3 files changed

+104
-21
lines changed

3 files changed

+104
-21
lines changed

pnpm-lock.yaml

Lines changed: 30 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"node": ">=16"
1818
},
1919
"dependencies": {
20+
"editorconfig": "2.0.0",
2021
"fast-glob": "3.3.2",
2122
"fuzzy-search": "3.2.1",
2223
"node-fetch": "2.7.0",

server/src/shfmt/index.ts

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { spawn } from 'child_process'
2+
import * as editorconfig from 'editorconfig'
23
import * as LSP from 'vscode-languageserver/node'
3-
import { TextDocument, TextEdit } from 'vscode-languageserver-textdocument'
4+
import { DocumentUri, TextDocument, TextEdit } from 'vscode-languageserver-textdocument'
45

56
import { logger } from '../util/logger'
67

@@ -58,26 +59,82 @@ export class Formatter {
5859
]
5960
}
6061

62+
private async getShfmtArguments(
63+
documentUri: DocumentUri,
64+
formatOptions?: LSP.FormattingOptions | null,
65+
lspShfmtConfig?: Record<string, string | boolean> | null,
66+
): Promise<string[]> {
67+
const args: string[] = []
68+
69+
// this is the config that we'll use to build args - default to language server config
70+
let activeShfmtConfig = { ...lspShfmtConfig }
71+
72+
// do we have a document stored on the local filesystem?
73+
const filepathMatch = documentUri.match(/^file:\/\/(.*)$/)
74+
if (filepathMatch) {
75+
const filepath = filepathMatch[1]
76+
args.push(`--filename=${filepathMatch[1]}`)
77+
78+
const editorconfigProperties = await editorconfig.parse(filepath)
79+
logger.debug(
80+
`Shfmt: found .editorconfig properties: ${JSON.stringify(
81+
editorconfigProperties,
82+
)}`,
83+
)
84+
85+
const editorconfigShfmtConfig: Record<string, any> = {}
86+
editorconfigShfmtConfig.binaryNextLine = editorconfigProperties.binary_next_line
87+
editorconfigShfmtConfig.caseIndent = editorconfigProperties.switch_case_indent
88+
editorconfigShfmtConfig.funcNextLine = editorconfigProperties.function_next_line
89+
editorconfigShfmtConfig.keepPadding = editorconfigProperties.keep_padding
90+
// --simplify is not supported via .editorconfig
91+
editorconfigShfmtConfig.spaceRedirects = editorconfigProperties.space_redirects
92+
editorconfigShfmtConfig.languageDialect = editorconfigProperties.shell_variant
93+
94+
// if we have any shfmt-specific options in .editorconfig, use the config in .editorconfig and
95+
// ignore the language server config (this is similar to shfmt's approach of using either
96+
// .editorconfig or command line flags, but not both)
97+
if (
98+
editorconfigShfmtConfig.binaryNextLine !== undefined ||
99+
editorconfigShfmtConfig.caseIndent !== undefined ||
100+
editorconfigShfmtConfig.funcNextLine !== undefined ||
101+
editorconfigShfmtConfig.keepPadding !== undefined ||
102+
editorconfigShfmtConfig.spaceRedirects !== undefined ||
103+
editorconfigShfmtConfig.languageDialect !== undefined
104+
) {
105+
logger.debug(
106+
'Shfmt: detected shfmt properties in .editorconfig - ignoring language server shfmt config',
107+
)
108+
activeShfmtConfig = { ...editorconfigShfmtConfig }
109+
} else {
110+
logger.debug(
111+
'Shfmt: no shfmt properties found in .editorconfig - using language server shfmt config',
112+
)
113+
}
114+
}
115+
116+
// indentation always comes via the editor - if someone is using .editorconfig then the
117+
// expectation is that they will have configured their editor's indentation in this way too
118+
const indentation: number = formatOptions?.insertSpaces ? formatOptions.tabSize : 0
119+
args.push(`-i=${indentation}`) // --indent
120+
121+
if (activeShfmtConfig?.binaryNextLine) args.push('-bn') // --binary-next-line
122+
if (activeShfmtConfig?.caseIndent) args.push('-ci') // --case-indent
123+
if (activeShfmtConfig?.funcNextLine) args.push('-fn') // --func-next-line
124+
if (activeShfmtConfig?.keepPadding) args.push('-kp') // --keep-padding
125+
if (activeShfmtConfig?.simplifyCode) args.push('-s') // --simplify
126+
if (activeShfmtConfig?.spaceRedirects) args.push('-sr') // --space-redirects
127+
if (activeShfmtConfig?.languageDialect) args.push(`-ln=${activeShfmtConfig.languageDialect}`) // --language-dialect
128+
129+
return args
130+
}
131+
61132
private async runShfmt(
62133
document: TextDocument,
63134
formatOptions?: LSP.FormattingOptions | null,
64135
shfmtConfig?: Record<string, string | boolean> | null,
65136
): Promise<string> {
66-
const indentation: number = formatOptions?.insertSpaces ? formatOptions.tabSize : 0
67-
const args: string[] = [`-i=${indentation}`] // --indent
68-
if (shfmtConfig?.binaryNextLine) args.push('-bn') // --binary-next-line
69-
if (shfmtConfig?.caseIndent) args.push('-ci') // --case-indent
70-
if (shfmtConfig?.funcNextLine) args.push('-fn') // --func-next-line
71-
if (shfmtConfig?.keepPadding) args.push('-kp') // --keep-padding
72-
if (shfmtConfig?.simplifyCode) args.push('-s') // --simplify
73-
if (shfmtConfig?.spaceRedirects) args.push('-sr') // --space-redirects
74-
if (shfmtConfig?.languageDialect) args.push(`-ln=${shfmtConfig.languageDialect}`) // --language-dialect
75-
76-
// If we can determine a local filename, pass that to shfmt to aid language dialect detection
77-
const filePathMatch = document.uri.match(/^file:\/\/(.*)$/)
78-
if (filePathMatch) {
79-
args.push(`--filename=${filePathMatch[1]}`)
80-
}
137+
const args = await this.getShfmtArguments(document.uri, formatOptions, shfmtConfig)
81138

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

0 commit comments

Comments
 (0)