Skip to content

Commit c091672

Browse files
authored
Support JSON Schema versions from schemastore (#710)
* Support JSON Schema versions from schemastore Signed-off-by: Yevhen Vydolob <yvydolob@redhat.com> * Fix test Signed-off-by: Yevhen Vydolob <yvydolob@redhat.com> * add tests Signed-off-by: Yevhen Vydolob <yvydolob@redhat.com>
1 parent 79c3163 commit c091672

File tree

2 files changed

+175
-23
lines changed

2 files changed

+175
-23
lines changed

src/schema-status-bar-item.ts

Lines changed: 126 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ import {
1616
import { CommonLanguageClient, RequestType } from 'vscode-languageclient/node';
1717

1818
type FileUri = string;
19+
type SchemaVersions = { [version: string]: string };
1920
interface JSONSchema {
2021
name?: string;
2122
description?: string;
2223
uri: string;
24+
versions?: SchemaVersions;
2325
}
2426

2527
interface MatchingJSONSchema extends JSONSchema {
@@ -31,15 +33,24 @@ interface SchemaItem extends QuickPickItem {
3133
schema?: MatchingJSONSchema;
3234
}
3335

36+
interface SchemaVersionItem extends QuickPickItem {
37+
version: string;
38+
url: string;
39+
}
40+
3441
// eslint-disable-next-line @typescript-eslint/ban-types
35-
const getJSONSchemas: RequestType<FileUri, MatchingJSONSchema[], {}> = new RequestType('yaml/get/all/jsonSchemas');
42+
const getJSONSchemasRequestType: RequestType<FileUri, MatchingJSONSchema[], {}> = new RequestType('yaml/get/all/jsonSchemas');
3643

3744
// eslint-disable-next-line @typescript-eslint/ban-types
38-
const getSchema: RequestType<FileUri, JSONSchema[], {}> = new RequestType('yaml/get/jsonSchema');
45+
const getSchemaRequestType: RequestType<FileUri, JSONSchema[], {}> = new RequestType('yaml/get/jsonSchema');
3946

4047
export let statusBarItem: StatusBarItem;
4148

4249
let client: CommonLanguageClient;
50+
let versionSelection: SchemaItem = undefined;
51+
52+
const selectVersionLabel = 'Select Different Version';
53+
4354
export function createJSONSchemaStatusBarItem(context: ExtensionContext, languageclient: CommonLanguageClient): void {
4455
if (statusBarItem) {
4556
updateStatusBar(window.activeTextEditor);
@@ -61,14 +72,32 @@ export function createJSONSchemaStatusBarItem(context: ExtensionContext, languag
6172

6273
async function updateStatusBar(editor: TextEditor): Promise<void> {
6374
if (editor && editor.document.languageId === 'yaml') {
75+
versionSelection = undefined;
6476
// get schema info there
65-
const schema = await client.sendRequest(getSchema, editor.document.uri.toString());
77+
const schema = await client.sendRequest(getSchemaRequestType, editor.document.uri.toString());
6678
if (!schema || schema.length === 0) {
6779
statusBarItem.text = 'No JSON Schema';
6880
statusBarItem.tooltip = 'Select JSON Schema';
6981
statusBarItem.backgroundColor = undefined;
7082
} else if (schema.length === 1) {
7183
statusBarItem.text = schema[0].name ?? schema[0].uri;
84+
let version;
85+
if (schema[0].versions) {
86+
version = findUsedVersion(schema[0].versions, schema[0].uri);
87+
} else {
88+
const schemas = await client.sendRequest(getJSONSchemasRequestType, window.activeTextEditor.document.uri.toString());
89+
let versionSchema: JSONSchema;
90+
const schemaStoreItem = findSchemaStoreItem(schemas, schema[0].uri);
91+
if (schemaStoreItem) {
92+
[version, versionSchema] = schemaStoreItem;
93+
(versionSchema as MatchingJSONSchema).usedForCurrentFile = true;
94+
versionSchema.uri = schema[0].uri;
95+
versionSelection = createSelectVersionItem(version, versionSchema as MatchingJSONSchema);
96+
}
97+
}
98+
if (version && !statusBarItem.text.includes(version)) {
99+
statusBarItem.text += `(${version})`;
100+
}
72101
statusBarItem.tooltip = 'Select JSON Schema';
73102
statusBarItem.backgroundColor = undefined;
74103
} else {
@@ -84,11 +113,15 @@ async function updateStatusBar(editor: TextEditor): Promise<void> {
84113
}
85114

86115
async function showSchemaSelection(): Promise<void> {
87-
const schemas = await client.sendRequest(getJSONSchemas, window.activeTextEditor.document.uri.toString());
116+
const schemas = await client.sendRequest(getJSONSchemasRequestType, window.activeTextEditor.document.uri.toString());
88117
const schemasPick = window.createQuickPick<SchemaItem>();
89-
const pickItems: SchemaItem[] = [];
118+
let pickItems: SchemaItem[] = [];
90119

91120
for (const val of schemas) {
121+
if (val.usedForCurrentFile && val.versions) {
122+
const item = createSelectVersionItem(findUsedVersion(val.versions, val.uri), val);
123+
pickItems.unshift(item);
124+
}
92125
const item = {
93126
label: val.name ?? val.uri,
94127
description: val.description,
@@ -98,8 +131,17 @@ async function showSchemaSelection(): Promise<void> {
98131
};
99132
pickItems.push(item);
100133
}
134+
if (versionSelection) {
135+
pickItems.unshift(versionSelection);
136+
}
101137

102-
pickItems.sort((a, b) => {
138+
pickItems = pickItems.sort((a, b) => {
139+
if (a.schema?.usedForCurrentFile && a.schema?.versions) {
140+
return -1;
141+
}
142+
if (b.schema?.usedForCurrentFile && b.schema?.versions) {
143+
return 1;
144+
}
103145
if (a.schema?.usedForCurrentFile) {
104146
return -1;
105147
}
@@ -117,23 +159,10 @@ async function showSchemaSelection(): Promise<void> {
117159
schemasPick.onDidChangeSelection((selection) => {
118160
try {
119161
if (selection.length > 0) {
120-
if (selection[0].schema) {
121-
const settings: Record<string, unknown> = workspace.getConfiguration('yaml').get('schemas');
122-
const fileUri = window.activeTextEditor.document.uri.toString();
123-
const newSettings = Object.assign({}, settings);
124-
deleteExistingFilePattern(newSettings, fileUri);
125-
const schemaURI = selection[0].schema.uri;
126-
const schemaSettings = newSettings[schemaURI];
127-
if (schemaSettings) {
128-
if (Array.isArray(schemaSettings)) {
129-
(schemaSettings as Array<string>).push(fileUri);
130-
} else if (typeof schemaSettings === 'string') {
131-
newSettings[schemaURI] = [schemaSettings, fileUri];
132-
}
133-
} else {
134-
newSettings[schemaURI] = fileUri;
135-
}
136-
workspace.getConfiguration('yaml').update('schemas', newSettings);
162+
if (selection[0].label === selectVersionLabel) {
163+
handleSchemaVersionSelection(selection[0].schema);
164+
} else if (selection[0].schema) {
165+
writeSchemaUriMapping(selection[0].schema.uri);
137166
}
138167
}
139168
} catch (err) {
@@ -162,3 +191,77 @@ function deleteExistingFilePattern(settings: Record<string, unknown>, fileUri: s
162191

163192
return settings;
164193
}
194+
195+
function createSelectVersionItem(version: string, schema: MatchingJSONSchema): SchemaItem {
196+
return {
197+
label: selectVersionLabel,
198+
detail: `Current: ${version}`,
199+
alwaysShow: true,
200+
schema: schema,
201+
};
202+
}
203+
function findSchemaStoreItem(schemas: JSONSchema[], url: string): [string, JSONSchema] | undefined {
204+
for (const schema of schemas) {
205+
if (schema.versions) {
206+
for (const version in schema.versions) {
207+
if (url === schema.versions[version]) {
208+
return [version, schema];
209+
}
210+
}
211+
}
212+
}
213+
}
214+
215+
function writeSchemaUriMapping(schemaUrl: string): void {
216+
const settings: Record<string, unknown> = workspace.getConfiguration('yaml').get('schemas');
217+
const fileUri = window.activeTextEditor.document.uri.toString();
218+
const newSettings = Object.assign({}, settings);
219+
deleteExistingFilePattern(newSettings, fileUri);
220+
const schemaSettings = newSettings[schemaUrl];
221+
if (schemaSettings) {
222+
if (Array.isArray(schemaSettings)) {
223+
(schemaSettings as Array<string>).push(fileUri);
224+
} else if (typeof schemaSettings === 'string') {
225+
newSettings[schemaUrl] = [schemaSettings, fileUri];
226+
}
227+
} else {
228+
newSettings[schemaUrl] = fileUri;
229+
}
230+
workspace.getConfiguration('yaml').update('schemas', newSettings);
231+
}
232+
233+
function handleSchemaVersionSelection(schema: MatchingJSONSchema): void {
234+
const versionPick = window.createQuickPick<SchemaVersionItem>();
235+
const versionItems: SchemaVersionItem[] = [];
236+
const usedVersion = findUsedVersion(schema.versions, schema.uri);
237+
for (const version in schema.versions) {
238+
versionItems.push({
239+
label: version + (usedVersion === version ? '$(check)' : ''),
240+
url: schema.versions[version],
241+
version: version,
242+
});
243+
}
244+
245+
versionPick.items = versionItems;
246+
versionPick.title = `Select JSON Schema version for ${schema.name}`;
247+
versionPick.placeholder = 'Version';
248+
versionPick.onDidHide(() => versionPick.dispose());
249+
250+
versionPick.onDidChangeSelection((items) => {
251+
if (items && items.length === 1) {
252+
writeSchemaUriMapping(items[0].url);
253+
}
254+
versionPick.hide();
255+
});
256+
versionPick.show();
257+
}
258+
259+
function findUsedVersion(versions: SchemaVersions, uri: string): string {
260+
for (const version in versions) {
261+
const versionUri = versions[version];
262+
if (versionUri === uri) {
263+
return version;
264+
}
265+
}
266+
return 'latest';
267+
}

test/json-schema-selection.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { CommonLanguageClient } from 'vscode-languageclient';
1111
import * as vscode from 'vscode';
1212
import { TestLanguageClient } from './helper';
1313
import * as jsonStatusBar from '../src/schema-status-bar-item';
14+
1415
const expect = chai.expect;
1516
chai.use(sinonChai);
1617

@@ -105,4 +106,52 @@ describe('Status bar should work in multiple different scenarios', () => {
105106
expect(statusBar.backgroundColor).to.eql({ id: 'statusBarItem.warningBackground' });
106107
expect(statusBar.show).calledTwice;
107108
});
109+
110+
it('Should show JSON Schema Store schema version', async () => {
111+
const context: vscode.ExtensionContext = {
112+
subscriptions: [],
113+
} as vscode.ExtensionContext;
114+
const statusBar = ({ show: sandbox.stub() } as unknown) as vscode.StatusBarItem;
115+
createStatusBarItemStub.returns(statusBar);
116+
onDidChangeActiveTextEditorStub.returns({ document: { uri: vscode.Uri.parse('/foo.yaml') } });
117+
clcStub.sendRequest
118+
.withArgs(sinon.match.has('method', 'yaml/get/jsonSchema'), sinon.match('/foo.yaml'))
119+
.resolves([{ uri: 'https://foo.com/bar.json', name: 'bar schema' }]);
120+
clcStub.sendRequest
121+
.withArgs(sinon.match.has('method', 'yaml/get/all/jsonSchemas'), sinon.match.any)
122+
.resolves([{ versions: { '1.0.0': 'https://foo.com/bar.json' } }]);
123+
124+
createJSONSchemaStatusBarItem(context, (clcStub as unknown) as CommonLanguageClient);
125+
const callBackFn = onDidChangeActiveTextEditorStub.firstCall.firstArg;
126+
await callBackFn({ document: { languageId: 'yaml', uri: vscode.Uri.parse('/foo.yaml') } });
127+
128+
expect(statusBar.text).to.equal('bar schema(1.0.0)');
129+
expect(statusBar.tooltip).to.equal('Select JSON Schema');
130+
expect(statusBar.backgroundColor).to.be.undefined;
131+
expect(statusBar.show).calledTwice;
132+
});
133+
134+
it('Should show JSON Schema Store schema version, dont include version', async () => {
135+
const context: vscode.ExtensionContext = {
136+
subscriptions: [],
137+
} as vscode.ExtensionContext;
138+
const statusBar = ({ show: sandbox.stub() } as unknown) as vscode.StatusBarItem;
139+
createStatusBarItemStub.returns(statusBar);
140+
onDidChangeActiveTextEditorStub.returns({ document: { uri: vscode.Uri.parse('/foo.yaml') } });
141+
clcStub.sendRequest
142+
.withArgs(sinon.match.has('method', 'yaml/get/jsonSchema'), sinon.match('/foo.yaml'))
143+
.resolves([{ uri: 'https://foo.com/bar.json', name: 'bar schema(1.0.0)' }]);
144+
clcStub.sendRequest
145+
.withArgs(sinon.match.has('method', 'yaml/get/all/jsonSchemas'), sinon.match.any)
146+
.resolves([{ versions: { '1.0.0': 'https://foo.com/bar.json' } }]);
147+
148+
createJSONSchemaStatusBarItem(context, (clcStub as unknown) as CommonLanguageClient);
149+
const callBackFn = onDidChangeActiveTextEditorStub.firstCall.firstArg;
150+
await callBackFn({ document: { languageId: 'yaml', uri: vscode.Uri.parse('/foo.yaml') } });
151+
152+
expect(statusBar.text).to.equal('bar schema(1.0.0)');
153+
expect(statusBar.tooltip).to.equal('Select JSON Schema');
154+
expect(statusBar.backgroundColor).to.be.undefined;
155+
expect(statusBar.show).calledTwice;
156+
});
108157
});

0 commit comments

Comments
 (0)