Skip to content

Commit a8ce0f5

Browse files
authored
Notify user if configured $schema is out of date (#1736)
* Notify user if configured $schema is out of date * Remove .only * Handle github infra issues * Fix typo * Fix config name check on windows * Watch for JSON parsing failure * Throw error for all non-404 errors
1 parent b781e0e commit a8ce0f5

File tree

6 files changed

+235
-55
lines changed

6 files changed

+235
-55
lines changed

package.json

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,16 @@
674674
"default": "prompt",
675675
"markdownDescription": "Controls whether to open a swift project automatically after creating it.",
676676
"scope": "application"
677+
},
678+
"swift.lspConfigurationBranch": {
679+
"type": "string",
680+
"markdownDescription": "Set the branch to use when setting the `$schema` property of the SourceKit-LSP configuration. For example: \"release/6.1\" or \"main\". When this setting is unset, the extension will determine the branch based on the version of the toolchain that is in use."
681+
},
682+
"swift.checkLspConfigurationSchema": {
683+
"type": "boolean",
684+
"default": true,
685+
"markdownDescription": "When opening a .sourckit-lsp/config.json configuration file, whether or not to check if the $schema matches the version of Swift you are using.",
686+
"scope": "machine-overridable"
677687
}
678688
}
679689
},
@@ -749,10 +759,6 @@
749759
"order": 6,
750760
"scope": "machine-overridable"
751761
},
752-
"swift.sourcekit-lsp.configurationBranch": {
753-
"type": "string",
754-
"markdownDescription": "Set the branch to use when setting the `$schema` property of the SourceKit-LSP configuration. For example: \"release/6.1\" or \"main\". When this setting is unset, the extension will determine the branch based on the version of the toolchain that is in use."
755-
},
756762
"sourcekit-lsp.inlayHints.enabled": {
757763
"type": "boolean",
758764
"default": true,

src/WorkspaceContext.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -569,9 +569,7 @@ export class WorkspaceContext implements vscode.Disposable {
569569
* one of those. If not then it searches up the tree to find the uppermost folder in the
570570
* workspace that contains a Package.swift
571571
*/
572-
private async getPackageFolder(
573-
url: vscode.Uri
574-
): Promise<FolderContext | vscode.Uri | undefined> {
572+
async getPackageFolder(url: vscode.Uri): Promise<FolderContext | vscode.Uri | undefined> {
575573
// is editor document in any of the current FolderContexts
576574
const folder = this.folders.find(context => {
577575
return isPathInsidePath(url.fsPath, context.folder.fsPath);

src/commands/generateSourcekitConfiguration.ts

Lines changed: 116 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15-
import { join } from "path";
15+
import { basename, dirname, join } from "path";
1616
import * as vscode from "vscode";
1717
import { FolderContext } from "../FolderContext";
1818
import { selectFolder } from "../ui/SelectFolderQuickPick";
@@ -85,26 +85,24 @@ async function createSourcekitConfiguration(
8585
}
8686
await vscode.workspace.fs.createDirectory(sourcekitFolder);
8787
}
88-
const version = folderContext.toolchain.swiftVersion;
89-
const versionString = `${version.major}.${version.minor}`;
90-
let branch =
91-
configuration.lsp.configurationBranch ||
92-
(version.dev ? "main" : `release/${versionString}`);
93-
if (!(await checkURLExists(schemaURL(branch)))) {
94-
branch = "main";
95-
}
96-
await vscode.workspace.fs.writeFile(
97-
sourcekitConfigFile,
98-
Buffer.from(
99-
JSON.stringify(
100-
{
101-
$schema: schemaURL(branch),
102-
},
103-
undefined,
104-
2
88+
try {
89+
const url = await determineSchemaURL(folderContext);
90+
await vscode.workspace.fs.writeFile(
91+
sourcekitConfigFile,
92+
Buffer.from(
93+
JSON.stringify(
94+
{
95+
$schema: url,
96+
},
97+
undefined,
98+
2
99+
)
105100
)
106-
)
107-
);
101+
);
102+
} catch (e) {
103+
void vscode.window.showErrorMessage(`${e}`);
104+
return false;
105+
}
108106
return true;
109107
}
110108

@@ -114,8 +112,105 @@ const schemaURL = (branch: string) =>
114112
async function checkURLExists(url: string): Promise<boolean> {
115113
try {
116114
const response = await fetch(url, { method: "HEAD" });
117-
return response.ok;
115+
if (response.ok) {
116+
return true;
117+
} else if (response.status !== 404) {
118+
throw new Error(`Received exit code ${response.status} when trying to fetch ${url}`);
119+
}
120+
return false;
118121
} catch {
119122
return false;
120123
}
121124
}
125+
126+
export async function determineSchemaURL(folderContext: FolderContext): Promise<string> {
127+
const version = folderContext.toolchain.swiftVersion;
128+
const versionString = `${version.major}.${version.minor}`;
129+
let branch =
130+
configuration.lspConfigurationBranch || (version.dev ? "main" : `release/${versionString}`);
131+
if (!(await checkURLExists(schemaURL(branch)))) {
132+
branch = "main";
133+
}
134+
return schemaURL(branch);
135+
}
136+
137+
async function checkDocumentSchema(doc: vscode.TextDocument, workspaceContext: WorkspaceContext) {
138+
const folder = await workspaceContext.getPackageFolder(doc.uri);
139+
if (!folder) {
140+
return;
141+
}
142+
const folderContext = folder as FolderContext;
143+
if (!folderContext.name) {
144+
return; // Not a FolderContext if no "name"
145+
}
146+
let buffer: Uint8Array;
147+
try {
148+
buffer = await vscode.workspace.fs.readFile(doc.uri);
149+
} catch (error) {
150+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
151+
workspaceContext.outputChannel.appendLine(
152+
`Failed to read file at ${doc.uri.fsPath}: ${error}`
153+
);
154+
}
155+
return;
156+
}
157+
let config;
158+
try {
159+
const contents = Buffer.from(buffer).toString("utf-8");
160+
config = JSON.parse(contents);
161+
} catch (error) {
162+
workspaceContext.outputChannel.appendLine(
163+
`Failed to parse JSON from ${doc.uri.fsPath}: ${error}`
164+
);
165+
return;
166+
}
167+
const schema = config.$schema;
168+
if (!schema) {
169+
return;
170+
}
171+
const newUrl = await determineSchemaURL(folderContext);
172+
if (newUrl === schema) {
173+
return;
174+
}
175+
const result = await vscode.window.showInformationMessage(
176+
`The $schema property for ${doc.uri.fsPath} is not set to the version of the Swift toolchain that you are using. Would you like to update the $schema property?`,
177+
"Yes",
178+
"No",
179+
"Don't Ask Again"
180+
);
181+
if (result === "Yes") {
182+
config.$schema = newUrl;
183+
await vscode.workspace.fs.writeFile(
184+
doc.uri,
185+
Buffer.from(JSON.stringify(config, undefined, 2))
186+
);
187+
return;
188+
} else if (result === "Don't Ask Again") {
189+
configuration.checkLspConfigurationSchema = false;
190+
return;
191+
}
192+
}
193+
194+
export async function handleSchemaUpdate(
195+
doc: vscode.TextDocument,
196+
workspaceContext: WorkspaceContext
197+
) {
198+
if (
199+
!configuration.checkLspConfigurationSchema ||
200+
!(
201+
basename(dirname(doc.uri.fsPath)) === ".sourcekit-lsp" &&
202+
basename(doc.uri.fsPath) === "config.json"
203+
)
204+
) {
205+
return;
206+
}
207+
await checkDocumentSchema(doc, workspaceContext);
208+
}
209+
210+
export function registerSourceKitSchemaWatcher(
211+
workspaceContext: WorkspaceContext
212+
): vscode.Disposable {
213+
return vscode.workspace.onDidOpenTextDocument(doc => {
214+
void handleSchemaUpdate(doc, workspaceContext);
215+
});
216+
}

src/configuration.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,6 @@ export interface LSPConfiguration {
5555
readonly supportedLanguages: string[];
5656
/** Is SourceKit-LSP disabled */
5757
readonly disable: boolean;
58-
/** Configuration branch to use when setting $schema */
59-
readonly configurationBranch: string;
6058
}
6159

6260
/** debugger configuration */
@@ -152,11 +150,6 @@ const configuration = {
152150
.getConfiguration("swift.sourcekit-lsp")
153151
.get<boolean>("disable", false);
154152
},
155-
get configurationBranch(): string {
156-
return vscode.workspace
157-
.getConfiguration("swift.sourcekit-lsp")
158-
.get<string>("configurationBranch", "");
159-
},
160153
};
161154
},
162155

@@ -503,6 +496,22 @@ const configuration = {
503496
.getConfiguration("swift")
504497
.get<Record<string, boolean>>("excludePathsFromActivation", {});
505498
},
499+
get lspConfigurationBranch(): string {
500+
return vscode.workspace.getConfiguration("swift").get<string>("lspConfigurationBranch", "");
501+
},
502+
get checkLspConfigurationSchema(): boolean {
503+
return vscode.workspace
504+
.getConfiguration("swift")
505+
.get<boolean>("checkLspConfigurationSchema", true);
506+
},
507+
set checkLspConfigurationSchema(value: boolean) {
508+
void vscode.workspace
509+
.getConfiguration("swift")
510+
.update("checkLspConfigurationSchema", value)
511+
.then(() => {
512+
/* Put in worker queue */
513+
});
514+
},
506515
};
507516

508517
const vsCodeVariableRegex = new RegExp(/\$\{(.+?)\}/g);

src/extension.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { resolveFolderDependencies } from "./commands/dependencies/resolve";
3636
import { SelectedXcodeWatcher } from "./toolchain/SelectedXcodeWatcher";
3737
import configuration, { handleConfigurationChangeEvent } from "./configuration";
3838
import contextKeys from "./contextKeys";
39+
import { registerSourceKitSchemaWatcher } from "./commands/generateSourcekitConfiguration";
3940

4041
/**
4142
* External API as exposed by the extension. Can be queried by other extensions
@@ -136,6 +137,8 @@ export async function activate(context: vscode.ExtensionContext): Promise<Api> {
136137
);
137138
context.subscriptions.push(TestExplorer.observeFolders(workspaceContext));
138139

140+
context.subscriptions.push(registerSourceKitSchemaWatcher(workspaceContext));
141+
139142
// setup workspace context with initial workspace folders
140143
void workspaceContext.addWorkspaceFolders();
141144

0 commit comments

Comments
 (0)