Skip to content

Commit ea7c4fe

Browse files
committed
feat: add scope completion in VSCode extension
1 parent b00daa3 commit ea7c4fe

File tree

12 files changed

+413
-15
lines changed

12 files changed

+413
-15
lines changed

.changeset/eleven-laws-care.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"google-workspace-developer-tools": minor
3+
---
4+
5+
Implement code completion for OAuth2 scopes.

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
- run: pnpm build
3838
- run: pnpm lint
3939
- run: pnpm check
40-
- run: pnpm test
40+
- run: xvfb-run -a pnpm test
4141
- run: pnpm ci:package
4242
- run: |
4343
git config --global user.name "googleworkspace-bot"

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@ jobs:
3131
- run: pnpm build
3232
- run: pnpm lint
3333
- run: pnpm check
34-
- run: pnpm test
34+
- run: xvfb-run -a pnpm test
3535
- run: pnpm ci:package

packages/vscode-extension/.vscode-test.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,6 @@ import { defineConfig } from "@vscode/test-cli";
1818

1919
export default defineConfig({
2020
files: "out/test/**/*.test.cjs",
21+
launchArgs: ["--user-data-dir=/tmp/vscode-test-user-data"],
22+
version: "insiders",
2123
});

packages/vscode-extension/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ Automatically validate and document Google Workspace OAuth2 scopes in your code:
1717
- **Hover Documentation**: See scope descriptions, associated APIs, and documentation links on hover
1818
- **Multi-API Support**: Coverage for all Google Workspace APIs (Gmail, Drive, Calendar, Chat, Admin, and more)
1919

20-
![OAuth2 Scope Linting](https://raw.githubusercontent.com/googleworkspace/vscode-extension/main/packages/vscode-extension/assets/scope-diagnostics.png)
20+
Get code completions for all Google OAuth2 scopes:
21+
22+
![OAuth2 Scope Linting & Completions](https://raw.githubusercontent.com/googleworkspace/vscode-extension/main/packages/vscode-extension/assets/scope-completion.gif)
2123

2224
**Scope Classifications:**
2325

717 KB
Loading

packages/vscode-extension/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "google-workspace-developer-tools",
33
"displayName": "Google Workspace Developer Tools",
44
"description": "Lint Google Workspace OAuth2 scopes and more.",
5-
"version": "0.5.4",
5+
"version": "0.5.6",
66
"publisher": "google-workspace",
77
"license": "Apache-2.0",
88
"private": true,
@@ -54,13 +54,13 @@
5454
"scripts": {
5555
"build": "tsup",
5656
"check": "tsc --noEmit",
57-
"ci:package": "vsce package",
57+
"ci:package": "vsce package --no-dependencies",
5858
"ci:publish": "ovsx publish --skip-duplicate && vsce publish --skip-duplicate",
5959
"ci:version": "changeset version && pnpm i --lockfile-only --engine-strict=false",
6060
"ci:update": "tsx scripts/fetch-apis.ts",
6161
"dev": "tsup --watch",
6262
"pretest": "pnpm build",
63-
"test": "xvfb-run -a vscode-test",
63+
"test": "vscode-test",
6464
"vscode:prepublish": "tsup"
6565
},
6666
"devDependencies": {
@@ -76,5 +76,8 @@
7676
"tsup": "catalog:",
7777
"tsx": "catalog:",
7878
"typescript": "catalog:"
79+
},
80+
"dependencies": {
81+
"vitest": "^4.0.4"
7982
}
8083
}

packages/vscode-extension/src/extension.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,35 @@
1717
import * as vscode from "vscode";
1818
import { SCOPES, ScopeClassification, getScopeMarkdown } from "./scopes.js";
1919

20+
export function scopeCompletion(
21+
document: vscode.TextDocument,
22+
position: vscode.Position,
23+
): vscode.CompletionItem[] {
24+
const completionItems: vscode.CompletionItem[] = [];
25+
26+
const lineText = document.lineAt(position.line).text;
27+
const textBeforeCursor = lineText.substring(0, position.character);
28+
29+
const match = textBeforeCursor.match(
30+
/(https:\/\/www\.googleapis\.com\/auth\/[a-zA-Z._-]*)$/,
31+
);
32+
33+
if (!match) {
34+
return completionItems;
35+
}
36+
37+
for (const [scope] of SCOPES.entries()) {
38+
if (scope.startsWith(match[1])) {
39+
const item = new vscode.CompletionItem(scope.split("/").pop() ?? scope);
40+
item.insertText = scope.replace(match[1], "");
41+
completionItems.push(item);
42+
}
43+
}
44+
return completionItems.sort((a, b) =>
45+
String(a.label).localeCompare(String(b.label)),
46+
);
47+
}
48+
2049
export function activate(context: vscode.ExtensionContext) {
2150
if (vscode.lm.registerMcpServerDefinitionProvider) {
2251
context.subscriptions.push(
@@ -77,6 +106,20 @@ export function activate(context: vscode.ExtensionContext) {
77106
vscode.languages.createDiagnosticCollection("scopes");
78107
context.subscriptions.push(scopeDiagnostics);
79108

109+
const scopeCompletionProvider =
110+
vscode.languages.registerCompletionItemProvider(
111+
{ scheme: "file" },
112+
{
113+
provideCompletionItems(document, position) {
114+
console.log(position);
115+
return scopeCompletion(document, position);
116+
},
117+
},
118+
"/",
119+
".",
120+
);
121+
context.subscriptions.push(scopeCompletionProvider);
122+
80123
function updateDiagnostics(
81124
document: vscode.TextDocument,
82125
collection: vscode.DiagnosticCollection,

packages/vscode-extension/src/scopes.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,6 @@ export const SCOPES = new Map<string, Scope>();
4343

4444
for (const { title, version, documentationLink, scopes } of GOOGLE_APIS || []) {
4545
for (const { id, description } of scopes || []) {
46-
console.log(
47-
`Processing scope: ${id} - ${description} (${title} v${version})`,
48-
);
49-
5046
if (!SCOPES.has(id)) {
5147
SCOPES.set(id, {
5248
description: description,
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as assert from "node:assert";
18+
import * as vscode from "vscode";
19+
import { scopeCompletion } from "../extension.js";
20+
21+
suite("Completion Provider", () => {
22+
test("it should provide completion for scopes", async () => {
23+
const doc = await vscode.workspace.openTextDocument({
24+
language: "javascript",
25+
content: "const scope = 'https://www.googleapis.com/auth/drive.f",
26+
});
27+
28+
scopeCompletion(doc, doc.lineAt(0).range.end);
29+
30+
const completionList = scopeCompletion(doc, doc.lineAt(0).range.end);
31+
32+
assert.ok(completionList.length > 0, "Completion list should not be empty");
33+
assert.equal(completionList[0].label, "drive.file");
34+
assert.equal(completionList[0].insertText, "ile");
35+
});
36+
});

0 commit comments

Comments
 (0)