Skip to content
7 changes: 7 additions & 0 deletions src/jsonLanguageTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ export {
TextDocumentEdit, VersionedTextDocumentIdentifier, DocumentHighlightKind
};

/**
* Represents a set of active JSON Schema vocabularies.
* Used to filter which keywords are processed during validation based on the metaschema's $vocabulary declaration.
* @since 2019-09
*/
export type Vocabularies = Set<string>;

/**
* Error codes used by diagnostics
*/
Expand Down
4 changes: 2 additions & 2 deletions src/jsonSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ export interface JSONSchema {
$defs?: { [name: string]: JSONSchema };
$anchor?: string;
$recursiveRef?: string;
$recursiveAnchor?: string;
$vocabulary?: any;
$recursiveAnchor?: boolean | string;
Comment thread
keegan-caruso marked this conversation as resolved.
Outdated
$vocabulary?: { [uri: string]: boolean };

// schema 2020-12
prefixItems?: JSONSchemaRef[];
Expand Down
227 changes: 145 additions & 82 deletions src/parser/jsonParser.ts

Large diffs are not rendered by default.

369 changes: 271 additions & 98 deletions src/services/jsonSchemaService.ts

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions src/services/jsonValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ export class JSONValidation {
for (const warning of schema.warnings) {
addSchemaProblem(warning.message, warning.code, warning.relatedInformation);
}
const semanticErrors = jsonDocument.validate(textDocument, schema.schema, schemaValidation, documentSettings?.schemaDraft);
const semanticErrors = jsonDocument.validate(textDocument, schema.schema, schemaValidation, documentSettings?.schemaDraft, schema.activeVocabularies);
if (semanticErrors) {
semanticErrors.forEach(addProblem);
semanticErrors.forEach(addProblem);
}
}
if (schemaAllowsComments(schema.schema)) {
Expand Down Expand Up @@ -109,7 +109,7 @@ export class JSONValidation {
};

if (schema) {
const uri = schema.id || ('schemaservice://untitled/' + idCounter++);
const uri = schema.$id || schema.id || ('schemaservice://untitled/' + idCounter++);
const handle = this.jsonSchemaService.registerExternalSchema({ uri, schema });
return handle.getResolvedSchema().then(resolvedSchema => {
return getDiagnostics(resolvedSchema);
Expand Down
102 changes: 102 additions & 0 deletions src/services/vocabularies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

/**
* Checks if a keyword is enabled based on the active vocabularies.
* If no vocabulary constraints are present, all keywords are enabled.
* Core keywords are always enabled regardless of vocabulary settings.
*
* @param keyword The keyword to check (e.g., 'type', 'properties', '$ref')
* @param activeVocabularies Set of active vocabulary URIs, or undefined if no constraints
* @returns true if the keyword should be processed, false otherwise
*/
export function isKeywordEnabled(
keyword: string,
activeVocabularies?: Set<string>
): boolean {
const vocabularyKeywords: { [uri: string]: string[] } = {
'https://json-schema.org/draft/2019-09/vocab/core': [
'$id', '$schema', '$ref', '$anchor', '$recursiveRef',
'$recursiveAnchor', '$defs', '$comment', '$vocabulary'
],
'https://json-schema.org/draft/2019-09/vocab/applicator': [
'prefixItems', 'items', 'contains', 'additionalProperties',
'properties', 'patternProperties', 'dependentSchemas',
'propertyNames', 'if', 'then', 'else', 'allOf', 'anyOf', 'oneOf', 'not'
],
'https://json-schema.org/draft/2019-09/vocab/validation': [
'type', 'enum', 'const', 'multipleOf', 'maximum', 'exclusiveMaximum',
'minimum', 'exclusiveMinimum', 'maxLength', 'minLength', 'pattern',
'maxItems', 'minItems', 'uniqueItems', 'maxContains', 'minContains',
'maxProperties', 'minProperties', 'required', 'dependentRequired'
],
'https://json-schema.org/draft/2019-09/vocab/meta-data': [
'title', 'description', 'default', 'deprecated',
'readOnly', 'writeOnly', 'examples'
],
'https://json-schema.org/draft/2019-09/vocab/format': [
'format'
],
'https://json-schema.org/draft/2019-09/vocab/content': [
'contentEncoding', 'contentMediaType', 'contentSchema'
],
'https://json-schema.org/draft/2020-12/vocab/core': [
'$id', '$schema', '$ref', '$anchor', '$dynamicRef',
'$dynamicAnchor', '$defs', '$comment', '$vocabulary'
],
'https://json-schema.org/draft/2020-12/vocab/applicator': [
'prefixItems', 'items', 'contains', 'additionalProperties',
'properties', 'patternProperties', 'dependentSchemas',
'propertyNames', 'if', 'then', 'else', 'allOf', 'anyOf', 'oneOf', 'not'
],
'https://json-schema.org/draft/2020-12/vocab/unevaluated': [
'unevaluatedItems', 'unevaluatedProperties'
],
'https://json-schema.org/draft/2020-12/vocab/validation': [
'type', 'enum', 'const', 'multipleOf', 'maximum', 'exclusiveMaximum',
'minimum', 'exclusiveMinimum', 'maxLength', 'minLength', 'pattern',
'maxItems', 'minItems', 'uniqueItems', 'maxContains', 'minContains',
'maxProperties', 'minProperties', 'required', 'dependentRequired'
],
'https://json-schema.org/draft/2020-12/vocab/meta-data': [
'title', 'description', 'default', 'deprecated',
'readOnly', 'writeOnly', 'examples'
],
'https://json-schema.org/draft/2020-12/vocab/format-annotation': [
'format'
],
'https://json-schema.org/draft/2020-12/vocab/format-assertion': [
'format'
],
'https://json-schema.org/draft/2020-12/vocab/content': [
'contentEncoding', 'contentMediaType', 'contentSchema'
]
};


// If no vocabulary constraints, treat all keywords as enabled
if (!activeVocabularies) {
return true;
}

// Check if this keyword belongs to any active vocabulary
for (const [vocabUri, keywords] of Object.entries(vocabularyKeywords)) {
if (keywords.includes(keyword) && activeVocabularies.has(vocabUri)) {
return true;
}
}

// Core keywords are always enabled per JSON Schema spec.
// Check both 2019-09 and 2020-12 core vocabularies.
const core201909 = vocabularyKeywords['https://json-schema.org/draft/2019-09/vocab/core'];
const core202012 = vocabularyKeywords['https://json-schema.org/draft/2020-12/vocab/core'];

if (core201909.includes(keyword) || core202012.includes(keyword)) {
return true;
}

// Keyword not found in any vocabulary - disable it
return false;
}
Loading