Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 54 additions & 26 deletions extensions/json-language-features/client/src/jsonClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
const uri = Uri.parse(uriPath);
const uriString = uri.toString(true);
if (uri.scheme === 'untitled') {
Expand All @@ -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') {
Expand All @@ -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();

Expand Down Expand Up @@ -542,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<string> => {
let timeout: Disposable | undefined;
return new Promise<string>((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);
}
Expand All @@ -562,9 +570,15 @@ 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);
});
Comment on lines +574 to +577

const registryWatchers = new Map<string, Disposable>();
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();
Expand Down Expand Up @@ -598,15 +612,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<Record<string, boolean>>(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)));

Expand Down Expand Up @@ -682,14 +701,14 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP
return settingsCache;
}

async function getSchemaAssociations(forceRefresh: boolean): Promise<ISchemaAssociation[]> {
async function getSchemaAssociations(forceRefresh: boolean, includeRemoteRegistries: boolean): Promise<ISchemaAssociation[]> {
if (!schemaAssociationsCache || forceRefresh) {
schemaAssociationsCache = computeSchemaAssociations();
schemaAssociationsCache = computeSchemaAssociations(getRemoteSchemaRegistryContent, includeRemoteRegistries);
}
return schemaAssociationsCache;
}

async function isTrusted(uri: Uri): Promise<boolean> {
async function isTrusted(uri: Uri, allowKnownSchemaAssociations = true): Promise<boolean> {
if (uri.scheme !== 'http' && uri.scheme !== 'https') {
return true;
}
Expand All @@ -700,10 +719,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, schemaAssociationRefreshIncludesRemoteRegistries);
for (const association of knownAssociations) {
if (association.uri === uriString) {
return true;
}
}
}
const settingsCache = getSettings(false);
Expand Down Expand Up @@ -800,9 +821,9 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP
};
}

async function computeSchemaAssociations(): Promise<ISchemaAssociation[]> {
async function computeSchemaAssociations(getRegistryContent: (uri: string) => Promise<string>, includeRemoteRegistries: boolean): Promise<ISchemaAssociation[]> {
const extensionAssociations = getSchemaExtensionAssociations();
return extensionAssociations.concat(await getSchemaRegistryAssociations());
return extensionAssociations.concat(await getSchemaRegistryAssociations(getRegistryContent, includeRemoteRegistries));
}

function resolveExtensionResource(extensionUri: Uri, resource: string): Uri {
Expand Down Expand Up @@ -860,14 +881,20 @@ function getSchemaRegistryUris(): Uri[] {
return result;
}

async function getSchemaRegistryAssociations(): Promise<ISchemaAssociation[]> {
const result: ISchemaAssociation[] = [];
for (const registryUri of getSchemaRegistryUris()) {
function isWatchableRegistryUri(uri: Uri): boolean {
return uri.scheme !== 'http' && uri.scheme !== 'https';
}

async function getSchemaRegistryAssociations(getRegistryContent: (uri: string) => Promise<string>, includeRemoteRegistries: boolean): Promise<ISchemaAssociation[]> {
const registryUris = getSchemaRegistryUris().filter(uri => includeRemoteRegistries || isWatchableRegistryUri(uri));
const registryAssociations = await Promise.all(registryUris.map(async registryUri => {
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)) {
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({
Expand All @@ -876,12 +903,14 @@ async function getSchemaRegistryAssociations(): Promise<ISchemaAssociation[]> {
});
}
}
return result;
}
} catch {
// Ignore unavailable or invalid registry.
}
}
return result;
return [];
}));
return registryAssociations.flat();
}


Expand Down Expand Up @@ -996,4 +1025,3 @@ export namespace ErrorCodes {
export function isSchemaResolveError(d: Diagnostic) {
return typeof d.code === 'number' && d.code >= ErrorCodes.SchemaResolveError;
}

3 changes: 3 additions & 0 deletions extensions/json-language-features/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@
"jsonValidationRegistry": [
{
"url": "vscode://schemas-associations/schemas-associations.json"
},
{
"url": "https://www.schemastore.org/api/json/catalog.json"
}
],
"commands": [
Expand Down
Loading