From 78a08d95328f680061edb8fbdb52860b71e12e93 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Sun, 2 Nov 2025 23:41:46 +0100 Subject: [PATCH] use flat 2019-09 and 2020-12 schemas --- build/bundle-schemas.cjs | 276 +++++-- src/services/configuration.ts | 801 +-------------------- src/services/schemas/draft-2019-09-flat.ts | 309 ++++++++ src/services/schemas/draft-2020-12-flat.ts | 302 ++++++++ src/test/schema.test.ts | 42 +- 5 files changed, 886 insertions(+), 844 deletions(-) create mode 100644 src/services/schemas/draft-2019-09-flat.ts create mode 100644 src/services/schemas/draft-2020-12-flat.ts diff --git a/build/bundle-schemas.cjs b/build/bundle-schemas.cjs index 205ef091..220e2ff6 100644 --- a/build/bundle-schemas.cjs +++ b/build/bundle-schemas.cjs @@ -7,64 +7,248 @@ const fs = require('fs').promises; const Bundler = require("@hyperjump/json-schema-bundle"); (async function () { - bundle(`https://json-schema.org/draft/2019-09/schema`, 'draft09'); - bundle(`https://json-schema.org/draft/2020-12/schema`, 'draft12'); + bundle(`https://json-schema.org/draft/2019-09/schema`, 'draft-2019-09', 'https://json-schema.org/draft/2019-09'); + bundle(`https://json-schema.org/draft/2020-12/schema`, 'draft-2020-12', 'https://json-schema.org/draft/2020-12'); }()); -async function bundle(uri, filename) { - const metaSchema = await Bundler.get(uri); - const bundle = await Bundler.bundle(metaSchema); - const jsonified = JSON.stringify(bundle, null, 2).replace(/"undefined": ""/g, '"$dynamicAnchor": "meta"'); - const jsified = 'export default ' + printObject(JSON.parse(jsonified)); - fs.writeFile(`./${filename}.json`, jsonified, 'utf8'); - fs.writeFile(`./${filename}.js`, jsified, 'utf8'); +async function bundle(uri, filename, derivedURL) { + const metaSchema = await Bundler.get(uri); + let bundle = await Bundler.bundle(metaSchema); + bundle = JSON.parse(JSON.stringify(bundle, null, 2).replace(/"undefined": ""/g, '"$dynamicAnchor": "meta"')); + fs.writeFile(`./${filename}.json`, JSON.stringify(bundle, null, 2), 'utf8'); + bundle = flattenDraftMetaSchema(bundle); + const jsified = getCopyright(derivedURL) + 'export default ' + printObject(bundle); + fs.writeFile(`./${filename}-flat.json`, JSON.stringify(bundle, null, 2), 'utf8'); + fs.writeFile(`./src/services/schemas/${filename}-flat.ts`, jsified, 'utf8'); } +function getCopyright(derivedURL) { + return [ + '/*---------------------------------------------------------------------------------------------', + ' * Copyright (c) Microsoft Corporation. All rights reserved.', + ' * Licensed under the MIT License. See License.txt in the project root for license information.', + ' *--------------------------------------------------------------------------------------------*/', + '', + '// This file is generated - do not edit directly!', + '// Derived from ' + derivedURL, + ].join('\n') + '\n\n'; +} + function printLiteral(value) { - if (typeof value === 'string') { - return `'${value}'`; - } - return value; + if (typeof value === 'string') { + return `'${value}'`; + } + return value; } function printKey(value) { - if (value.match(/^[a-zA-Z_$][a-zA-Z0-9_$]*$/)) { - return `${value}`; - } - return `'${value}'`; + if (value.match(/^[a-zA-Z_$][a-zA-Z0-9_$]*$/)) { + return `${value}`; + } + return `'${value}'`; } function indent(level) { - return '\t'.repeat(level); + return '\t'.repeat(level); } function printObject(obj, indentLevel = 0) { - const result = []; - if (Array.isArray(obj)) { - result.push(`[`); - for (const item of obj) { - if (typeof item === 'object' && item !== null) { - result.push(`${indent(indentLevel + 1)}${printObject(item, indentLevel + 1)},`); - } else { - result.push(`${indent(indentLevel + 1)}${printLiteral(item)},`); - } - } - result.push(`${indent(indentLevel)}]`); - return result.join('\n'); - } - if (obj === null) { - result.push(`null`); - return result.join('\n'); - } - - result.push(`{`); - for (const [key, value] of Object.entries(obj)) { - if (typeof value === 'object' && value !== null) { - result.push(`${indent(indentLevel + 1)}${printKey(key)}: ${printObject(value, indentLevel + 1)},`); - } else { - result.push(`${indent(indentLevel + 1)}${printKey(key)}: ${printLiteral(value)},`); - } - } - result.push(`${indent(indentLevel)}}`); - return result.join('\n'); + const result = []; + if (Array.isArray(obj)) { + result.push(`[`); + for (const item of obj) { + if (typeof item === 'object' && item !== null) { + result.push(`${indent(indentLevel + 1)}${printObject(item, indentLevel + 1)},`); + } else { + result.push(`${indent(indentLevel + 1)}${printLiteral(item)},`); + } + } + result.push(`${indent(indentLevel)}]`); + return result.join('\n'); + } + if (obj === null) { + result.push(`null`); + return result.join('\n'); + } + + result.push(`{`); + for (const [key, value] of Object.entries(obj)) { + if (typeof value === 'object' && value !== null) { + result.push(`${indent(indentLevel + 1)}${printKey(key)}: ${printObject(value, indentLevel + 1)},`); + } else { + result.push(`${indent(indentLevel + 1)}${printKey(key)}: ${printLiteral(value)},`); + } + } + result.push(`${indent(indentLevel)}}`); + return result.join('\n'); +} +// flatten + +const DEFAULT_ANCHOR = 'meta'; + +function visit(node, callback) { + if (!node || typeof node !== 'object') return; + if (Array.isArray(node)) { + for (const item of node) { + visit(item, callback); + } + return; + } + + for (const key of Object.keys(node)) { + callback(node, key); + visit(node[key], callback); + } +} + +/** Recursively replace $dynamicRef:#meta with $ref:#meta */ +function replaceDynamicRefs(node, anchorName = DEFAULT_ANCHOR) { + visit(node, (n, k) => { + const v = n[k]; + if (k === '$dynamicRef' && v === '#' + anchorName) { + n['$ref'] = '#'; + delete n['$dynamicRef']; + }; + }); +} + +/** Recursively replace $dynamicRef:#meta with $ref:#meta */ +function replaceRecursiveRefs(node, anchorName = DEFAULT_ANCHOR) { + visit(node, (n, k) => { + const v = n[k]; + if (k === '$recursiveRef') { + n['$ref'] = v; + delete n['$recursiveRef']; + }; + }); +} + +/** Replace refs that point to a vocabulary */ +function replaceOldRefs(node, anchorName = DEFAULT_ANCHOR) { + visit(node, (n, k) => { + const v = n[k]; + if (k === '$ref' && typeof v === 'string' && v.startsWith(anchorName + '/')) { + const segments = v.split('#'); + if (segments.length === 2) { + n['$ref'] = `#${segments[1]}`; + } + } + }); +} + +/** Remove all $dynamicAnchor occurrences (except keep keyword definition property) */ +function stripDynamicAnchors(node) { + visit(node, (n, k) => { + if (k === '$dynamicAnchor') { + delete n[k]; + } + }); +} + +/** Collect vocabulary object definitions from $defs */ +function collectVocabularies(schema) { + const vocabularies = []; + const defs = schema.$defs || {}; + for (const [key, value] of Object.entries(defs)) { + if (value && typeof value === 'object' && !Array.isArray(value) && value.$id && value.$dynamicAnchor === DEFAULT_ANCHOR && value.properties) { + vocabularies.push(value); + } + } + return vocabularies; +} + +/** Merge properties from each vocabulary into root.properties (shallow) */ +function mergeVocabularyProperties(root, vocabularies) { + if (!root.properties) root.properties = {}; + replaceOldRefs(root); + for (const vocab of vocabularies) { + for (const [propName, propSchema] of Object.entries(vocab.properties || {})) { + if (!(propName in root.properties)) { + root.properties[propName] = propSchema; + } else { + // Simple heuristic: if both are objects, attempt shallow merge, else keep existing + const existing = root.properties[propName]; + if (isPlainObject(existing) && isPlainObject(propSchema)) { + root.properties[propName] = { ...existing, ...propSchema }; + } + } + } + } +} + +function isPlainObject(o) { + return !!o && typeof o === 'object' && !Array.isArray(o); +} + +/** Gather unified $defs from vocab $defs (only specific keys) */ +function buildUnifiedDefs(schema, vocabularies) { + const unified = schema.$defs && !referencesVocabulary(schema.$defs) ? { ...schema.$defs } : {}; + + function harvest(defsObj) { + if (!defsObj || typeof defsObj !== 'object') return; + for (const [k, v] of Object.entries(defsObj)) { + if (!(k in unified)) { + unified[k] = v; + } else { + console.warn(`Warning: duplicate definition for key ${k} found while building unified $defs. Keeping the first occurrence.`); + } + } + } + + for (const vocab of vocabularies) harvest(vocab.$defs); + + // Adjust schemaArray items dynamicRef->ref later with global replacement + return unified; +} + +function referencesVocabulary(defs) { + return Object.keys(defs).some(k => k.startsWith('https://json-schema.org/draft/')); +} + +function flattenDraftMetaSchema(original) { + // Clone to avoid mutating input reference + const schema = JSON.parse(JSON.stringify(original)); + + const anchorName = schema.$dynamicAnchor || DEFAULT_ANCHOR; + + // 1. Collect vocabulary schemas + const vocabularies = collectVocabularies(schema); + + // 2. Merge vocabulary properties into root + mergeVocabularyProperties(schema, vocabularies); + + // 3. Build unified $defs + const unifiedDefs = buildUnifiedDefs(schema, vocabularies); + + // 4. Remove top-level allOf (flatten composition) + delete schema.allOf; + + // 5. Remove vocabulary objects from $defs + if (schema.$defs) { + for (const k of Object.keys(schema.$defs)) { + if (schema.$defs[k] && schema.$defs[k].$id && schema.$defs[k].$dynamicAnchor === anchorName) { + delete schema.$defs[k]; + } + } + } + + // 6. Assign unified defs + schema.$defs = unifiedDefs; + + // 7. Convert dynamic recursion markers + replaceDynamicRefs(schema, anchorName); + replaceRecursiveRefs(schema, anchorName); + stripDynamicAnchors(schema); + + // 8. Add static anchor at root + delete schema.$dynamicAnchor; + + // 9. Update title to signal flattening + if (schema.title) { + schema.title = '(Flattened static) ' + schema.title; + } else { + schema.title = 'Flattened Draft 2020-12 meta-schema'; + } + + return schema; } diff --git a/src/services/configuration.ts b/src/services/configuration.ts index 86220e03..ce77d34c 100644 --- a/src/services/configuration.ts +++ b/src/services/configuration.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { ISchemaContributions } from './jsonSchemaService'; +import draft201909Flat from './schemas/draft-2019-09-flat'; +import draft202012Flat from './schemas/draft-2020-12-flat'; import * as l10n from '@vscode/l10n'; @@ -458,803 +460,8 @@ export const schemaContributions: ISchemaContributions = { }, 'default': true }, - 'https://json-schema.org/draft/2020-12/schema': { - $id: 'https://json-schema.org/draft/2020-12/schema', - $schema: 'https://json-schema.org/draft/2020-12/schema', - title: 'Core and Validation specifications meta-schema', - $dynamicAnchor: 'meta', - allOf: [ - { - $ref: 'meta/core', - }, - { - $ref: 'meta/applicator', - }, - { - $ref: 'meta/unevaluated', - }, - { - $ref: 'meta/validation', - }, - { - $ref: 'meta/meta-data', - }, - { - $ref: 'meta/format-annotation', - }, - { - $ref: 'meta/content', - }, - ], - type: [ - 'object', - 'boolean', - ], - properties: { - definitions: { - $comment: 'While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.', - type: 'object', - additionalProperties: { - $dynamicRef: '#meta', - }, - default: { - }, - }, - dependencies: { - $comment: '"dependencies" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to "dependentSchemas" and "dependentRequired"', - type: 'object', - additionalProperties: { - anyOf: [ - { - $dynamicRef: '#meta', - }, - { - $ref: 'meta/validation#/$defs/stringArray', - }, - ], - }, - }, - }, - $defs: { - 'https://json-schema.org/draft/2020-12/meta/core': { - $id: 'https://json-schema.org/draft/2020-12/meta/core', - title: 'Core vocabulary meta-schema', - $dynamicAnchor: 'meta', - type: [ - 'object', - 'boolean', - ], - properties: { - $id: { - type: 'string', - format: 'uri-reference', - $comment: 'Non-empty fragments not allowed.', - pattern: '^[^#]*#?$', - }, - $schema: { - type: 'string', - format: 'uri', - }, - $anchor: { - type: 'string', - pattern: '^[A-Za-z_][-A-Za-z0-9._]*$', - }, - $ref: { - type: 'string', - format: 'uri-reference', - }, - $dynamicRef: { - type: 'string', - format: 'uri-reference', - }, - $dynamicAnchor: { - type: 'string', - pattern: '^[A-Za-z_][-A-Za-z0-9._]*$', - }, - $vocabulary: { - type: 'object', - propertyNames: { - type: 'string', - format: 'uri', - }, - additionalProperties: { - type: 'boolean', - }, - }, - $comment: { - type: 'string', - }, - $defs: { - type: 'object', - additionalProperties: { - $dynamicRef: '#meta', - }, - default: { - }, - }, - }, - }, - 'https://json-schema.org/draft/2020-12/meta/applicator': { - $id: 'https://json-schema.org/draft/2020-12/meta/applicator', - title: 'Applicator vocabulary meta-schema', - $dynamicAnchor: 'meta', - type: [ - 'object', - 'boolean', - ], - properties: { - prefixItems: { - $ref: '#/$defs/schemaArray', - }, - items: { - $dynamicRef: '#meta', - }, - contains: { - $dynamicRef: '#meta', - }, - additionalProperties: { - $dynamicRef: '#meta', - }, - properties: { - type: 'object', - additionalProperties: { - $dynamicRef: '#meta', - }, - default: { - }, - }, - patternProperties: { - type: 'object', - additionalProperties: { - $dynamicRef: '#meta', - }, - propertyNames: { - format: 'regex', - }, - default: { - }, - }, - dependentSchemas: { - type: 'object', - additionalProperties: { - $dynamicRef: '#meta', - }, - }, - propertyNames: { - $dynamicRef: '#meta', - }, - if: { - $dynamicRef: '#meta', - }, - then: { - $dynamicRef: '#meta', - }, - else: { - $dynamicRef: '#meta', - }, - allOf: { - $ref: '#/$defs/schemaArray', - }, - anyOf: { - $ref: '#/$defs/schemaArray', - }, - oneOf: { - $ref: '#/$defs/schemaArray', - }, - not: { - $dynamicRef: '#meta', - }, - }, - $defs: { - schemaArray: { - type: 'array', - minItems: 1, - items: { - $dynamicRef: '#meta', - }, - }, - }, - }, - 'https://json-schema.org/draft/2020-12/meta/unevaluated': { - $id: 'https://json-schema.org/draft/2020-12/meta/unevaluated', - title: 'Unevaluated applicator vocabulary meta-schema', - $dynamicAnchor: 'meta', - type: [ - 'object', - 'boolean', - ], - properties: { - unevaluatedItems: { - $dynamicRef: '#meta', - }, - unevaluatedProperties: { - $dynamicRef: '#meta', - }, - }, - }, - 'https://json-schema.org/draft/2020-12/meta/validation': { - $id: 'https://json-schema.org/draft/2020-12/meta/validation', - title: 'Validation vocabulary meta-schema', - $dynamicAnchor: 'meta', - type: [ - 'object', - 'boolean', - ], - properties: { - multipleOf: { - type: 'number', - exclusiveMinimum: 0, - }, - maximum: { - type: 'number', - }, - exclusiveMaximum: { - type: 'number', - }, - minimum: { - type: 'number', - }, - exclusiveMinimum: { - type: 'number', - }, - maxLength: { - $ref: '#/$defs/nonNegativeInteger', - }, - minLength: { - $ref: '#/$defs/nonNegativeIntegerDefault0', - }, - pattern: { - type: 'string', - format: 'regex', - }, - maxItems: { - $ref: '#/$defs/nonNegativeInteger', - }, - minItems: { - $ref: '#/$defs/nonNegativeIntegerDefault0', - }, - uniqueItems: { - type: 'boolean', - default: false, - }, - maxContains: { - $ref: '#/$defs/nonNegativeInteger', - }, - minContains: { - $ref: '#/$defs/nonNegativeInteger', - default: 1, - }, - maxProperties: { - $ref: '#/$defs/nonNegativeInteger', - }, - minProperties: { - $ref: '#/$defs/nonNegativeIntegerDefault0', - }, - required: { - $ref: '#/$defs/stringArray', - }, - dependentRequired: { - type: 'object', - additionalProperties: { - $ref: '#/$defs/stringArray', - }, - }, - const: true, - enum: { - type: 'array', - items: true, - }, - type: { - anyOf: [ - { - $ref: '#/$defs/simpleTypes', - }, - { - type: 'array', - items: { - $ref: '#/$defs/simpleTypes', - }, - minItems: 1, - uniqueItems: true, - }, - ], - }, - }, - $defs: { - nonNegativeInteger: { - type: 'integer', - minimum: 0, - }, - nonNegativeIntegerDefault0: { - $ref: '#/$defs/nonNegativeInteger', - default: 0, - }, - simpleTypes: { - enum: [ - 'array', - 'boolean', - 'integer', - 'null', - 'number', - 'object', - 'string', - ], - }, - stringArray: { - type: 'array', - items: { - type: 'string', - }, - uniqueItems: true, - default: [ - ], - }, - }, - }, - 'https://json-schema.org/draft/2020-12/meta/meta-data': { - $id: 'https://json-schema.org/draft/2020-12/meta/meta-data', - title: 'Meta-data vocabulary meta-schema', - $dynamicAnchor: 'meta', - type: [ - 'object', - 'boolean', - ], - properties: { - title: { - type: 'string', - }, - description: { - type: 'string', - }, - default: true, - deprecated: { - type: 'boolean', - default: false, - }, - readOnly: { - type: 'boolean', - default: false, - }, - writeOnly: { - type: 'boolean', - default: false, - }, - examples: { - type: 'array', - items: true, - }, - }, - }, - 'https://json-schema.org/draft/2020-12/meta/format-annotation': { - $id: 'https://json-schema.org/draft/2020-12/meta/format-annotation', - title: 'Format vocabulary meta-schema for annotation results', - $dynamicAnchor: 'meta', - type: [ - 'object', - 'boolean', - ], - properties: { - format: { - type: 'string', - }, - }, - }, - 'https://json-schema.org/draft/2020-12/meta/content': { - $id: 'https://json-schema.org/draft/2020-12/meta/content', - title: 'Content vocabulary meta-schema', - $dynamicAnchor: 'meta', - type: [ - 'object', - 'boolean', - ], - properties: { - contentMediaType: { - type: 'string', - }, - contentEncoding: { - type: 'string', - }, - contentSchema: { - $dynamicRef: '#meta', - }, - }, - }, - }, - }, - 'https://json-schema.org/draft/2019-09/schema': { - $id: 'https://json-schema.org/draft/2019-09/schema', - $schema: 'https://json-schema.org/draft/2019-09/schema', - $dynamicAnchor: 'meta', - title: 'Core and Validation specifications meta-schema', - allOf: [ - { - $ref: 'meta/core', - }, - { - $ref: 'meta/applicator', - }, - { - $ref: 'meta/validation', - }, - { - $ref: 'meta/meta-data', - }, - { - $ref: 'meta/format', - }, - { - $ref: 'meta/content', - }, - ], - type: [ - 'object', - 'boolean', - ], - properties: { - definitions: { - $comment: 'While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.', - type: 'object', - additionalProperties: { - $recursiveRef: '#', - }, - default: { - }, - }, - dependencies: { - $comment: '"dependencies" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to "dependentSchemas" and "dependentRequired"', - type: 'object', - additionalProperties: { - anyOf: [ - { - $recursiveRef: '#', - }, - { - $ref: 'meta/validation#/$defs/stringArray', - }, - ], - }, - }, - }, - $defs: { - 'https://json-schema.org/draft/2019-09/meta/core': { - $id: 'https://json-schema.org/draft/2019-09/meta/core', - $dynamicAnchor: 'meta', - title: 'Core vocabulary meta-schema', - type: [ - 'object', - 'boolean', - ], - properties: { - $id: { - type: 'string', - format: 'uri-reference', - $comment: 'Non-empty fragments not allowed.', - pattern: '^[^#]*#?$', - }, - $schema: { - type: 'string', - format: 'uri', - }, - $anchor: { - type: 'string', - pattern: '^[A-Za-z][-A-Za-z0-9.:_]*$', - }, - $ref: { - type: 'string', - format: 'uri-reference', - }, - $recursiveRef: { - type: 'string', - format: 'uri-reference', - }, - $recursiveAnchor: { - type: 'boolean', - default: false, - }, - $vocabulary: { - type: 'object', - propertyNames: { - type: 'string', - format: 'uri', - }, - additionalProperties: { - type: 'boolean', - }, - }, - $comment: { - type: 'string', - }, - $defs: { - type: 'object', - additionalProperties: { - $recursiveRef: '#', - }, - default: { - }, - }, - }, - }, - 'https://json-schema.org/draft/2019-09/meta/applicator': { - $id: 'https://json-schema.org/draft/2019-09/meta/applicator', - $dynamicAnchor: 'meta', - title: 'Applicator vocabulary meta-schema', - properties: { - additionalItems: { - $recursiveRef: '#', - }, - unevaluatedItems: { - $recursiveRef: '#', - }, - items: { - anyOf: [ - { - $recursiveRef: '#', - }, - { - $ref: '#/$defs/schemaArray', - }, - ], - }, - contains: { - $recursiveRef: '#', - }, - additionalProperties: { - $recursiveRef: '#', - }, - unevaluatedProperties: { - $recursiveRef: '#', - }, - properties: { - type: 'object', - additionalProperties: { - $recursiveRef: '#', - }, - default: { - }, - }, - patternProperties: { - type: 'object', - additionalProperties: { - $recursiveRef: '#', - }, - propertyNames: { - format: 'regex', - }, - default: { - }, - }, - dependentSchemas: { - type: 'object', - additionalProperties: { - $recursiveRef: '#', - }, - }, - propertyNames: { - $recursiveRef: '#', - }, - if: { - $recursiveRef: '#', - }, - then: { - $recursiveRef: '#', - }, - else: { - $recursiveRef: '#', - }, - allOf: { - $ref: '#/$defs/schemaArray', - }, - anyOf: { - $ref: '#/$defs/schemaArray', - }, - oneOf: { - $ref: '#/$defs/schemaArray', - }, - not: { - $recursiveRef: '#', - }, - }, - $defs: { - schemaArray: { - type: 'array', - minItems: 1, - items: { - $recursiveRef: '#', - }, - }, - }, - }, - 'https://json-schema.org/draft/2019-09/meta/validation': { - $id: 'https://json-schema.org/draft/2019-09/meta/validation', - $dynamicAnchor: 'meta', - title: 'Validation vocabulary meta-schema', - type: [ - 'object', - 'boolean', - ], - properties: { - multipleOf: { - type: 'number', - exclusiveMinimum: 0, - }, - maximum: { - type: 'number', - }, - exclusiveMaximum: { - type: 'number', - }, - minimum: { - type: 'number', - }, - exclusiveMinimum: { - type: 'number', - }, - maxLength: { - $ref: '#/$defs/nonNegativeInteger', - }, - minLength: { - $ref: '#/$defs/nonNegativeIntegerDefault0', - }, - pattern: { - type: 'string', - format: 'regex', - }, - maxItems: { - $ref: '#/$defs/nonNegativeInteger', - }, - minItems: { - $ref: '#/$defs/nonNegativeIntegerDefault0', - }, - uniqueItems: { - type: 'boolean', - default: false, - }, - maxContains: { - $ref: '#/$defs/nonNegativeInteger', - }, - minContains: { - $ref: '#/$defs/nonNegativeInteger', - default: 1, - }, - maxProperties: { - $ref: '#/$defs/nonNegativeInteger', - }, - minProperties: { - $ref: '#/$defs/nonNegativeIntegerDefault0', - }, - required: { - $ref: '#/$defs/stringArray', - }, - dependentRequired: { - type: 'object', - additionalProperties: { - $ref: '#/$defs/stringArray', - }, - }, - const: true, - enum: { - type: 'array', - items: true, - }, - type: { - anyOf: [ - { - $ref: '#/$defs/simpleTypes', - }, - { - type: 'array', - items: { - $ref: '#/$defs/simpleTypes', - }, - minItems: 1, - uniqueItems: true, - }, - ], - }, - }, - $defs: { - nonNegativeInteger: { - type: 'integer', - minimum: 0, - }, - nonNegativeIntegerDefault0: { - $ref: '#/$defs/nonNegativeInteger', - default: 0, - }, - simpleTypes: { - enum: [ - 'array', - 'boolean', - 'integer', - 'null', - 'number', - 'object', - 'string', - ], - }, - stringArray: { - type: 'array', - items: { - type: 'string', - }, - uniqueItems: true, - default: [ - ], - }, - }, - }, - 'https://json-schema.org/draft/2019-09/meta/meta-data': { - $id: 'https://json-schema.org/draft/2019-09/meta/meta-data', - $dynamicAnchor: 'meta', - title: 'Meta-data vocabulary meta-schema', - type: [ - 'object', - 'boolean', - ], - properties: { - title: { - type: 'string', - }, - description: { - type: 'string', - }, - default: true, - deprecated: { - type: 'boolean', - default: false, - }, - readOnly: { - type: 'boolean', - default: false, - }, - writeOnly: { - type: 'boolean', - default: false, - }, - examples: { - type: 'array', - items: true, - }, - }, - }, - 'https://json-schema.org/draft/2019-09/meta/format': { - $id: 'https://json-schema.org/draft/2019-09/meta/format', - $dynamicAnchor: 'meta', - title: 'Format vocabulary meta-schema', - type: [ - 'object', - 'boolean', - ], - properties: { - format: { - type: 'string', - }, - }, - }, - 'https://json-schema.org/draft/2019-09/meta/content': { - $id: 'https://json-schema.org/draft/2019-09/meta/content', - $dynamicAnchor: 'meta', - title: 'Content vocabulary meta-schema', - type: [ - 'object', - 'boolean', - ], - properties: { - contentMediaType: { - type: 'string', - }, - contentEncoding: { - type: 'string', - }, - contentSchema: { - $recursiveRef: '#', - }, - }, - }, - }, - } - - + 'https://json-schema.org/draft/2020-12/schema': draft202012Flat, + 'https://json-schema.org/draft/2019-09/schema': draft201909Flat } }; const descriptions: { [prop: string]: string } = { diff --git a/src/services/schemas/draft-2019-09-flat.ts b/src/services/schemas/draft-2019-09-flat.ts new file mode 100644 index 00000000..25c46b17 --- /dev/null +++ b/src/services/schemas/draft-2019-09-flat.ts @@ -0,0 +1,309 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// This file is generated - do not edit directly! +// Derived from https://json-schema.org/draft/2019-09 + +export default { + $id: 'https://json-schema.org/draft/2019-09/schema', + $schema: 'https://json-schema.org/draft/2019-09/schema', + title: '(Flattened static) Core and Validation specifications meta-schema', + type: [ + 'object', + 'boolean', + ], + properties: { + definitions: { + $comment: 'While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.', + type: 'object', + additionalProperties: { + $ref: '#', + }, + default: { + }, + }, + dependencies: { + $comment: '"dependencies" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to "dependentSchemas" and "dependentRequired"', + type: 'object', + additionalProperties: { + anyOf: [ + { + $ref: '#', + }, + { + $ref: '#/$defs/stringArray', + }, + ], + }, + }, + $id: { + type: 'string', + format: 'uri-reference', + $comment: 'Non-empty fragments not allowed.', + pattern: '^[^#]*#?$', + }, + $schema: { + type: 'string', + format: 'uri', + }, + $anchor: { + type: 'string', + pattern: '^[A-Za-z][-A-Za-z0-9.:_]*$', + }, + $ref: { + type: 'string', + format: 'uri-reference', + }, + $recursiveAnchor: { + type: 'boolean', + default: false, + }, + $vocabulary: { + type: 'object', + propertyNames: { + type: 'string', + format: 'uri', + }, + additionalProperties: { + type: 'boolean', + }, + }, + $comment: { + type: 'string', + }, + $defs: { + type: 'object', + additionalProperties: { + $ref: '#', + }, + default: { + }, + }, + additionalItems: { + $ref: '#', + }, + unevaluatedItems: { + $ref: '#', + }, + items: { + anyOf: [ + { + $ref: '#', + }, + { + $ref: '#/$defs/schemaArray', + }, + ], + }, + contains: { + $ref: '#', + }, + additionalProperties: { + $ref: '#', + }, + unevaluatedProperties: { + $ref: '#', + }, + properties: { + type: 'object', + additionalProperties: { + $ref: '#', + }, + default: { + }, + }, + patternProperties: { + type: 'object', + additionalProperties: { + $ref: '#', + }, + propertyNames: { + format: 'regex', + }, + default: { + }, + }, + dependentSchemas: { + type: 'object', + additionalProperties: { + $ref: '#', + }, + }, + propertyNames: { + $ref: '#', + }, + if: { + $ref: '#', + }, + then: { + $ref: '#', + }, + else: { + $ref: '#', + }, + allOf: { + $ref: '#/$defs/schemaArray', + }, + anyOf: { + $ref: '#/$defs/schemaArray', + }, + oneOf: { + $ref: '#/$defs/schemaArray', + }, + not: { + $ref: '#', + }, + multipleOf: { + type: 'number', + exclusiveMinimum: 0, + }, + maximum: { + type: 'number', + }, + exclusiveMaximum: { + type: 'number', + }, + minimum: { + type: 'number', + }, + exclusiveMinimum: { + type: 'number', + }, + maxLength: { + $ref: '#/$defs/nonNegativeInteger', + }, + minLength: { + $ref: '#/$defs/nonNegativeIntegerDefault0', + }, + pattern: { + type: 'string', + format: 'regex', + }, + maxItems: { + $ref: '#/$defs/nonNegativeInteger', + }, + minItems: { + $ref: '#/$defs/nonNegativeIntegerDefault0', + }, + uniqueItems: { + type: 'boolean', + default: false, + }, + maxContains: { + $ref: '#/$defs/nonNegativeInteger', + }, + minContains: { + $ref: '#/$defs/nonNegativeInteger', + default: 1, + }, + maxProperties: { + $ref: '#/$defs/nonNegativeInteger', + }, + minProperties: { + $ref: '#/$defs/nonNegativeIntegerDefault0', + }, + required: { + $ref: '#/$defs/stringArray', + }, + dependentRequired: { + type: 'object', + additionalProperties: { + $ref: '#/$defs/stringArray', + }, + }, + const: true, + enum: { + type: 'array', + items: true, + }, + type: { + anyOf: [ + { + $ref: '#/$defs/simpleTypes', + }, + { + type: 'array', + items: { + $ref: '#/$defs/simpleTypes', + }, + minItems: 1, + uniqueItems: true, + }, + ], + }, + title: { + type: 'string', + }, + description: { + type: 'string', + }, + default: true, + deprecated: { + type: 'boolean', + default: false, + }, + readOnly: { + type: 'boolean', + default: false, + }, + writeOnly: { + type: 'boolean', + default: false, + }, + examples: { + type: 'array', + items: true, + }, + format: { + type: 'string', + }, + contentMediaType: { + type: 'string', + }, + contentEncoding: { + type: 'string', + }, + contentSchema: { + $ref: '#', + }, + }, + $defs: { + schemaArray: { + type: 'array', + minItems: 1, + items: { + $ref: '#', + }, + }, + nonNegativeInteger: { + type: 'integer', + minimum: 0, + }, + nonNegativeIntegerDefault0: { + $ref: '#/$defs/nonNegativeInteger', + default: 0, + }, + simpleTypes: { + enum: [ + 'array', + 'boolean', + 'integer', + 'null', + 'number', + 'object', + 'string', + ], + }, + stringArray: { + type: 'array', + items: { + type: 'string', + }, + uniqueItems: true, + default: [ + ], + }, + }, +} \ No newline at end of file diff --git a/src/services/schemas/draft-2020-12-flat.ts b/src/services/schemas/draft-2020-12-flat.ts new file mode 100644 index 00000000..f6ca94a5 --- /dev/null +++ b/src/services/schemas/draft-2020-12-flat.ts @@ -0,0 +1,302 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// This file is generated - do not edit directly! +// Derived from https://json-schema.org/draft/2020-12 + +export default { + $id: 'https://json-schema.org/draft/2020-12/schema', + $schema: 'https://json-schema.org/draft/2020-12/schema', + title: '(Flattened static) Core and Validation specifications meta-schema', + type: [ + 'object', + 'boolean', + ], + properties: { + definitions: { + $comment: 'While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.', + type: 'object', + additionalProperties: { + $ref: '#', + }, + default: { + }, + }, + dependencies: { + $comment: '"dependencies" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to "dependentSchemas" and "dependentRequired"', + type: 'object', + additionalProperties: { + anyOf: [ + { + $ref: '#', + }, + { + $ref: '#/$defs/stringArray', + }, + ], + }, + }, + $id: { + type: 'string', + format: 'uri-reference', + $comment: 'Non-empty fragments not allowed.', + pattern: '^[^#]*#?$', + }, + $schema: { + type: 'string', + format: 'uri', + }, + $anchor: { + type: 'string', + pattern: '^[A-Za-z_][-A-Za-z0-9._]*$', + }, + $ref: { + type: 'string', + format: 'uri-reference', + }, + $dynamicRef: { + type: 'string', + format: 'uri-reference', + }, + $vocabulary: { + type: 'object', + propertyNames: { + type: 'string', + format: 'uri', + }, + additionalProperties: { + type: 'boolean', + }, + }, + $comment: { + type: 'string', + }, + $defs: { + type: 'object', + additionalProperties: { + $ref: '#', + }, + default: { + }, + }, + prefixItems: { + $ref: '#/$defs/schemaArray', + }, + items: { + $ref: '#', + }, + contains: { + $ref: '#', + }, + additionalProperties: { + $ref: '#', + }, + properties: { + type: 'object', + additionalProperties: { + $ref: '#', + }, + default: { + }, + }, + patternProperties: { + type: 'object', + additionalProperties: { + $ref: '#', + }, + propertyNames: { + format: 'regex', + }, + default: { + }, + }, + dependentSchemas: { + type: 'object', + additionalProperties: { + $ref: '#', + }, + }, + propertyNames: { + $ref: '#', + }, + if: { + $ref: '#', + }, + then: { + $ref: '#', + }, + else: { + $ref: '#', + }, + allOf: { + $ref: '#/$defs/schemaArray', + }, + anyOf: { + $ref: '#/$defs/schemaArray', + }, + oneOf: { + $ref: '#/$defs/schemaArray', + }, + not: { + $ref: '#', + }, + unevaluatedItems: { + $ref: '#', + }, + unevaluatedProperties: { + $ref: '#', + }, + multipleOf: { + type: 'number', + exclusiveMinimum: 0, + }, + maximum: { + type: 'number', + }, + exclusiveMaximum: { + type: 'number', + }, + minimum: { + type: 'number', + }, + exclusiveMinimum: { + type: 'number', + }, + maxLength: { + $ref: '#/$defs/nonNegativeInteger', + }, + minLength: { + $ref: '#/$defs/nonNegativeIntegerDefault0', + }, + pattern: { + type: 'string', + format: 'regex', + }, + maxItems: { + $ref: '#/$defs/nonNegativeInteger', + }, + minItems: { + $ref: '#/$defs/nonNegativeIntegerDefault0', + }, + uniqueItems: { + type: 'boolean', + default: false, + }, + maxContains: { + $ref: '#/$defs/nonNegativeInteger', + }, + minContains: { + $ref: '#/$defs/nonNegativeInteger', + default: 1, + }, + maxProperties: { + $ref: '#/$defs/nonNegativeInteger', + }, + minProperties: { + $ref: '#/$defs/nonNegativeIntegerDefault0', + }, + required: { + $ref: '#/$defs/stringArray', + }, + dependentRequired: { + type: 'object', + additionalProperties: { + $ref: '#/$defs/stringArray', + }, + }, + const: true, + enum: { + type: 'array', + items: true, + }, + type: { + anyOf: [ + { + $ref: '#/$defs/simpleTypes', + }, + { + type: 'array', + items: { + $ref: '#/$defs/simpleTypes', + }, + minItems: 1, + uniqueItems: true, + }, + ], + }, + title: { + type: 'string', + }, + description: { + type: 'string', + }, + default: true, + deprecated: { + type: 'boolean', + default: false, + }, + readOnly: { + type: 'boolean', + default: false, + }, + writeOnly: { + type: 'boolean', + default: false, + }, + examples: { + type: 'array', + items: true, + }, + format: { + type: 'string', + }, + contentMediaType: { + type: 'string', + }, + contentEncoding: { + type: 'string', + }, + contentSchema: { + $ref: '#', + }, + }, + $defs: { + schemaArray: { + type: 'array', + minItems: 1, + items: { + $ref: '#', + }, + }, + nonNegativeInteger: { + type: 'integer', + minimum: 0, + }, + nonNegativeIntegerDefault0: { + $ref: '#/$defs/nonNegativeInteger', + default: 0, + }, + simpleTypes: { + enum: [ + 'array', + 'boolean', + 'integer', + 'null', + 'number', + 'object', + 'string', + ], + }, + stringArray: { + type: 'array', + items: { + type: 'string', + }, + uniqueItems: true, + default: [ + ], + }, + }, +} \ No newline at end of file diff --git a/src/test/schema.test.ts b/src/test/schema.test.ts index e4b4492f..e34950c4 100644 --- a/src/test/schema.test.ts +++ b/src/test/schema.test.ts @@ -2047,11 +2047,51 @@ suite('JSON Schema', () => { { const { textDoc, jsonDoc } = toDocument('{ }', undefined, 'foo://bar/folder/foo.json'); const res = await ls.doValidation(textDoc, jsonDoc); - console.log(res); } }); + test('validate against draft-2019-09', async function () { + const schema: JSONSchema = { + $schema: 'https://json-schema.org/draft/2019-09/schema', + type: 'object', + properties: { + name: { + type: 'string', + minLength: 4, + } + }, + required: ['name'] + }; + const ls = getLanguageService({}); + { + const { textDoc, jsonDoc } = toDocument(JSON.stringify(schema)); + assert.deepStrictEqual(jsonDoc.syntaxErrors, []); + const resolveError = await ls.doValidation(textDoc, jsonDoc, { schemaRequest: 'error' }); + assert.deepStrictEqual(resolveError, []); + } + }); + test('validate against draft-2020-12', async function () { + const schema: JSONSchema = { + $schema: 'https://json-schema.org/draft/2020-12/schema', + type: 'object', + properties: { + name: { + type: 'string', + minLength: 4, + } + }, + required: ['name'] + }; + + const ls = getLanguageService({}); + { + const { textDoc, jsonDoc } = toDocument(JSON.stringify(schema)); + assert.deepStrictEqual(jsonDoc.syntaxErrors, []); + const resolveError = await ls.doValidation(textDoc, jsonDoc, { schemaRequest: 'error' }); + assert.deepStrictEqual(resolveError, []); + } + }); });