From 2da1b12bad23a2962797b22bf15de916e4622f8f Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 23 Jul 2026 11:45:22 +0200 Subject: [PATCH 1/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- extensions/json-language-features/client/src/jsonClient.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index fa1c6d10209921..625f60b4a9ef39 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -996,4 +996,3 @@ export namespace ErrorCodes { export function isSchemaResolveError(d: Diagnostic) { return typeof d.code === 'number' && d.code >= ErrorCodes.SchemaResolveError; } - From b6f6a23ae6b13a39b4bd3bcd9994aed30d499047 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Fri, 24 Jul 2026 09:55:09 -0400 Subject: [PATCH 2/3] feat: adds json schema store support to the json language extension --- .../client/src/jsonClient.ts | 48 ++++++++++++------- .../json-language-features/package.json | 3 ++ 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index 625f60b4a9ef39..10b577f7abdb17 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -391,8 +391,7 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP const schemaDocuments: { [uri: string]: boolean } = {}; - // handle content request - client.onRequest(VSCodeContentRequest.type, async (uriPath: string) => { + async function getSchemaContent(uriPath: string, allowKnownSchemaAssociations = true): Promise { const uri = Uri.parse(uriPath); const uriString = uri.toString(true); if (uri.scheme === 'untitled') { @@ -419,7 +418,7 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP if (!workspace.isTrusted) { throw new ResponseError(SchemaRequestServiceErrors.UntrustedWorkspaceError, l10n.t('Downloading schemas is disabled in untrusted workspaces')); } - if (!await isTrusted(uri)) { + if (!await isTrusted(uri, allowKnownSchemaAssociations)) { throw new ResponseError(SchemaRequestServiceErrors.UntrustedSchemaError, l10n.t('Location {0} is untrusted', uriString)); } if (runtime.telemetry && uri.authority === 'schema.management.azure.com') { @@ -440,7 +439,10 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP } else { throw new ResponseError(SchemaRequestServiceErrors.HTTPDisabledError, l10n.t('Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload)); } - }); + } + + // handle content request + client.onRequest(VSCodeContentRequest.type, uriPath => getSchemaContent(uriPath)); await client.start(); @@ -564,7 +566,7 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP const registryWatchers = new Map(); const updateRegistryWatchers = () => { - const registryUris = new Set(getSchemaRegistryUris().map(uri => uri.toString())); + const registryUris = new Set(getSchemaRegistryUris().filter(isWatchableRegistryUri).map(uri => uri.toString())); for (const [uri, watcher] of registryWatchers) { if (!registryUris.has(uri)) { watcher.dispose(); @@ -598,15 +600,20 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP updateFormatterRegistration(); } else if (e.affectsConfiguration(SettingIds.enableSchemaDownload)) { schemaDownloadEnabled = !!workspace.getConfiguration().get(SettingIds.enableSchemaDownload); + refreshSchemaAssociations(); triggerValidation(); } else if (e.affectsConfiguration(SettingIds.editorFoldingMaximumRegions) || e.affectsConfiguration(SettingIds.editorColorDecoratorsLimit)) { client.sendNotification(DidChangeConfigurationNotification.type, { settings: getSettings(true) }); } else if (e.affectsConfiguration(SettingIds.trustedDomains)) { trustedDomains = workspace.getConfiguration().get>(SettingIds.trustedDomains, {}); + refreshSchemaAssociations(); triggerValidation(); } })); - toDispose.push(workspace.onDidGrantWorkspaceTrust(() => triggerValidation())); + toDispose.push(workspace.onDidGrantWorkspaceTrust(() => { + refreshSchemaAssociations(); + triggerValidation(); + })); toDispose.push(createLanguageStatusItem(documentSelector, (uri: string) => client.sendRequest(LanguageStatusRequest.type, uri))); @@ -684,12 +691,12 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP async function getSchemaAssociations(forceRefresh: boolean): Promise { if (!schemaAssociationsCache || forceRefresh) { - schemaAssociationsCache = computeSchemaAssociations(); + schemaAssociationsCache = computeSchemaAssociations(uri => getSchemaContent(uri, false)); } return schemaAssociationsCache; } - async function isTrusted(uri: Uri): Promise { + async function isTrusted(uri: Uri, allowKnownSchemaAssociations = true): Promise { if (uri.scheme !== 'http' && uri.scheme !== 'https') { return true; } @@ -700,10 +707,12 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP return true; } - const knownAssociations = await getSchemaAssociations(false); - for (const association of knownAssociations) { - if (association.uri === uriString) { - return true; + if (allowKnownSchemaAssociations) { + const knownAssociations = await getSchemaAssociations(false); + for (const association of knownAssociations) { + if (association.uri === uriString) { + return true; + } } } const settingsCache = getSettings(false); @@ -800,9 +809,9 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP }; } -async function computeSchemaAssociations(): Promise { +async function computeSchemaAssociations(getRegistryContent: (uri: string) => Promise): Promise { const extensionAssociations = getSchemaExtensionAssociations(); - return extensionAssociations.concat(await getSchemaRegistryAssociations()); + return extensionAssociations.concat(await getSchemaRegistryAssociations(getRegistryContent)); } function resolveExtensionResource(extensionUri: Uri, resource: string): Uri { @@ -860,12 +869,17 @@ function getSchemaRegistryUris(): Uri[] { return result; } -async function getSchemaRegistryAssociations(): Promise { +function isWatchableRegistryUri(uri: Uri): boolean { + return uri.scheme !== 'http' && uri.scheme !== 'https'; +} + +async function getSchemaRegistryAssociations(getRegistryContent: (uri: string) => Promise): Promise { const result: ISchemaAssociation[] = []; for (const registryUri of getSchemaRegistryUris()) { try { - const data = await workspace.fs.readFile(registryUri); - const rawStr = new TextDecoder().decode(data); + const rawStr = isWatchableRegistryUri(registryUri) + ? new TextDecoder().decode(await workspace.fs.readFile(registryUri)) + : await getRegistryContent(registryUri.toString(true)); const registry = <{ schemas?: { url?: string; fileMatch?: string[] }[] }>JSON.parse(rawStr); if (Array.isArray(registry.schemas)) { for (const schema of registry.schemas) { diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index cf3e0d5ecc691e..4fa10ef714794d 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -177,6 +177,9 @@ "jsonValidationRegistry": [ { "url": "vscode://schemas-associations/schemas-associations.json" + }, + { + "url": "https://www.schemastore.org/api/json/catalog.json" } ], "commands": [ From bb30c9046c33ffd9ae459b2bb934df9e09d73db9 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Mon, 27 Jul 2026 09:37:57 -0400 Subject: [PATCH 3/3] fix: load remote JSON schema registries asynchronously --- .../client/src/jsonClient.ts | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index 10b577f7abdb17..72e92d7b86c11a 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -544,16 +544,22 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP providedCodeActionKinds: [CodeActionKind.QuickFix] })); - client.sendNotification(SchemaAssociationNotification.type, await getSchemaAssociations(false)); - let schemaAssociationRefreshGeneration = 0; let schemaAssociationRefreshTrigger: Disposable | undefined; + let schemaAssociationRefreshIncludesRemoteRegistries = false; + const getRemoteSchemaRegistryContent = (uri: string): Promise => { + let timeout: Disposable | undefined; + return new Promise((resolve, reject) => { + timeout = runtime.timer.setTimeout(() => reject(new Error(`Timed out while loading schema registry ${uri}`)), 5000); + getSchemaContent(uri, false).then(resolve, reject); + }).finally(() => timeout?.dispose()); + }; const refreshSchemaAssociations = () => { const generation = ++schemaAssociationRefreshGeneration; schemaAssociationRefreshTrigger?.dispose(); schemaAssociationRefreshTrigger = runtime.timer.setTimeout(async () => { schemaAssociationRefreshTrigger = undefined; - const associations = await getSchemaAssociations(true); + const associations = await getSchemaAssociations(true, schemaAssociationRefreshIncludesRemoteRegistries); if (generation === schemaAssociationRefreshGeneration) { client.sendNotification(SchemaAssociationNotification.type, associations); } @@ -564,6 +570,12 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP schemaAssociationRefreshTrigger?.dispose(); })); + client.sendNotification(SchemaAssociationNotification.type, await getSchemaAssociations(false, false)); + getSchemaAssociations(true, true).then(associations => { + schemaAssociationRefreshIncludesRemoteRegistries = true; + client.sendNotification(SchemaAssociationNotification.type, associations); + }); + const registryWatchers = new Map(); const updateRegistryWatchers = () => { const registryUris = new Set(getSchemaRegistryUris().filter(isWatchableRegistryUri).map(uri => uri.toString())); @@ -689,9 +701,9 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP return settingsCache; } - async function getSchemaAssociations(forceRefresh: boolean): Promise { + async function getSchemaAssociations(forceRefresh: boolean, includeRemoteRegistries: boolean): Promise { if (!schemaAssociationsCache || forceRefresh) { - schemaAssociationsCache = computeSchemaAssociations(uri => getSchemaContent(uri, false)); + schemaAssociationsCache = computeSchemaAssociations(getRemoteSchemaRegistryContent, includeRemoteRegistries); } return schemaAssociationsCache; } @@ -708,7 +720,7 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP } if (allowKnownSchemaAssociations) { - const knownAssociations = await getSchemaAssociations(false); + const knownAssociations = await getSchemaAssociations(false, schemaAssociationRefreshIncludesRemoteRegistries); for (const association of knownAssociations) { if (association.uri === uriString) { return true; @@ -809,9 +821,9 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP }; } -async function computeSchemaAssociations(getRegistryContent: (uri: string) => Promise): Promise { +async function computeSchemaAssociations(getRegistryContent: (uri: string) => Promise, includeRemoteRegistries: boolean): Promise { const extensionAssociations = getSchemaExtensionAssociations(); - return extensionAssociations.concat(await getSchemaRegistryAssociations(getRegistryContent)); + return extensionAssociations.concat(await getSchemaRegistryAssociations(getRegistryContent, includeRemoteRegistries)); } function resolveExtensionResource(extensionUri: Uri, resource: string): Uri { @@ -873,15 +885,16 @@ function isWatchableRegistryUri(uri: Uri): boolean { return uri.scheme !== 'http' && uri.scheme !== 'https'; } -async function getSchemaRegistryAssociations(getRegistryContent: (uri: string) => Promise): Promise { - const result: ISchemaAssociation[] = []; - for (const registryUri of getSchemaRegistryUris()) { +async function getSchemaRegistryAssociations(getRegistryContent: (uri: string) => Promise, includeRemoteRegistries: boolean): Promise { + const registryUris = getSchemaRegistryUris().filter(uri => includeRemoteRegistries || isWatchableRegistryUri(uri)); + const registryAssociations = await Promise.all(registryUris.map(async registryUri => { try { const rawStr = isWatchableRegistryUri(registryUri) ? new TextDecoder().decode(await workspace.fs.readFile(registryUri)) : await getRegistryContent(registryUri.toString(true)); const registry = <{ schemas?: { url?: string; fileMatch?: string[] }[] }>JSON.parse(rawStr); if (Array.isArray(registry.schemas)) { + const result: ISchemaAssociation[] = []; for (const schema of registry.schemas) { if (typeof schema.url === 'string' && Array.isArray(schema.fileMatch) && schema.fileMatch.every(fileMatch => typeof fileMatch === 'string')) { result.push({ @@ -890,12 +903,14 @@ async function getSchemaRegistryAssociations(getRegistryContent: (uri: string) = }); } } + return result; } } catch { // Ignore unavailable or invalid registry. } - } - return result; + return []; + })); + return registryAssociations.flat(); }