Skip to content

Commit 4f12bc1

Browse files
committed
added support for buildifier auto-formating on save
1 parent 5e22ac9 commit 4f12bc1

File tree

8 files changed

+1147
-8
lines changed

8 files changed

+1147
-8
lines changed

package.json

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,46 @@
5757
{
5858
"id": "bazelproject",
5959
"aliases": [
60-
"bazelproject",
6160
"bazelproject"
6261
],
6362
"extensions": [
6463
".bazelproject"
6564
],
6665
"configuration": "./syntaxes/bazelproject-language-configuration.json"
66+
},
67+
{
68+
"id": "starlark",
69+
"aliases": [
70+
"Starlark",
71+
"starlark",
72+
"Bazel"
73+
],
74+
"extensions": [
75+
".BUILD",
76+
".WORKSPACE",
77+
".bazel",
78+
".bzl",
79+
".bzlmod",
80+
".sky",
81+
".star"
82+
],
83+
"filenames": [
84+
"BUILD",
85+
"WORKSPACE"
86+
],
87+
"configuration": "./syntaxes/starlark-language-configuration.json"
6788
}
6889
],
6990
"grammars": [
7091
{
7192
"language": "bazelproject",
7293
"scopeName": "source.bazelproject",
7394
"path": "./syntaxes/bazelproject.tmLanguage.json"
95+
},
96+
{
97+
"language": "starlark",
98+
"scopeName": "source.starlark",
99+
"path": "./syntaxes/starlark.tmLanguage.json"
74100
}
75101
],
76102
"taskDefinitions": [
@@ -131,6 +157,18 @@
131157
"default": true,
132158
"description": "Display 'sync project view' notification info window on .bazelproject edit",
133159
"scope": "window"
160+
},
161+
"bazel.buildifier.enable": {
162+
"type": "boolean",
163+
"default": true,
164+
"description": "Enable buildifier formatting tool on save",
165+
"scope": "window"
166+
},
167+
"bazel.buildifier.binary": {
168+
"type": ["string", "null"],
169+
"default": null,
170+
"description": "path to buildifier binary. If not set buildifier from your PATH will be used",
171+
"scope": "window"
134172
}
135173
}
136174
},
@@ -270,7 +308,7 @@
270308
"esbuild:watch": "npm run esbuild:base -- --sourcemap --watch",
271309
"analyze": "npm run esbuild:base -- --minify --metafile --analyze && esbuild-visualizer --metadata ./meta.json --open",
272310
"lint": "eslint src --ext ts",
273-
"lint:fix" : "eslint src --ext ts --fix",
311+
"lint:fix": "eslint src --ext ts --fix",
274312
"test": "run-s clean test:*",
275313
"test:compile": "tsc -b ./test/tsconfig.json",
276314
"test:lint": "eslint --ext .js,.ts,.tsx src",

src/buildifier.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { exec } from 'child_process';
2+
import {
3+
languages,
4+
Range,
5+
TextDocument,
6+
TextEdit,
7+
window,
8+
workspace,
9+
} from 'vscode';
10+
import { getWorkspaceRoot } from './util';
11+
12+
export function registerBuildifierFormatter() {
13+
languages.registerDocumentFormattingEditProvider(
14+
{ scheme: 'file', language: 'starlark' },
15+
{
16+
async provideDocumentFormattingEdits(
17+
document: TextDocument
18+
): Promise<TextEdit[]> {
19+
if (
20+
workspace.getConfiguration('bazel.buildifier').get('enable', false)
21+
) {
22+
try {
23+
if (await buildifierExists()) {
24+
const updatedContent = await runBuildifier(document.fileName);
25+
26+
// only return an edit if there is a value in `updatedContent`
27+
return !!updatedContent
28+
? [
29+
TextEdit.replace(
30+
new Range(
31+
0,
32+
0,
33+
document.lineCount - 1,
34+
document.lineAt(
35+
document.lineCount - 1
36+
).rangeIncludingLineBreak.end.character
37+
),
38+
updatedContent
39+
),
40+
]
41+
: [];
42+
}
43+
} catch (err) {
44+
window.showErrorMessage(`${err}`);
45+
return [];
46+
}
47+
}
48+
49+
return [];
50+
},
51+
}
52+
);
53+
}
54+
55+
function buildifierExists(): Promise<boolean> {
56+
return new Promise((resolve, reject) => {
57+
exec(
58+
`${getBuildifierCmd()} -version`,
59+
{ cwd: getWorkspaceRoot() },
60+
(err, stdout, stderr) => {
61+
if (err) {
62+
return reject(err);
63+
}
64+
return resolve(!stderr);
65+
}
66+
);
67+
});
68+
}
69+
70+
/**
71+
* Utility function used to fetch the formatted text from the `buildifier`
72+
* cmd. Uses `exec` since we want to get all of the cmd response in a single
73+
* text blob
74+
* @param bazelFile
75+
* @returns
76+
*/
77+
function runBuildifier(bazelFile: string): Promise<string> {
78+
return new Promise((resolve, reject) => {
79+
exec(
80+
`${getBuildifierCmd()} -mode print_if_changed ${workspace.asRelativePath(bazelFile)}`,
81+
{
82+
cwd: getWorkspaceRoot(),
83+
},
84+
(err, stdout, stderr) => {
85+
if (err) {
86+
console.error(stderr);
87+
return reject(err);
88+
}
89+
return resolve(stdout);
90+
}
91+
);
92+
});
93+
}
94+
95+
function getBuildifierCmd(): string {
96+
return workspace.getConfiguration('bazel.buildifier').get('binary')
97+
? workspace.getConfiguration('bazel.buildifier').get('binary', 'buildifier')
98+
: 'buildifier';
99+
}

src/extension.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
getBazelTerminal,
1616
} from './bazelLangaugeServerTerminal';
1717
import { BazelTaskManager } from './bazelTaskManager';
18+
import { registerBuildifierFormatter } from './buildifier';
1819
import { Commands, executeJavaLanguageServerCommand } from './commands';
1920
import { registerLSClient } from './loggingTCPServer';
2021
import { ProjectViewManager } from './projectViewManager';
@@ -27,7 +28,6 @@ import {
2728
} from './util';
2829

2930
const workspaceRoot = getWorkspaceRoot();
30-
const workspaceRootName = workspaceRoot.split('/').reverse()[0];
3131

3232
export async function activate(context: ExtensionContext) {
3333
// activates
@@ -124,6 +124,8 @@ export async function activate(context: ExtensionContext) {
124124
)
125125
);
126126

127+
registerBuildifierFormatter();
128+
127129
// trigger a refresh of the tree view when any task get executed
128130
tasks.onDidStartTask((_) => BazelRunTargetProvider.instance.refresh());
129131
tasks.onDidEndTask((_) => BazelRunTargetProvider.instance.refresh());
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"comments": {
3+
"lineComment": "#"
4+
},
5+
"brackets": [
6+
["{", "}"],
7+
["[", "]"],
8+
["(", ")"]
9+
],
10+
"autoClosingPairs": [
11+
["{", "}"],
12+
["[", "]"],
13+
["(", ")"],
14+
{
15+
"open": "\"",
16+
"close": "\"",
17+
"notIn": ["string", "comment"]
18+
},
19+
{
20+
"open": "'",
21+
"close": "'",
22+
"notIn": ["string", "comment"]
23+
}
24+
],
25+
"surroundingPairs": [
26+
["{", "}"],
27+
["[", "]"],
28+
["(", ")"],
29+
["\"", "\""],
30+
["'", "'"]
31+
]
32+
}

0 commit comments

Comments
 (0)