Skip to content

Commit aef7a0a

Browse files
Handle cases when a project doesn't use codeownership (#19)
* Also Fix 'Maximum call stack size exceeded' during `run` function * show when a workspace not configured vs no owner * run prettier
1 parent e355bc2 commit aef7a0a

File tree

2 files changed

+128
-74
lines changed

2 files changed

+128
-74
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ View code ownership for every file right in the status bar. You'll get the name
1111
Quick access to the owning team's config file. Clicking on the status bar item will open a popup that includes a button that opens the team's config file. See [Code Teams](https://github.com/rubyatscale/code_teams) for more information on team config files.
1212

1313
## Installation
14+
1415
[Install from Marketplace](https://marketplace.visualstudio.com/items?itemName=Gusto.code-ownership-vscode)
1516

1617
## Requirements

src/extension.ts

Lines changed: 127 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { relative, resolve, sep } from 'path';
22
import * as cp from 'child_process';
33
import { readFile } from 'fs/promises';
4+
import { existsSync } from 'fs';
45

56
import * as yaml from 'js-yaml';
67
import * as vscode from 'vscode';
@@ -9,7 +10,8 @@ let channel: vscode.OutputChannel;
910

1011
function run(file: string | vscode.Uri | null | undefined) {
1112
if (!file) {
12-
run(vscode.window.activeTextEditor?.document.uri);
13+
if (!vscode.window.activeTextEditor) return;
14+
run(vscode.window.activeTextEditor.document.uri);
1315
return;
1416
}
1517

@@ -200,6 +202,7 @@ class StatusProvider implements vscode.Disposable {
200202

201203
private _status: Status = 'idle';
202204
private _owner: Owner | undefined = undefined;
205+
private _isConfigured: boolean | null = null;
203206

204207
get status(): Status {
205208
return this._status;
@@ -217,6 +220,14 @@ class StatusProvider implements vscode.Disposable {
217220
this.update();
218221
}
219222

223+
get isConfigured(): boolean | null {
224+
return this._isConfigured;
225+
}
226+
set isConfigured(value: boolean | null) {
227+
this._isConfigured = value;
228+
this.update();
229+
}
230+
220231
private update() {
221232
if (this.status === 'error') {
222233
this.statusBarItem.command = 'code-ownership-vscode.showOutputChannel';
@@ -237,9 +248,14 @@ class StatusProvider implements vscode.Disposable {
237248
this.statusBarItem.text = `$(account) Owner: ${this.owner.teamName}`;
238249
this.statusBarItem.tooltip = undefined;
239250
this.statusBarItem.show();
251+
} else if (this.isConfigured === false) {
252+
this.statusBarItem.text = `$(info) Ownership: not configured`;
253+
this.statusBarItem.tooltip =
254+
'This workspace is not configured for code ownership';
255+
this.statusBarItem.show();
240256
} else {
241257
this.statusBarItem.text = `$(warning) Owner: none`;
242-
this.statusBarItem.tooltip = undefined;
258+
this.statusBarItem.tooltip = 'This file has no assigned team ownership';
243259
this.statusBarItem.show();
244260
}
245261
}
@@ -251,92 +267,129 @@ class StatusProvider implements vscode.Disposable {
251267
}
252268

253269
class Worker implements vscode.Disposable {
270+
private isConfigured: boolean | null = null;
271+
254272
constructor(
255273
private readonly workspace: vscode.WorkspaceFolder,
256274
private readonly statusProvider: StatusProvider,
257-
) {}
275+
) {
276+
this.checkConfiguration();
277+
}
278+
279+
private async checkConfiguration(): Promise<void> {
280+
const binaryPath = resolve(this.workspace.uri.fsPath, 'bin/codeownership');
281+
this.isConfigured = existsSync(binaryPath);
282+
this.statusProvider.isConfigured = this.isConfigured;
283+
284+
if (!this.isConfigured) {
285+
log(
286+
'info',
287+
`No code ownership binary found in workspace: ${this.workspace.name}`,
288+
);
289+
} else {
290+
log(
291+
'info',
292+
`Code ownership binary found in workspace: ${this.workspace.name}`,
293+
);
294+
}
295+
}
258296

259297
workspaceHas(file: vscode.Uri): boolean {
260298
return file.fsPath.startsWith(this.workspace.uri.fsPath);
261299
}
262300

263301
async run(file: vscode.Uri): Promise<void> {
264-
if (this.workspaceHas(file)) {
265-
this.statusProvider.status = 'working';
266-
267-
await new Promise((r) => setTimeout(r, 50));
268-
269-
// bin/codeownership currenlty wants relative paths
270-
const cwd = this.workspace.uri.fsPath;
271-
const relativePath = relative(cwd, file.fsPath);
272-
273-
logSpace();
274-
log('debug', `cwd: ${cwd}`);
275-
log('debug', `workspace: ${this.workspace.uri.fsPath}`);
276-
log('debug', `file path: ${file.fsPath}`);
277-
log('debug', `relative path: ${relativePath}`);
278-
279-
try {
280-
const output = await runCommand(
281-
cwd,
282-
`bin/codeownership for_file "${relativePath}" --json`,
283-
this.statusProvider,
284-
);
302+
if (!this.workspaceHas(file)) return;
285303

286-
const obj = JSON.parse(output);
304+
if (this.isConfigured === null) {
305+
await this.checkConfiguration();
306+
}
287307

288-
if (typeof obj.team_name !== 'string') {
289-
log(
290-
'warning',
291-
'Missing expected property `team_name` in command output',
292-
);
293-
}
294-
if (typeof obj.team_yml !== 'string') {
295-
log(
296-
'warning',
297-
'Missing expected property `team_yml` in command output',
298-
);
299-
}
308+
if (!this.isConfigured) {
309+
this.statusProvider.owner = undefined;
310+
this.statusProvider.status = 'idle';
311+
return;
312+
}
300313

301-
if (
302-
typeof obj.team_name === 'string' &&
303-
typeof obj.team_yml === 'string' &&
304-
obj.team_name !== 'Unowned'
305-
) {
306-
const teamConfig = resolve(this.workspace.uri.fsPath, obj.team_yml);
307-
308-
const actions: UserAction[] = [];
309-
310-
const slackChannel = await getSlackChannel(teamConfig);
311-
312-
if (slackChannel) {
313-
actions.push({
314-
title: `Slack: #${slackChannel}`,
315-
uri: vscode.Uri.parse(
316-
`https://slack.com/app_redirect?channel=${slackChannel}`,
317-
),
318-
});
319-
}
320-
321-
actions.push({
322-
title: 'View team config',
323-
uri: vscode.Uri.parse(teamConfig),
324-
});
325-
326-
this.statusProvider.owner = {
327-
filepath: file.fsPath,
328-
teamName: obj.team_name,
329-
teamConfig,
330-
actions,
331-
};
332-
} else {
333-
this.statusProvider.owner = undefined;
334-
}
314+
this.statusProvider.status = 'working';
315+
await new Promise((r) => setTimeout(r, 50));
316+
317+
const cwd = this.workspace.uri.fsPath;
318+
const relativePath = relative(cwd, file.fsPath);
319+
320+
logSpace();
321+
log('info', `Checking ownership for ${relativePath}`);
322+
log('debug', `cwd: ${cwd}`);
323+
log('debug', `workspace: ${this.workspace.uri.fsPath}`);
324+
log('debug', `file path: ${file.fsPath}`);
325+
326+
// Run ownership check
327+
const output = await runCommand(
328+
cwd,
329+
`bin/codeownership for_file "${relativePath}" --json`,
330+
this.statusProvider,
331+
);
332+
333+
if (!output) {
334+
log('info', 'Code ownership check returned no output');
335+
this.statusProvider.owner = undefined;
336+
this.statusProvider.status = 'idle';
337+
return;
338+
}
339+
340+
try {
341+
const obj = JSON.parse(output);
342+
343+
if (!obj.team_name) {
344+
log('info', 'No team name found in ownership data');
345+
this.statusProvider.owner = undefined;
335346
this.statusProvider.status = 'idle';
336-
} catch {
337-
this.statusProvider.status = 'error';
338-
log('error', 'Error parsing command output');
347+
return;
339348
}
349+
350+
if (!obj.team_yml) {
351+
log('info', 'No team config file found in ownership data');
352+
this.statusProvider.owner = undefined;
353+
this.statusProvider.status = 'idle';
354+
return;
355+
}
356+
357+
if (obj.team_name === 'Unowned') {
358+
log('info', 'File is explicitly unowned');
359+
this.statusProvider.owner = undefined;
360+
this.statusProvider.status = 'idle';
361+
return;
362+
}
363+
364+
const teamConfig = resolve(this.workspace.uri.fsPath, obj.team_yml);
365+
const actions: UserAction[] = [];
366+
367+
const slackChannel = await getSlackChannel(teamConfig);
368+
if (slackChannel) {
369+
actions.push({
370+
title: `Slack: #${slackChannel}`,
371+
uri: vscode.Uri.parse(
372+
`https://slack.com/app_redirect?channel=${slackChannel}`,
373+
),
374+
});
375+
}
376+
377+
actions.push({
378+
title: 'View team config',
379+
uri: vscode.Uri.parse(teamConfig),
380+
});
381+
382+
this.statusProvider.owner = {
383+
filepath: file.fsPath,
384+
teamName: obj.team_name,
385+
teamConfig,
386+
actions,
387+
};
388+
this.statusProvider.status = 'idle';
389+
} catch (error) {
390+
log('info', `Invalid ownership data format: ${error.message}`);
391+
this.statusProvider.owner = undefined;
392+
this.statusProvider.status = 'idle';
340393
}
341394
}
342395

0 commit comments

Comments
 (0)