diff --git a/src/jsonLanguageService.ts b/src/jsonLanguageService.ts index 1e2e54fa..dc3b143d 100644 --- a/src/jsonLanguageService.ts +++ b/src/jsonLanguageService.ts @@ -10,7 +10,7 @@ import { JSONValidation } from './services/jsonValidation'; import { JSONDocumentSymbols } from './services/jsonDocumentSymbols'; import { parse as parseJSON, newJSONDocument } from './parser/jsonParser'; import { schemaContributions } from './services/configuration'; -import { JSONSchemaService } from './services/jsonSchemaService'; +import { ISchemaHandle, JSONSchemaService } from './services/jsonSchemaService'; import { getFoldingRanges } from './services/jsonFolding'; import { getSelectionRanges } from './services/jsonSelectionRanges'; import { sort } from './utils/sort'; @@ -24,7 +24,7 @@ import { FoldingRange, JSONSchema, SelectionRange, FoldingRangesContext, DocumentSymbolsContext, ColorInformationContext as DocumentColorsContext, TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic, - TextEdit, FormattingOptions, DocumentSymbol, DefinitionLink, MatchingSchema, JSONLanguageStatus, SortOptions + TextEdit, FormattingOptions, DocumentSymbol, DefinitionLink, MatchingSchema, JSONLanguageStatus, SortOptions, SchemaConfiguration } from './jsonLanguageTypes'; import { findLinks } from './services/jsonLinks'; import { DocumentLink } from 'vscode-languageserver-types'; @@ -41,6 +41,8 @@ export interface LanguageService { parseJSONDocument(document: TextDocument): JSONDocument; newJSONDocument(rootNode: ASTNode, syntaxDiagnostics?: Diagnostic[]): JSONDocument; resetSchema(uri: string): boolean; + registerExternalSchema(config: SchemaConfiguration): ISchemaHandle; + clearExternalSchema(uri:string): void; getMatchingSchemas(document: TextDocument, jsonDocument: JSONDocument, schema?: JSONSchema): Thenable; getLanguageStatus(document: TextDocument, jsonDocument: JSONDocument): JSONLanguageStatus; doResolve(item: CompletionItem): Thenable; @@ -77,6 +79,8 @@ export function getLanguageService(params: LanguageServiceParams): LanguageServi jsonValidation.configure(settings); }, resetSchema: (uri: string) => jsonSchemaService.onResourceChange(uri), + registerExternalSchema: jsonSchemaService.registerExternalSchema.bind(jsonSchemaService), + clearExternalSchema: jsonSchemaService.clearExternalSchema.bind(jsonSchemaService), doValidation: jsonValidation.doValidation.bind(jsonValidation), getLanguageStatus: jsonValidation.getLanguageStatus.bind(jsonValidation), parseJSONDocument: (document: TextDocument) => parseJSON(document, { collectComments: true }), diff --git a/src/services/jsonSchemaService.ts b/src/services/jsonSchemaService.ts index eabfbacd..21d710e0 100644 --- a/src/services/jsonSchemaService.ts +++ b/src/services/jsonSchemaService.ts @@ -21,6 +21,11 @@ export interface IJSONSchemaService { */ registerExternalSchema(config: SchemaConfiguration): ISchemaHandle; + /** + * Clear a secific schema + */ + clearExternalSchema(uri:string): void; + /** * Clears all cached schema files */ @@ -361,6 +366,18 @@ export class JSONSchemaService implements IJSONSchemaService { return config.schema ? this.addSchemaHandle(id, config.schema) : this.getOrAddSchemaHandle(id); } + public clearExternalSchema(uri: string): void { + const normalizedUri = normalizeId(uri); + if (this.schemasById[normalizedUri] && this.registeredSchemasIds[normalizedUri]) { + delete this.schemasById[normalizedUri]; + delete this.registeredSchemasIds[normalizedUri]; + this.filePatternAssociations = this.filePatternAssociations.filter( + (association) => !association.getURIs().includes(normalizedUri) + ); + this.cachedSchemaForResource = undefined; + } + } + public clearExternalSchemas(): void { this.schemasById = {}; this.filePatternAssociations = []; diff --git a/src/test/schema.test.ts b/src/test/schema.test.ts index f83b520b..2a2eba7d 100644 --- a/src/test/schema.test.ts +++ b/src/test/schema.test.ts @@ -1037,6 +1037,53 @@ suite('JSON Schema', () => { }); + test('Clear specific Schema',async function(){ + const service = new SchemaService.JSONSchemaService(newMockRequestService(), workspaceContext); + const id1 = 'http://myschemastore/test1'; + const schema1: JSONSchema = { + type: 'object', + properties: { + child: { + type: 'number' + } + } + }; + + const id2 = 'http://myschemastore/test2'; + const schema2: JSONSchema = { + type: 'object', + properties: { + child: { + type: 'string' + } + } + }; + + const id3 = 'http://myschemastore/test3'; + const schema3: JSONSchema = { + type: 'object', + properties: { + child: { + type: 'boolean' + } + } + }; + + service.registerExternalSchema({ uri: id1, schema: schema1,fileMatch:['*.json'] }); + service.registerExternalSchema({ uri: id2, schema: schema2,fileMatch:['*.json'] }); + service.registerExternalSchema({ uri: id3, schema: schema3,fileMatch:['*.json'] }); + + let resolvedSchema = await service.getSchemaForResource('main.json'); + assert.deepStrictEqual(resolvedSchema?.errors, []); + assert.strictEqual(3, resolvedSchema?.schema.allOf?.length); + service.clearExternalSchema(id1); + + resolvedSchema = await service.getSchemaForResource('main.json'); + assert.deepStrictEqual(resolvedSchema?.errors, []); + assert.strictEqual(2, resolvedSchema?.schema.allOf?.length); + + }); + test('Schema contributions', async function () { const service = new SchemaService.JSONSchemaService(newMockRequestService(), workspaceContext); @@ -1976,4 +2023,62 @@ suite('JSON Schema', () => { }); + test('Validation on Multiple Schemas',async function(){ + const id1 = 'http://myschemastore/test1'; + const schema1: JSONSchema = { + type: 'object', + properties: { + foo: { + type: 'number' + } + }, + required:['foo'] + }; + + const id2 = 'http://myschemastore/test2'; + const schema2: JSONSchema = { + type: 'object', + properties: { + bar: { + type: 'number' + } + }, + required:['bar'] + }; + + const id3 = 'http://myschemastore/test3'; + const schema3: JSONSchema = { + type: 'object', + properties: { + foobar: { + type: 'number' + } + }, + required:['foobar'] + }; + + const testDoc = toDocument(JSON.stringify({}),{},'main.json'); + + const ls = getLanguageService({ workspaceContext }); + ls.registerExternalSchema({ uri: id1, schema: schema1,fileMatch:['*.json'] }); + ls.registerExternalSchema({ uri: id2, schema: schema2,fileMatch:['*.json'] }); + ls.registerExternalSchema({ uri: id3, schema: schema3,fileMatch:['*.json'] }); + + let validation = await ls.doValidation(testDoc.textDoc, testDoc.jsonDoc); + assert.deepStrictEqual(validation.length, 3); + assert.deepStrictEqual(validation.map(v => v.message), ['Missing property "foo".','Missing property "bar".','Missing property "foobar".']); + + ls.clearExternalSchema(id1); + validation = await ls.doValidation(testDoc.textDoc, testDoc.jsonDoc); + assert.deepStrictEqual(validation.length, 2); + assert.deepStrictEqual(validation.map(v => v.message), ['Missing property "bar".','Missing property "foobar".']); + + ls.clearExternalSchema(id2); + ls.clearExternalSchema(id3); + validation = await ls.doValidation(testDoc.textDoc, testDoc.jsonDoc); + assert.deepStrictEqual(validation.length, 0); + assert.deepStrictEqual(validation.map(v => v.message), []); + + }); + });