Skip to content
Open
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
9 changes: 9 additions & 0 deletions src/jsonLanguageTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ export {
TextDocumentEdit, VersionedTextDocumentIdentifier, DocumentHighlightKind
};

/**
* Represents active JSON Schema vocabularies with their required/optional status.
* Key = vocabulary URI, Value = true if required, false if optional.
* Both required and optional vocabularies are active; the boolean indicates
* whether the validator must understand it (true) or should understand it (false).
* @since 2019-09
*/
export type Vocabularies = Map<string, boolean>;

/**
* 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;
$vocabulary?: { [uri: string]: boolean };

// schema 2020-12
prefixItems?: JSONSchemaRef[];
Expand Down
287 changes: 195 additions & 92 deletions src/parser/jsonParser.ts

Large diffs are not rendered by default.

445 changes: 346 additions & 99 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
152 changes: 152 additions & 0 deletions src/services/vocabularies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { SchemaDraft } from '../jsonLanguageTypes';

/*
* 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?: Map<string, boolean>
): 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;
}

/*
* Checks if format validation should produce assertion errors.
*
* According to JSON Schema 2020-12:
* - format-annotation: format is purely informational, no validation errors
* - format-assertion: format must be validated and can produce errors
*
* For backwards compatibility:
* - If no vocabularies are specified and no explicit 2019-09+ draft, format asserts
* - 2019-09 format vocabulary is annotation-only
* - 2020-12 format-assertion vocabulary asserts
* - 2020-12 format-annotation vocabulary does not assert
*
* @param activeVocabularies Map of active vocabulary URIs to required flag, or undefined if no constraints
* @param schemaDraft The explicitly declared schema draft (only set when $schema was present), or undefined
* @returns true if format validation should produce errors, false if annotation-only
*/
export function isFormatAssertionEnabled(activeVocabularies?: Map<string, boolean>, schemaDraft?: SchemaDraft): boolean {
// If vocabulary constraints are present, use them to determine format assertion
if (activeVocabularies && activeVocabularies.size > 0) {
// 2020-12 format-assertion explicitly enables format validation errors
if (activeVocabularies.has('https://json-schema.org/draft/2020-12/vocab/format-assertion')) {
return true;
}

// 2019-09 format vocabulary is annotation-only per spec
if (activeVocabularies.has('https://json-schema.org/draft/2019-09/vocab/format')) {
return false;
}

// 2020-12 format-annotation is annotation-only, no assertion
if (activeVocabularies.has('https://json-schema.org/draft/2020-12/vocab/format-annotation')) {
return false;
}

// No format vocabulary active - no assertion
return false;
}

// No vocabulary constraints:
// For explicitly declared 2019-09+ schemas, format is annotation-only by default per spec.
// For older drafts or no explicit $schema, format asserts for backward compatibility.
if (schemaDraft !== undefined && schemaDraft >= SchemaDraft.v2019_09) {
return false;
}

return true;
}
Loading