vscode-json-languageservice 6.0.0-next.1 → 6.0.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/lib/esm/jsonLanguageTypes.d.ts +8 -0
- package/lib/esm/jsonSchema.d.ts +71 -2
- package/lib/esm/parser/jsonParser.js +209 -90
- package/lib/esm/services/jsonLinks.js +93 -2
- package/lib/esm/services/jsonSchemaService.js +635 -96
- package/lib/esm/services/jsonValidation.js +2 -2
- package/lib/esm/services/vocabularies.js +139 -0
- package/package.json +10 -10
|
@@ -7,12 +7,15 @@ import { isNumber, equals, isBoolean, isString, isDefined, isObject } from '../u
|
|
|
7
7
|
import { extendedRegExp, stringLength } from '../utils/strings.js';
|
|
8
8
|
import { ErrorCode, Diagnostic, DiagnosticSeverity, Range, SchemaDraft } from '../jsonLanguageTypes.js';
|
|
9
9
|
import { URI } from 'vscode-uri';
|
|
10
|
+
import { isKeywordEnabled, isFormatAssertionEnabled } from '../services/vocabularies.js';
|
|
10
11
|
import * as l10n from '@vscode/l10n';
|
|
11
12
|
const formats = {
|
|
12
13
|
'color-hex': { errorMessage: l10n.t('Invalid color format. Use #RGB, #RGBA, #RRGGBB or #RRGGBBAA.'), pattern: /^#([0-9A-Fa-f]{3,4}|([0-9A-Fa-f]{2}){3,4})$/ },
|
|
13
14
|
'date-time': { errorMessage: l10n.t('String is not a RFC3339 date-time.'), pattern: /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)([01][0-9]|2[0-3]):([0-5][0-9]))$/i },
|
|
14
15
|
'date': { errorMessage: l10n.t('String is not a RFC3339 date.'), pattern: /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/i },
|
|
15
16
|
'time': { errorMessage: l10n.t('String is not a RFC3339 time.'), pattern: /^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)([01][0-9]|2[0-3]):([0-5][0-9]))$/i },
|
|
17
|
+
'duration': { errorMessage: l10n.t('String is not an ISO 8601 duration.'), pattern: /^P(?!$)(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+(\.\d+)?S)?)?$/ },
|
|
18
|
+
'uuid': { errorMessage: l10n.t('String is not a valid UUID.'), pattern: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i },
|
|
16
19
|
'email': { errorMessage: l10n.t('String is not an e-mail address.'), pattern: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}))$/ },
|
|
17
20
|
'hostname': { errorMessage: l10n.t('String is not a hostname.'), pattern: /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i },
|
|
18
21
|
'ipv4': { errorMessage: l10n.t('String is not an IPv4 address.'), pattern: /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/ },
|
|
@@ -130,8 +133,10 @@ const schemaDraftFromId = {
|
|
|
130
133
|
'https://json-schema.org/draft/2020-12/schema': SchemaDraft.v2020_12
|
|
131
134
|
};
|
|
132
135
|
class EvaluationContext {
|
|
133
|
-
constructor(schemaDraft) {
|
|
136
|
+
constructor(schemaDraft, activeVocabularies, explicitSchemaDraft) {
|
|
134
137
|
this.schemaDraft = schemaDraft;
|
|
138
|
+
this.activeVocabularies = activeVocabularies;
|
|
139
|
+
this.explicitSchemaDraft = explicitSchemaDraft;
|
|
135
140
|
}
|
|
136
141
|
}
|
|
137
142
|
class SchemaCollector {
|
|
@@ -264,10 +269,14 @@ export class JSONDocument {
|
|
|
264
269
|
doVisit(this.root);
|
|
265
270
|
}
|
|
266
271
|
}
|
|
267
|
-
validate(textDocument, schema, severity = DiagnosticSeverity.Warning, schemaDraft) {
|
|
272
|
+
validate(textDocument, schema, severity = DiagnosticSeverity.Warning, schemaDraft, activeVocabularies) {
|
|
268
273
|
if (this.root && schema) {
|
|
269
274
|
const validationResult = new ValidationResult();
|
|
270
|
-
|
|
275
|
+
const { explicitDraft, effectiveDraft } = resolveDrafts(schema, schemaDraft);
|
|
276
|
+
const context = new EvaluationContext(effectiveDraft, activeVocabularies, explicitDraft);
|
|
277
|
+
const schemaStack = [];
|
|
278
|
+
const schemaRoots = [schema];
|
|
279
|
+
validate(this.root, schema, validationResult, NoOpSchemaCollector.instance, context, schemaStack, schemaRoots);
|
|
271
280
|
return validationResult.problems.map(p => {
|
|
272
281
|
const range = Range.create(textDocument.positionAt(p.location.offset), textDocument.positionAt(p.location.offset + p.location.length));
|
|
273
282
|
return Diagnostic.create(range, p.message, p.severity ?? severity, p.code);
|
|
@@ -275,32 +284,123 @@ export class JSONDocument {
|
|
|
275
284
|
}
|
|
276
285
|
return undefined;
|
|
277
286
|
}
|
|
278
|
-
getMatchingSchemas(schema, focusOffset = -1, exclude) {
|
|
287
|
+
getMatchingSchemas(schema, focusOffset = -1, exclude, activeVocabularies) {
|
|
279
288
|
if (this.root && schema) {
|
|
280
289
|
const matchingSchemas = new SchemaCollector(focusOffset, exclude);
|
|
281
|
-
const
|
|
282
|
-
const context = new EvaluationContext(
|
|
283
|
-
|
|
290
|
+
const { explicitDraft, effectiveDraft } = resolveDrafts(schema);
|
|
291
|
+
const context = new EvaluationContext(effectiveDraft, activeVocabularies, explicitDraft);
|
|
292
|
+
const schemaStack = [];
|
|
293
|
+
const schemaRoots = [schema];
|
|
294
|
+
validate(this.root, schema, new ValidationResult(), matchingSchemas, context, schemaStack, schemaRoots);
|
|
284
295
|
return matchingSchemas.schemas;
|
|
285
296
|
}
|
|
286
297
|
return [];
|
|
287
298
|
}
|
|
288
299
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
300
|
+
/*
|
|
301
|
+
* Resolves the schema draft versions for validation.
|
|
302
|
+
* - explicitDraft: the draft explicitly declared via $schema or passed as an override.
|
|
303
|
+
* Undefined when no $schema is present and no override is provided.
|
|
304
|
+
* - effectiveDraft: the draft used for keyword gating, defaulting to 2020-12.
|
|
305
|
+
* These are tracked separately so that behaviors like format assertion can
|
|
306
|
+
* distinguish "explicitly 2020-12" (annotation-only per spec) from
|
|
307
|
+
* "no $schema, defaulting to 2020-12" (asserts for backward compatibility).
|
|
308
|
+
*/
|
|
309
|
+
function resolveDrafts(schema, schemaDraftOverride) {
|
|
310
|
+
const explicitDraft = schemaDraftOverride ?? (schema.$schema ? getSchemaDraftFromId(schema.$schema) : undefined);
|
|
311
|
+
const effectiveDraft = explicitDraft ?? SchemaDraft.v2020_12;
|
|
312
|
+
return { explicitDraft, effectiveDraft };
|
|
295
313
|
}
|
|
296
|
-
|
|
314
|
+
// Keywords introduced in 2019-09 (not available in draft-07 and earlier)
|
|
315
|
+
const keywords201909 = new Set([
|
|
316
|
+
'dependentRequired', 'dependentSchemas',
|
|
317
|
+
'unevaluatedProperties', 'unevaluatedItems',
|
|
318
|
+
'minContains', 'maxContains'
|
|
319
|
+
]);
|
|
320
|
+
function validate(n, schema, validationResult, matchingSchemas, context, schemaStack, schemaRoots) {
|
|
297
321
|
if (!n || !matchingSchemas.include(n)) {
|
|
298
322
|
return;
|
|
299
323
|
}
|
|
300
324
|
if (n.type === 'property') {
|
|
301
|
-
return validate(n.valueNode, schema, validationResult, matchingSchemas, context);
|
|
325
|
+
return validate(n.valueNode, schema, validationResult, matchingSchemas, context, schemaStack, schemaRoots);
|
|
302
326
|
}
|
|
303
327
|
const node = n;
|
|
328
|
+
if (schema.$recursiveRef) {
|
|
329
|
+
const hasRecursiveAnchor = (s) => s.$recursiveAnchor === true;
|
|
330
|
+
const isSchemaRoot = (s) => s.$id || s.id || s.$originalId;
|
|
331
|
+
// Find nearest schema resource root
|
|
332
|
+
const currentResourceRoot = schemaStack.slice().reverse().find(isSchemaRoot);
|
|
333
|
+
// If current resource has $recursiveAnchor, find first $recursiveAnchor in stack
|
|
334
|
+
// Otherwise use current resource root or fall back to document root
|
|
335
|
+
const targetSchema = (currentResourceRoot && hasRecursiveAnchor(currentResourceRoot))
|
|
336
|
+
? schemaStack.find(hasRecursiveAnchor)
|
|
337
|
+
: currentResourceRoot ?? schemaRoots[0];
|
|
338
|
+
// Validate against the target schema (avoiding infinite loop).
|
|
339
|
+
// Fall through afterwards so sibling keywords (e.g. unevaluatedProperties)
|
|
340
|
+
// are still evaluated against this node, accumulating processedProperties.
|
|
341
|
+
if (targetSchema && targetSchema !== schema) {
|
|
342
|
+
validate(node, targetSchema, validationResult, matchingSchemas, context, schemaStack, schemaRoots);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// Track schema document roots (schemas with $id or $originalId) if not already the root
|
|
346
|
+
const isNewRoot = (schema.$id || schema.id || schema.$originalId) && !schemaRoots.includes(schema);
|
|
347
|
+
if (isNewRoot) {
|
|
348
|
+
schemaRoots.push(schema);
|
|
349
|
+
}
|
|
350
|
+
// Push current schema to stack for $recursiveRef resolution
|
|
351
|
+
schemaStack.push(schema);
|
|
352
|
+
// 2020-12 $dynamicRef. The initial (static) target and the referenced plain-name
|
|
353
|
+
// fragment were recorded during schema resolution in $dynamicRefInfo (target and
|
|
354
|
+
// name). "Bookending": dynamic resolution applies only when that
|
|
355
|
+
// initial target is itself a $dynamicAnchor of the referenced name; then the
|
|
356
|
+
// reference resolves to the outermost matching $dynamicAnchor currently in the
|
|
357
|
+
// dynamic scope (schemaRoots, ordered outermost-first). Otherwise it behaves
|
|
358
|
+
// like a plain $ref to the initial target. This runs after the schemaRoots push
|
|
359
|
+
// so the resource that contains the $dynamicRef participates in its own
|
|
360
|
+
// dynamic-scope resolution.
|
|
361
|
+
const dynamicRefInfo = schema.$dynamicRefInfo;
|
|
362
|
+
const dynamicRefTarget = dynamicRefInfo?.target;
|
|
363
|
+
if (dynamicRefTarget !== undefined) {
|
|
364
|
+
let targetSchema = dynamicRefTarget;
|
|
365
|
+
const dynamicName = dynamicRefInfo?.name;
|
|
366
|
+
if (dynamicName !== undefined && dynamicRefTarget.$dynamicAnchor === dynamicName) {
|
|
367
|
+
for (const root of schemaRoots) {
|
|
368
|
+
const candidate = root.$anchorMaps?.dynamic.get(dynamicName);
|
|
369
|
+
if (candidate) {
|
|
370
|
+
targetSchema = candidate;
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// Validate the instance against the referenced schema, then fall through so
|
|
376
|
+
// that any sibling keywords on this same node are validated too: per 2020-12
|
|
377
|
+
// $dynamicRef is an applicator that applies alongside its siblings (like $ref),
|
|
378
|
+
// not a redirect that replaces them. The shared cleanup below pops the stack.
|
|
379
|
+
if (targetSchema && targetSchema !== schema) {
|
|
380
|
+
validate(node, targetSchema, validationResult, matchingSchemas, context, schemaStack, schemaRoots);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const enabled = (keyword) => {
|
|
384
|
+
// Draft-based keyword gating: keywords only exist in certain drafts.
|
|
385
|
+
// 'dependencies' was replaced by dependentRequired/dependentSchemas in 2019-09
|
|
386
|
+
if (keyword === 'dependencies') {
|
|
387
|
+
return context.schemaDraft <= SchemaDraft.v7;
|
|
388
|
+
}
|
|
389
|
+
// Keywords introduced in 2019-09 (not available in draft-07 and earlier)
|
|
390
|
+
if (keywords201909.has(keyword)) {
|
|
391
|
+
return context.schemaDraft >= SchemaDraft.v2019_09;
|
|
392
|
+
}
|
|
393
|
+
// 'prefixItems' was introduced in 2020-12
|
|
394
|
+
if (keyword === 'prefixItems') {
|
|
395
|
+
return context.schemaDraft >= SchemaDraft.v2020_12;
|
|
396
|
+
}
|
|
397
|
+
// Vocabulary-based filtering only applies for 2019-09+ (vocabulary is not a concept in older drafts)
|
|
398
|
+
if (context.schemaDraft >= SchemaDraft.v2019_09 && context.activeVocabularies) {
|
|
399
|
+
return isKeywordEnabled(keyword, context.activeVocabularies);
|
|
400
|
+
}
|
|
401
|
+
// No vocabulary info or pre-2019-09: enable all draft-appropriate keywords
|
|
402
|
+
return true;
|
|
403
|
+
};
|
|
304
404
|
_validateNode();
|
|
305
405
|
switch (node.type) {
|
|
306
406
|
case 'object':
|
|
@@ -317,11 +417,17 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
317
417
|
break;
|
|
318
418
|
}
|
|
319
419
|
matchingSchemas.add({ node: node, schema: schema });
|
|
420
|
+
// Pop schema from stack when done
|
|
421
|
+
schemaStack.pop();
|
|
422
|
+
// Pop schema root if we pushed one
|
|
423
|
+
if (isNewRoot) {
|
|
424
|
+
schemaRoots.pop();
|
|
425
|
+
}
|
|
320
426
|
function _validateNode() {
|
|
321
427
|
function matchesType(type) {
|
|
322
428
|
return node.type === type || (type === 'integer' && node.type === 'number' && node.isInteger);
|
|
323
429
|
}
|
|
324
|
-
if (Array.isArray(schema.type)) {
|
|
430
|
+
if (Array.isArray(schema.type) && enabled('type')) {
|
|
325
431
|
if (!schema.type.some(matchesType)) {
|
|
326
432
|
validationResult.problems.push({
|
|
327
433
|
location: { offset: node.offset, length: node.length },
|
|
@@ -329,7 +435,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
329
435
|
});
|
|
330
436
|
}
|
|
331
437
|
}
|
|
332
|
-
else if (schema.type) {
|
|
438
|
+
else if (schema.type && !Array.isArray(schema.type) && enabled('type')) {
|
|
333
439
|
if (!matchesType(schema.type)) {
|
|
334
440
|
validationResult.problems.push({
|
|
335
441
|
location: { offset: node.offset, length: node.length },
|
|
@@ -337,20 +443,20 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
337
443
|
});
|
|
338
444
|
}
|
|
339
445
|
}
|
|
340
|
-
if (Array.isArray(schema.allOf)) {
|
|
446
|
+
if (Array.isArray(schema.allOf) && enabled('allOf')) {
|
|
341
447
|
for (const subSchemaRef of schema.allOf) {
|
|
342
448
|
const subValidationResult = new ValidationResult();
|
|
343
449
|
const subMatchingSchemas = matchingSchemas.newSub();
|
|
344
|
-
validate(node, asSchema(subSchemaRef), subValidationResult, subMatchingSchemas, context);
|
|
450
|
+
validate(node, asSchema(subSchemaRef), subValidationResult, subMatchingSchemas, context, schemaStack, schemaRoots);
|
|
345
451
|
validationResult.merge(subValidationResult);
|
|
346
452
|
matchingSchemas.merge(subMatchingSchemas);
|
|
347
453
|
}
|
|
348
454
|
}
|
|
349
455
|
const notSchema = asSchema(schema.not);
|
|
350
|
-
if (notSchema) {
|
|
456
|
+
if (notSchema && enabled('not')) {
|
|
351
457
|
const subValidationResult = new ValidationResult();
|
|
352
458
|
const subMatchingSchemas = matchingSchemas.newSub();
|
|
353
|
-
validate(node, notSchema, subValidationResult, subMatchingSchemas, context);
|
|
459
|
+
validate(node, notSchema, subValidationResult, subMatchingSchemas, context, schemaStack, schemaRoots);
|
|
354
460
|
if (!subValidationResult.hasProblems()) {
|
|
355
461
|
validationResult.problems.push({
|
|
356
462
|
location: { offset: node.offset, length: node.length },
|
|
@@ -371,7 +477,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
371
477
|
const subSchema = asSchema(subSchemaRef);
|
|
372
478
|
const subValidationResult = new ValidationResult();
|
|
373
479
|
const subMatchingSchemas = matchingSchemas.newSub();
|
|
374
|
-
validate(node, subSchema, subValidationResult, subMatchingSchemas, context);
|
|
480
|
+
validate(node, subSchema, subValidationResult, subMatchingSchemas, context, schemaStack, schemaRoots);
|
|
375
481
|
if (!subValidationResult.hasProblems()) {
|
|
376
482
|
matches.push(subSchema);
|
|
377
483
|
}
|
|
@@ -413,16 +519,16 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
413
519
|
}
|
|
414
520
|
return matches.length;
|
|
415
521
|
};
|
|
416
|
-
if (Array.isArray(schema.anyOf)) {
|
|
522
|
+
if (Array.isArray(schema.anyOf) && enabled('anyOf')) {
|
|
417
523
|
testAlternatives(schema.anyOf, false);
|
|
418
524
|
}
|
|
419
|
-
if (Array.isArray(schema.oneOf)) {
|
|
525
|
+
if (Array.isArray(schema.oneOf) && enabled('oneOf')) {
|
|
420
526
|
testAlternatives(schema.oneOf, true);
|
|
421
527
|
}
|
|
422
528
|
const testBranch = (schema) => {
|
|
423
529
|
const subValidationResult = new ValidationResult();
|
|
424
530
|
const subMatchingSchemas = matchingSchemas.newSub();
|
|
425
|
-
validate(node, asSchema(schema), subValidationResult, subMatchingSchemas, context);
|
|
531
|
+
validate(node, asSchema(schema), subValidationResult, subMatchingSchemas, context, schemaStack, schemaRoots);
|
|
426
532
|
validationResult.merge(subValidationResult);
|
|
427
533
|
matchingSchemas.merge(subMatchingSchemas);
|
|
428
534
|
};
|
|
@@ -430,10 +536,12 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
430
536
|
const subSchema = asSchema(ifSchema);
|
|
431
537
|
const subValidationResult = new ValidationResult();
|
|
432
538
|
const subMatchingSchemas = matchingSchemas.newSub();
|
|
433
|
-
validate(node, subSchema, subValidationResult, subMatchingSchemas, context);
|
|
539
|
+
validate(node, subSchema, subValidationResult, subMatchingSchemas, context, schemaStack, schemaRoots);
|
|
434
540
|
matchingSchemas.merge(subMatchingSchemas);
|
|
435
|
-
|
|
541
|
+
// if passed: merge properties from if;
|
|
542
|
+
// otherwise don't merge properties from if, only from else
|
|
436
543
|
if (!subValidationResult.hasProblems()) {
|
|
544
|
+
validationResult.mergeProcessedProperties(subValidationResult);
|
|
437
545
|
if (thenSchema) {
|
|
438
546
|
testBranch(thenSchema);
|
|
439
547
|
}
|
|
@@ -443,10 +551,10 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
443
551
|
}
|
|
444
552
|
};
|
|
445
553
|
const ifSchema = asSchema(schema.if);
|
|
446
|
-
if (ifSchema) {
|
|
554
|
+
if (ifSchema && enabled('if')) {
|
|
447
555
|
testCondition(ifSchema, asSchema(schema.then), asSchema(schema.else));
|
|
448
556
|
}
|
|
449
|
-
if (Array.isArray(schema.enum)) {
|
|
557
|
+
if (Array.isArray(schema.enum) && enabled('enum')) {
|
|
450
558
|
const val = getNodeValue(node);
|
|
451
559
|
let enumValueMatch = false;
|
|
452
560
|
for (const e of schema.enum) {
|
|
@@ -465,7 +573,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
465
573
|
});
|
|
466
574
|
}
|
|
467
575
|
}
|
|
468
|
-
if (isDefined(schema.const)) {
|
|
576
|
+
if (isDefined(schema.const) && enabled('const')) {
|
|
469
577
|
const val = getNodeValue(node);
|
|
470
578
|
if (!equals(val, schema.const)) {
|
|
471
579
|
validationResult.problems.push({
|
|
@@ -578,7 +686,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
578
686
|
};
|
|
579
687
|
}
|
|
580
688
|
;
|
|
581
|
-
if (isNumber(schema.multipleOf)) {
|
|
689
|
+
if (isNumber(schema.multipleOf) && enabled('multipleOf')) {
|
|
582
690
|
let remainder = -1;
|
|
583
691
|
if (Number.isInteger(schema.multipleOf)) {
|
|
584
692
|
remainder = val % schema.multipleOf;
|
|
@@ -620,28 +728,28 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
620
728
|
return undefined;
|
|
621
729
|
}
|
|
622
730
|
const exclusiveMinimum = getExclusiveLimit(schema.minimum, schema.exclusiveMinimum);
|
|
623
|
-
if (isNumber(exclusiveMinimum) && val <= exclusiveMinimum) {
|
|
731
|
+
if (isNumber(exclusiveMinimum) && val <= exclusiveMinimum && enabled('exclusiveMinimum')) {
|
|
624
732
|
validationResult.problems.push({
|
|
625
733
|
location: { offset: node.offset, length: node.length },
|
|
626
734
|
message: l10n.t('Value is below the exclusive minimum of {0}.', exclusiveMinimum)
|
|
627
735
|
});
|
|
628
736
|
}
|
|
629
737
|
const exclusiveMaximum = getExclusiveLimit(schema.maximum, schema.exclusiveMaximum);
|
|
630
|
-
if (isNumber(exclusiveMaximum) && val >= exclusiveMaximum) {
|
|
738
|
+
if (isNumber(exclusiveMaximum) && val >= exclusiveMaximum && enabled('exclusiveMaximum')) {
|
|
631
739
|
validationResult.problems.push({
|
|
632
740
|
location: { offset: node.offset, length: node.length },
|
|
633
741
|
message: l10n.t('Value is above the exclusive maximum of {0}.', exclusiveMaximum)
|
|
634
742
|
});
|
|
635
743
|
}
|
|
636
744
|
const minimum = getLimit(schema.minimum, schema.exclusiveMinimum);
|
|
637
|
-
if (isNumber(minimum) && val < minimum) {
|
|
745
|
+
if (isNumber(minimum) && val < minimum && enabled('minimum')) {
|
|
638
746
|
validationResult.problems.push({
|
|
639
747
|
location: { offset: node.offset, length: node.length },
|
|
640
748
|
message: l10n.t('Value is below the minimum of {0}.', minimum)
|
|
641
749
|
});
|
|
642
750
|
}
|
|
643
751
|
const maximum = getLimit(schema.maximum, schema.exclusiveMaximum);
|
|
644
|
-
if (isNumber(maximum) && val > maximum) {
|
|
752
|
+
if (isNumber(maximum) && val > maximum && enabled('maximum')) {
|
|
645
753
|
validationResult.problems.push({
|
|
646
754
|
location: { offset: node.offset, length: node.length },
|
|
647
755
|
message: l10n.t('Value is above the maximum of {0}.', maximum)
|
|
@@ -649,19 +757,19 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
649
757
|
}
|
|
650
758
|
}
|
|
651
759
|
function _validateStringNode(node) {
|
|
652
|
-
if (isNumber(schema.minLength) && stringLength(node.value) < schema.minLength) {
|
|
760
|
+
if (isNumber(schema.minLength) && stringLength(node.value) < schema.minLength && enabled('minLength')) {
|
|
653
761
|
validationResult.problems.push({
|
|
654
762
|
location: { offset: node.offset, length: node.length },
|
|
655
763
|
message: l10n.t('String is shorter than the minimum length of {0}.', schema.minLength)
|
|
656
764
|
});
|
|
657
765
|
}
|
|
658
|
-
if (isNumber(schema.maxLength) && stringLength(node.value) > schema.maxLength) {
|
|
766
|
+
if (isNumber(schema.maxLength) && stringLength(node.value) > schema.maxLength && enabled('maxLength')) {
|
|
659
767
|
validationResult.problems.push({
|
|
660
768
|
location: { offset: node.offset, length: node.length },
|
|
661
769
|
message: l10n.t('String is longer than the maximum length of {0}.', schema.maxLength)
|
|
662
770
|
});
|
|
663
771
|
}
|
|
664
|
-
if (isString(schema.pattern)) {
|
|
772
|
+
if (isString(schema.pattern) && enabled('pattern')) {
|
|
665
773
|
const regex = extendedRegExp(schema.pattern);
|
|
666
774
|
if (regex && !(regex.test(node.value))) {
|
|
667
775
|
validationResult.problems.push({
|
|
@@ -670,7 +778,8 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
670
778
|
});
|
|
671
779
|
}
|
|
672
780
|
}
|
|
673
|
-
if (
|
|
781
|
+
// Only validate format if format-assertion vocabulary is active (not annotation-only)
|
|
782
|
+
if (schema.format && enabled('format') && isFormatAssertionEnabled(context.activeVocabularies, context.explicitSchemaDraft)) {
|
|
674
783
|
switch (schema.format) {
|
|
675
784
|
case 'uri':
|
|
676
785
|
case 'uri-reference':
|
|
@@ -700,6 +809,8 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
700
809
|
case 'date-time':
|
|
701
810
|
case 'date':
|
|
702
811
|
case 'time':
|
|
812
|
+
case 'duration':
|
|
813
|
+
case 'uuid':
|
|
703
814
|
case 'email':
|
|
704
815
|
case 'hostname':
|
|
705
816
|
case 'ipv4':
|
|
@@ -735,7 +846,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
735
846
|
const itemValidationResult = new ValidationResult();
|
|
736
847
|
const item = node.items[index];
|
|
737
848
|
if (item) {
|
|
738
|
-
validate(item, subSchema, itemValidationResult, matchingSchemas, context);
|
|
849
|
+
validate(item, subSchema, itemValidationResult, matchingSchemas, context, schemaStack, schemaRoots);
|
|
739
850
|
validationResult.mergePropertyMatch(itemValidationResult);
|
|
740
851
|
}
|
|
741
852
|
validationResult.processedProperties.add(String(index));
|
|
@@ -757,19 +868,19 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
757
868
|
else {
|
|
758
869
|
for (; index < node.items.length; index++) {
|
|
759
870
|
const itemValidationResult = new ValidationResult();
|
|
760
|
-
validate(node.items[index], additionalItemSchema, itemValidationResult, matchingSchemas, context);
|
|
871
|
+
validate(node.items[index], additionalItemSchema, itemValidationResult, matchingSchemas, context, schemaStack, schemaRoots);
|
|
761
872
|
validationResult.mergePropertyMatch(itemValidationResult);
|
|
762
873
|
validationResult.processedProperties.add(String(index));
|
|
763
874
|
}
|
|
764
875
|
}
|
|
765
876
|
}
|
|
766
877
|
const containsSchema = asSchema(schema.contains);
|
|
767
|
-
if (containsSchema) {
|
|
878
|
+
if (containsSchema && enabled('contains')) {
|
|
768
879
|
let containsCount = 0;
|
|
769
880
|
for (let index = 0; index < node.items.length; index++) {
|
|
770
881
|
const item = node.items[index];
|
|
771
882
|
const itemValidationResult = new ValidationResult();
|
|
772
|
-
validate(item, containsSchema, itemValidationResult, NoOpSchemaCollector.instance, context);
|
|
883
|
+
validate(item, containsSchema, itemValidationResult, NoOpSchemaCollector.instance, context, schemaStack, schemaRoots);
|
|
773
884
|
if (!itemValidationResult.hasProblems()) {
|
|
774
885
|
containsCount++;
|
|
775
886
|
if (context.schemaDraft >= SchemaDraft.v2020_12) {
|
|
@@ -783,13 +894,13 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
783
894
|
message: schema.errorMessage || l10n.t('Array does not contain required item.')
|
|
784
895
|
});
|
|
785
896
|
}
|
|
786
|
-
if (isNumber(schema.minContains) && containsCount < schema.minContains) {
|
|
897
|
+
if (isNumber(schema.minContains) && containsCount < schema.minContains && enabled('minContains')) {
|
|
787
898
|
validationResult.problems.push({
|
|
788
899
|
location: { offset: node.offset, length: node.length },
|
|
789
900
|
message: schema.errorMessage || l10n.t('Array has too few items that match the contains contraint. Expected {0} or more.', schema.minContains)
|
|
790
901
|
});
|
|
791
902
|
}
|
|
792
|
-
if (isNumber(schema.maxContains) && containsCount > schema.maxContains) {
|
|
903
|
+
if (isNumber(schema.maxContains) && containsCount > schema.maxContains && enabled('maxContains')) {
|
|
793
904
|
validationResult.problems.push({
|
|
794
905
|
location: { offset: node.offset, length: node.length },
|
|
795
906
|
message: schema.errorMessage || l10n.t('Array has too many items that match the contains contraint. Expected {0} or less.', schema.maxContains)
|
|
@@ -797,7 +908,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
797
908
|
}
|
|
798
909
|
}
|
|
799
910
|
const unevaluatedItems = schema.unevaluatedItems;
|
|
800
|
-
if (unevaluatedItems !== undefined) {
|
|
911
|
+
if (unevaluatedItems !== undefined && enabled('unevaluatedItems')) {
|
|
801
912
|
for (let i = 0; i < node.items.length; i++) {
|
|
802
913
|
if (!validationResult.processedProperties.has(String(i))) {
|
|
803
914
|
if (unevaluatedItems === false) {
|
|
@@ -808,7 +919,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
808
919
|
}
|
|
809
920
|
else {
|
|
810
921
|
const itemValidationResult = new ValidationResult();
|
|
811
|
-
validate(node.items[i], schema.unevaluatedItems, itemValidationResult, matchingSchemas, context);
|
|
922
|
+
validate(node.items[i], schema.unevaluatedItems, itemValidationResult, matchingSchemas, context, schemaStack, schemaRoots);
|
|
812
923
|
validationResult.mergePropertyMatch(itemValidationResult);
|
|
813
924
|
}
|
|
814
925
|
}
|
|
@@ -816,19 +927,19 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
816
927
|
validationResult.propertiesValueMatches++;
|
|
817
928
|
}
|
|
818
929
|
}
|
|
819
|
-
if (isNumber(schema.minItems) && node.items.length < schema.minItems) {
|
|
930
|
+
if (isNumber(schema.minItems) && node.items.length < schema.minItems && enabled('minItems')) {
|
|
820
931
|
validationResult.problems.push({
|
|
821
932
|
location: { offset: node.offset, length: node.length },
|
|
822
933
|
message: l10n.t('Array has too few items. Expected {0} or more.', schema.minItems)
|
|
823
934
|
});
|
|
824
935
|
}
|
|
825
|
-
if (isNumber(schema.maxItems) && node.items.length > schema.maxItems) {
|
|
936
|
+
if (isNumber(schema.maxItems) && node.items.length > schema.maxItems && enabled('maxItems')) {
|
|
826
937
|
validationResult.problems.push({
|
|
827
938
|
location: { offset: node.offset, length: node.length },
|
|
828
939
|
message: l10n.t('Array has too many items. Expected {0} or fewer.', schema.maxItems)
|
|
829
940
|
});
|
|
830
941
|
}
|
|
831
|
-
if (schema.uniqueItems === true) {
|
|
942
|
+
if (schema.uniqueItems === true && enabled('uniqueItems')) {
|
|
832
943
|
const values = getNodeValue(node);
|
|
833
944
|
function hasDuplicates() {
|
|
834
945
|
for (let i = 0; i < values.length - 1; i++) {
|
|
@@ -857,13 +968,11 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
857
968
|
seenKeys[key] = propertyNode.valueNode;
|
|
858
969
|
unprocessedProperties.add(key);
|
|
859
970
|
}
|
|
860
|
-
if (Array.isArray(schema.required)) {
|
|
971
|
+
if (Array.isArray(schema.required) && enabled('required')) {
|
|
861
972
|
for (const propertyName of schema.required) {
|
|
862
973
|
if (!seenKeys[propertyName]) {
|
|
863
|
-
const keyNode = node.parent && node.parent.type === 'property' && node.parent.keyNode;
|
|
864
|
-
const location = keyNode ? { offset: keyNode.offset, length: keyNode.length } : { offset: node.offset, length: 1 };
|
|
865
974
|
validationResult.problems.push({
|
|
866
|
-
location:
|
|
975
|
+
location: { offset: node.offset, length: node.length },
|
|
867
976
|
message: l10n.t('Missing property "{0}".', propertyName)
|
|
868
977
|
});
|
|
869
978
|
}
|
|
@@ -873,7 +982,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
873
982
|
unprocessedProperties.delete(prop);
|
|
874
983
|
validationResult.processedProperties.add(prop);
|
|
875
984
|
};
|
|
876
|
-
if (schema.properties) {
|
|
985
|
+
if (schema.properties && enabled('properties')) {
|
|
877
986
|
for (const propertyName of Object.keys(schema.properties)) {
|
|
878
987
|
propertyProcessed(propertyName);
|
|
879
988
|
const propertySchema = schema.properties[propertyName];
|
|
@@ -894,20 +1003,28 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
894
1003
|
}
|
|
895
1004
|
else {
|
|
896
1005
|
const propertyValidationResult = new ValidationResult();
|
|
897
|
-
validate(child, propertySchema, propertyValidationResult, matchingSchemas, context);
|
|
1006
|
+
validate(child, propertySchema, propertyValidationResult, matchingSchemas, context, schemaStack, schemaRoots);
|
|
898
1007
|
validationResult.mergePropertyMatch(propertyValidationResult);
|
|
899
1008
|
}
|
|
900
1009
|
}
|
|
901
1010
|
}
|
|
902
1011
|
}
|
|
903
|
-
if (schema.patternProperties) {
|
|
1012
|
+
if (schema.patternProperties && enabled('patternProperties')) {
|
|
1013
|
+
// Collect all properties matched by any pattern first
|
|
1014
|
+
// A property must be validated against all patterns it matches
|
|
1015
|
+
// Properties in `properties` can also match `patternProperties`, check all keys
|
|
1016
|
+
const allPropertyNames = Object.keys(seenKeys);
|
|
1017
|
+
const patternPropertyMatches = new Set();
|
|
904
1018
|
for (const propertyPattern of Object.keys(schema.patternProperties)) {
|
|
905
1019
|
const regex = extendedRegExp(propertyPattern);
|
|
906
1020
|
if (regex) {
|
|
907
|
-
const
|
|
908
|
-
for (const propertyName of unprocessedProperties) {
|
|
1021
|
+
for (const propertyName of allPropertyNames) {
|
|
909
1022
|
if (regex.test(propertyName)) {
|
|
910
|
-
|
|
1023
|
+
// Only mark as pattern match if not already in `properties`
|
|
1024
|
+
// for additionalProperties tracking
|
|
1025
|
+
if (unprocessedProperties.has(propertyName)) {
|
|
1026
|
+
patternPropertyMatches.add(propertyName);
|
|
1027
|
+
}
|
|
911
1028
|
const child = seenKeys[propertyName];
|
|
912
1029
|
if (child) {
|
|
913
1030
|
const propertySchema = schema.patternProperties[propertyPattern];
|
|
@@ -926,18 +1043,19 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
926
1043
|
}
|
|
927
1044
|
else {
|
|
928
1045
|
const propertyValidationResult = new ValidationResult();
|
|
929
|
-
validate(child, propertySchema, propertyValidationResult, matchingSchemas, context);
|
|
1046
|
+
validate(child, propertySchema, propertyValidationResult, matchingSchemas, context, schemaStack, schemaRoots);
|
|
930
1047
|
validationResult.mergePropertyMatch(propertyValidationResult);
|
|
931
1048
|
}
|
|
932
1049
|
}
|
|
933
1050
|
}
|
|
934
1051
|
}
|
|
935
|
-
processed.forEach(propertyProcessed);
|
|
936
1052
|
}
|
|
937
1053
|
}
|
|
1054
|
+
// Mark all matched properties as processed after all patterns have been checked
|
|
1055
|
+
patternPropertyMatches.forEach(propertyProcessed);
|
|
938
1056
|
}
|
|
939
1057
|
const additionalProperties = schema.additionalProperties;
|
|
940
|
-
if (additionalProperties !== undefined) {
|
|
1058
|
+
if (additionalProperties !== undefined && enabled('additionalProperties')) {
|
|
941
1059
|
for (const propertyName of unprocessedProperties) {
|
|
942
1060
|
propertyProcessed(propertyName);
|
|
943
1061
|
const child = seenKeys[propertyName];
|
|
@@ -951,14 +1069,32 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
951
1069
|
}
|
|
952
1070
|
else if (additionalProperties !== true) {
|
|
953
1071
|
const propertyValidationResult = new ValidationResult();
|
|
954
|
-
validate(child, additionalProperties, propertyValidationResult, matchingSchemas, context);
|
|
1072
|
+
validate(child, additionalProperties, propertyValidationResult, matchingSchemas, context, schemaStack, schemaRoots);
|
|
955
1073
|
validationResult.mergePropertyMatch(propertyValidationResult);
|
|
956
1074
|
}
|
|
957
1075
|
}
|
|
958
1076
|
}
|
|
959
1077
|
}
|
|
1078
|
+
if (schema.dependentRequired && enabled('dependentRequired')) {
|
|
1079
|
+
for (const key in schema.dependentRequired) {
|
|
1080
|
+
const prop = seenKeys[key];
|
|
1081
|
+
const propertyDeps = schema.dependentRequired[key];
|
|
1082
|
+
if (prop && Array.isArray(propertyDeps)) {
|
|
1083
|
+
_validatePropertyDependencies(key, propertyDeps);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
if (schema.dependentSchemas && enabled('dependentSchemas')) {
|
|
1088
|
+
for (const key in schema.dependentSchemas) {
|
|
1089
|
+
const prop = seenKeys[key];
|
|
1090
|
+
const propertyDeps = schema.dependentSchemas[key];
|
|
1091
|
+
if (prop) {
|
|
1092
|
+
_validatePropertyDependencies(key, propertyDeps);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
960
1096
|
const unevaluatedProperties = schema.unevaluatedProperties;
|
|
961
|
-
if (unevaluatedProperties !== undefined) {
|
|
1097
|
+
if (unevaluatedProperties !== undefined && enabled('unevaluatedProperties')) {
|
|
962
1098
|
const processed = [];
|
|
963
1099
|
for (const propertyName of unprocessedProperties) {
|
|
964
1100
|
if (!validationResult.processedProperties.has(propertyName)) {
|
|
@@ -974,7 +1110,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
974
1110
|
}
|
|
975
1111
|
else if (unevaluatedProperties !== true) {
|
|
976
1112
|
const propertyValidationResult = new ValidationResult();
|
|
977
|
-
validate(child, unevaluatedProperties, propertyValidationResult, matchingSchemas, context);
|
|
1113
|
+
validate(child, unevaluatedProperties, propertyValidationResult, matchingSchemas, context, schemaStack, schemaRoots);
|
|
978
1114
|
validationResult.mergePropertyMatch(propertyValidationResult);
|
|
979
1115
|
}
|
|
980
1116
|
}
|
|
@@ -982,7 +1118,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
982
1118
|
}
|
|
983
1119
|
processed.forEach(propertyProcessed);
|
|
984
1120
|
}
|
|
985
|
-
if (isNumber(schema.maxProperties)) {
|
|
1121
|
+
if (isNumber(schema.maxProperties) && enabled('maxProperties')) {
|
|
986
1122
|
if (node.properties.length > schema.maxProperties) {
|
|
987
1123
|
validationResult.problems.push({
|
|
988
1124
|
location: { offset: node.offset, length: node.length },
|
|
@@ -990,7 +1126,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
990
1126
|
});
|
|
991
1127
|
}
|
|
992
1128
|
}
|
|
993
|
-
if (isNumber(schema.minProperties)) {
|
|
1129
|
+
if (isNumber(schema.minProperties) && enabled('minProperties')) {
|
|
994
1130
|
if (node.properties.length < schema.minProperties) {
|
|
995
1131
|
validationResult.problems.push({
|
|
996
1132
|
location: { offset: node.offset, length: node.length },
|
|
@@ -998,25 +1134,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
998
1134
|
});
|
|
999
1135
|
}
|
|
1000
1136
|
}
|
|
1001
|
-
if (schema.
|
|
1002
|
-
for (const key in schema.dependentRequired) {
|
|
1003
|
-
const prop = seenKeys[key];
|
|
1004
|
-
const propertyDeps = schema.dependentRequired[key];
|
|
1005
|
-
if (prop && Array.isArray(propertyDeps)) {
|
|
1006
|
-
_validatePropertyDependencies(key, propertyDeps);
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
if (schema.dependentSchemas) {
|
|
1011
|
-
for (const key in schema.dependentSchemas) {
|
|
1012
|
-
const prop = seenKeys[key];
|
|
1013
|
-
const propertyDeps = schema.dependentSchemas[key];
|
|
1014
|
-
if (prop && isObject(propertyDeps)) {
|
|
1015
|
-
_validatePropertyDependencies(key, propertyDeps);
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
if (schema.dependencies) {
|
|
1137
|
+
if (schema.dependencies && enabled('dependencies')) {
|
|
1020
1138
|
for (const key in schema.dependencies) {
|
|
1021
1139
|
const prop = seenKeys[key];
|
|
1022
1140
|
if (prop) {
|
|
@@ -1025,11 +1143,11 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
1025
1143
|
}
|
|
1026
1144
|
}
|
|
1027
1145
|
const propertyNames = asSchema(schema.propertyNames);
|
|
1028
|
-
if (propertyNames) {
|
|
1146
|
+
if (propertyNames && enabled('propertyNames')) {
|
|
1029
1147
|
for (const f of node.properties) {
|
|
1030
1148
|
const key = f.keyNode;
|
|
1031
1149
|
if (key) {
|
|
1032
|
-
validate(key, propertyNames, validationResult, matchingSchemas, context);
|
|
1150
|
+
validate(key, propertyNames, validationResult, matchingSchemas, context, schemaStack, schemaRoots);
|
|
1033
1151
|
}
|
|
1034
1152
|
}
|
|
1035
1153
|
}
|
|
@@ -1051,8 +1169,9 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
|
|
|
1051
1169
|
const propertySchema = asSchema(propertyDep);
|
|
1052
1170
|
if (propertySchema) {
|
|
1053
1171
|
const propertyValidationResult = new ValidationResult();
|
|
1054
|
-
validate(node, propertySchema, propertyValidationResult, matchingSchemas, context);
|
|
1172
|
+
validate(node, propertySchema, propertyValidationResult, matchingSchemas, context, schemaStack, schemaRoots);
|
|
1055
1173
|
validationResult.mergePropertyMatch(propertyValidationResult);
|
|
1174
|
+
validationResult.mergeProcessedProperties(propertyValidationResult);
|
|
1056
1175
|
}
|
|
1057
1176
|
}
|
|
1058
1177
|
}
|