-
-
Notifications
You must be signed in to change notification settings - Fork 435
/
Copy pathclang-formatter.ts
163 lines (148 loc) · 4.78 KB
/
clang-formatter.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import * as os from 'os';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { MaybePromise } from '@theia/core/lib/common/types';
import { FileUri } from '@theia/core/lib/node/file-uri';
import { inject, injectable } from '@theia/core/shared/inversify';
import { constants, promises as fs } from 'fs';
import { join } from 'path';
import { ConfigService } from '../common/protocol';
import { Formatter, FormatterOptions } from '../common/protocol/formatter';
import { getExecPath, spawnCommand } from './exec-util';
@injectable()
export class ClangFormatter implements Formatter {
@inject(ConfigService)
private readonly configService: ConfigService;
@inject(EnvVariablesServer)
private readonly envVariableServer: EnvVariablesServer;
async format({
content,
formatterConfigFolderUris,
options,
}: {
content: string;
formatterConfigFolderUris: string[];
options?: FormatterOptions;
}): Promise<string> {
const [execPath, style] = await Promise.all([
this.execPath(),
this.style(formatterConfigFolderUris, options),
]);
const formatted = await spawnCommand(
`"${execPath}"`,
[style],
console.error,
content
);
return formatted;
}
private _execPath: string | undefined;
private async execPath(): Promise<string> {
if (this._execPath) {
return this._execPath;
}
this._execPath = await getExecPath('clang-format');
return this._execPath;
}
/**
* Calculates the `-style` flag for the formatter. Uses a `.clang-format` file if exists.
* Otherwise, falls back to the default config.
*
* Style precedence:
* 1. in the sketch folder,
* 1. `~/.arduinoIDE/.clang-format`,
* 1. `directories#data/.clang-format`, and
* 1. default style flag as a string.
*
* See: https://github.com/arduino/arduino-ide/issues/566
*/
private async style(
formatterConfigFolderUris: string[],
options?: FormatterOptions
): Promise<string> {
const clangFormatPaths = await Promise.all([
...formatterConfigFolderUris.map((uri) => this.clangConfigPath(uri)),
this.clangConfigPath(this.configDirPath()),
this.clangConfigPath(this.dataDirPath()),
]);
const first = clangFormatPaths.filter(Boolean).shift();
if (first) {
console.debug(
`Using ${ClangFormatFile} style configuration from '${first}'.`
);
return `-style=file:"${first}"`;
}
return `-style="${style(toClangOptions(options))}"`;
}
private async dataDirPath(): Promise<string | undefined> {
const { config } = await this.configService.getConfiguration();
if (!config?.dataDirUri) {
return undefined;
}
return FileUri.fsPath(config.dataDirUri);
}
private async configDirPath(): Promise<string> {
const configDirUri = await this.envVariableServer.getConfigDirUri();
return FileUri.fsPath(configDirUri);
}
private async clangConfigPath(
folderUri: MaybePromise<string | undefined>
): Promise<string | undefined> {
const uri = await folderUri;
if (!uri) {
return undefined;
}
const folderPath = FileUri.fsPath(uri);
const clangFormatPath = join(folderPath, ClangFormatFile);
try {
await fs.access(clangFormatPath, constants.R_OK);
return clangFormatPath;
} catch {
return undefined;
}
}
}
interface ClangFormatOptions {
readonly UseTab: 'Never' | 'ForIndentation';
readonly TabWidth: number;
}
const ClangFormatFile = '.clang-format';
function toClangOptions(
options?: FormatterOptions | undefined
): ClangFormatOptions {
if (!!options) {
return {
UseTab: options.insertSpaces ? 'Never' : 'ForIndentation',
TabWidth: options.tabSize,
};
}
return { UseTab: 'Never', TabWidth: 2 };
}
export function style({ TabWidth, UseTab }: ClangFormatOptions): string {
let styleArgument = JSON.stringify(styleJson({ TabWidth, UseTab })).replace(
/[\\"]/g,
'\\$&'
);
if (os.platform() === 'win32') {
// Windows command interpreter does not use backslash escapes. This causes the argument to have alternate quoted and
// unquoted sections.
// Special characters in the unquoted sections must be caret escaped.
const styleArgumentSplit = styleArgument.split('"');
for (let i = 1; i < styleArgumentSplit.length; i += 2) {
styleArgumentSplit[i] = styleArgumentSplit[i].replace(/[<>^|]/g, '^$&');
}
styleArgument = styleArgumentSplit.join('"');
}
return styleArgument;
}
function styleJson({
TabWidth,
UseTab,
}: ClangFormatOptions): Record<string, unknown> {
// Source: https://github.com/arduino/tooling-project-assets/tree/main/other/clang-format-configuration
const defaultConfig = require('../../src/node/default-formatter-config.json');
return {
...defaultConfig,
TabWidth,
UseTab,
};
}