vscode-json-languageservice 5.7.2 → 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.
Files changed (52) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/lib/esm/jsonContributions.d.ts +1 -1
  3. package/lib/esm/jsonLanguageService.d.ts +2 -2
  4. package/lib/esm/jsonLanguageService.js +13 -13
  5. package/lib/esm/jsonLanguageTypes.d.ts +10 -2
  6. package/lib/esm/jsonSchema.d.ts +71 -2
  7. package/lib/esm/parser/jsonParser.js +224 -93
  8. package/lib/esm/services/configuration.js +2 -2
  9. package/lib/esm/services/jsonCompletion.js +5 -5
  10. package/lib/esm/services/jsonDocumentSymbols.js +4 -4
  11. package/lib/esm/services/jsonFolding.js +1 -1
  12. package/lib/esm/services/jsonHover.js +2 -2
  13. package/lib/esm/services/jsonLinks.js +94 -3
  14. package/lib/esm/services/jsonSchemaService.js +639 -100
  15. package/lib/esm/services/jsonSelectionRanges.js +1 -1
  16. package/lib/esm/services/jsonValidation.js +4 -4
  17. package/lib/esm/services/schemas/draft-2019-09-flat.d.ts +1 -1
  18. package/lib/esm/services/schemas/draft-2020-12-flat.d.ts +1 -1
  19. package/lib/esm/services/vocabularies.js +139 -0
  20. package/lib/esm/utils/format.js +1 -1
  21. package/lib/esm/utils/sort.js +3 -3
  22. package/package.json +23 -20
  23. package/lib/umd/jsonContributions.d.ts +0 -21
  24. package/lib/umd/jsonContributions.js +0 -12
  25. package/lib/umd/jsonLanguageService.d.ts +0 -30
  26. package/lib/umd/jsonLanguageService.js +0 -79
  27. package/lib/umd/jsonLanguageTypes.d.ts +0 -305
  28. package/lib/umd/jsonLanguageTypes.js +0 -109
  29. package/lib/umd/jsonSchema.d.ts +0 -92
  30. package/lib/umd/jsonSchema.js +0 -12
  31. package/lib/umd/parser/jsonParser.js +0 -1365
  32. package/lib/umd/services/configuration.js +0 -536
  33. package/lib/umd/services/jsonCompletion.js +0 -982
  34. package/lib/umd/services/jsonDocumentSymbols.js +0 -285
  35. package/lib/umd/services/jsonFolding.js +0 -133
  36. package/lib/umd/services/jsonHover.js +0 -125
  37. package/lib/umd/services/jsonLinks.js +0 -85
  38. package/lib/umd/services/jsonSchemaService.js +0 -631
  39. package/lib/umd/services/jsonSelectionRanges.js +0 -74
  40. package/lib/umd/services/jsonValidation.js +0 -165
  41. package/lib/umd/services/schemas/draft-2019-09-flat.d.ts +0 -278
  42. package/lib/umd/services/schemas/draft-2019-09-flat.js +0 -314
  43. package/lib/umd/services/schemas/draft-2020-12-flat.d.ts +0 -276
  44. package/lib/umd/services/schemas/draft-2020-12-flat.js +0 -307
  45. package/lib/umd/utils/colors.js +0 -83
  46. package/lib/umd/utils/format.js +0 -33
  47. package/lib/umd/utils/glob.js +0 -137
  48. package/lib/umd/utils/json.js +0 -55
  49. package/lib/umd/utils/objects.js +0 -86
  50. package/lib/umd/utils/propertyTree.js +0 -90
  51. package/lib/umd/utils/sort.js +0 -384
  52. package/lib/umd/utils/strings.js +0 -97
@@ -3,16 +3,19 @@
3
3
  * Licensed under the MIT License. See License.txt in the project root for license information.
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import * as Json from 'jsonc-parser';
6
- import { isNumber, equals, isBoolean, isString, isDefined, isObject } from '../utils/objects';
7
- import { extendedRegExp, stringLength } from '../utils/strings';
8
- import { ErrorCode, Diagnostic, DiagnosticSeverity, Range, SchemaDraft } from '../jsonLanguageTypes';
6
+ import { isNumber, equals, isBoolean, isString, isDefined, isObject } from '../utils/objects.js';
7
+ import { extendedRegExp, stringLength } from '../utils/strings.js';
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
- validate(this.root, schema, validationResult, NoOpSchemaCollector.instance, new EvaluationContext(schemaDraft ?? getSchemaDraft(schema)));
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 schemaDraft = getSchemaDraft(schema);
282
- const context = new EvaluationContext(schemaDraft);
283
- validate(this.root, schema, new ValidationResult(), matchingSchemas, context);
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
- function getSchemaDraft(schema, fallBack = SchemaDraft.v2020_12) {
290
- let schemaId = schema.$schema;
291
- if (schemaId) {
292
- return getSchemaDraftFromId(schemaId) ?? fallBack;
293
- }
294
- return fallBack;
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
- function validate(n, schema, validationResult, matchingSchemas, context) {
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
- validationResult.mergeProcessedProperties(subValidationResult);
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({
@@ -514,6 +622,18 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
514
622
  }
515
623
  valueMap.get(schema.const).push(i);
516
624
  }
625
+ else if (schema.enum && Array.isArray(schema.enum)) {
626
+ for (const enumValue of schema.enum) {
627
+ if (!constMap.has(key)) {
628
+ constMap.set(key, new Map());
629
+ }
630
+ const valueMap = constMap.get(key);
631
+ if (!valueMap.has(enumValue)) {
632
+ valueMap.set(enumValue, []);
633
+ }
634
+ valueMap.get(enumValue).push(i);
635
+ }
636
+ }
517
637
  });
518
638
  }
519
639
  return constMap;
@@ -566,7 +686,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
566
686
  };
567
687
  }
568
688
  ;
569
- if (isNumber(schema.multipleOf)) {
689
+ if (isNumber(schema.multipleOf) && enabled('multipleOf')) {
570
690
  let remainder = -1;
571
691
  if (Number.isInteger(schema.multipleOf)) {
572
692
  remainder = val % schema.multipleOf;
@@ -608,28 +728,28 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
608
728
  return undefined;
609
729
  }
610
730
  const exclusiveMinimum = getExclusiveLimit(schema.minimum, schema.exclusiveMinimum);
611
- if (isNumber(exclusiveMinimum) && val <= exclusiveMinimum) {
731
+ if (isNumber(exclusiveMinimum) && val <= exclusiveMinimum && enabled('exclusiveMinimum')) {
612
732
  validationResult.problems.push({
613
733
  location: { offset: node.offset, length: node.length },
614
734
  message: l10n.t('Value is below the exclusive minimum of {0}.', exclusiveMinimum)
615
735
  });
616
736
  }
617
737
  const exclusiveMaximum = getExclusiveLimit(schema.maximum, schema.exclusiveMaximum);
618
- if (isNumber(exclusiveMaximum) && val >= exclusiveMaximum) {
738
+ if (isNumber(exclusiveMaximum) && val >= exclusiveMaximum && enabled('exclusiveMaximum')) {
619
739
  validationResult.problems.push({
620
740
  location: { offset: node.offset, length: node.length },
621
741
  message: l10n.t('Value is above the exclusive maximum of {0}.', exclusiveMaximum)
622
742
  });
623
743
  }
624
744
  const minimum = getLimit(schema.minimum, schema.exclusiveMinimum);
625
- if (isNumber(minimum) && val < minimum) {
745
+ if (isNumber(minimum) && val < minimum && enabled('minimum')) {
626
746
  validationResult.problems.push({
627
747
  location: { offset: node.offset, length: node.length },
628
748
  message: l10n.t('Value is below the minimum of {0}.', minimum)
629
749
  });
630
750
  }
631
751
  const maximum = getLimit(schema.maximum, schema.exclusiveMaximum);
632
- if (isNumber(maximum) && val > maximum) {
752
+ if (isNumber(maximum) && val > maximum && enabled('maximum')) {
633
753
  validationResult.problems.push({
634
754
  location: { offset: node.offset, length: node.length },
635
755
  message: l10n.t('Value is above the maximum of {0}.', maximum)
@@ -637,19 +757,19 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
637
757
  }
638
758
  }
639
759
  function _validateStringNode(node) {
640
- if (isNumber(schema.minLength) && stringLength(node.value) < schema.minLength) {
760
+ if (isNumber(schema.minLength) && stringLength(node.value) < schema.minLength && enabled('minLength')) {
641
761
  validationResult.problems.push({
642
762
  location: { offset: node.offset, length: node.length },
643
763
  message: l10n.t('String is shorter than the minimum length of {0}.', schema.minLength)
644
764
  });
645
765
  }
646
- if (isNumber(schema.maxLength) && stringLength(node.value) > schema.maxLength) {
766
+ if (isNumber(schema.maxLength) && stringLength(node.value) > schema.maxLength && enabled('maxLength')) {
647
767
  validationResult.problems.push({
648
768
  location: { offset: node.offset, length: node.length },
649
769
  message: l10n.t('String is longer than the maximum length of {0}.', schema.maxLength)
650
770
  });
651
771
  }
652
- if (isString(schema.pattern)) {
772
+ if (isString(schema.pattern) && enabled('pattern')) {
653
773
  const regex = extendedRegExp(schema.pattern);
654
774
  if (regex && !(regex.test(node.value))) {
655
775
  validationResult.problems.push({
@@ -658,7 +778,8 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
658
778
  });
659
779
  }
660
780
  }
661
- if (schema.format) {
781
+ // Only validate format if format-assertion vocabulary is active (not annotation-only)
782
+ if (schema.format && enabled('format') && isFormatAssertionEnabled(context.activeVocabularies, context.explicitSchemaDraft)) {
662
783
  switch (schema.format) {
663
784
  case 'uri':
664
785
  case 'uri-reference':
@@ -688,6 +809,8 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
688
809
  case 'date-time':
689
810
  case 'date':
690
811
  case 'time':
812
+ case 'duration':
813
+ case 'uuid':
691
814
  case 'email':
692
815
  case 'hostname':
693
816
  case 'ipv4':
@@ -723,7 +846,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
723
846
  const itemValidationResult = new ValidationResult();
724
847
  const item = node.items[index];
725
848
  if (item) {
726
- validate(item, subSchema, itemValidationResult, matchingSchemas, context);
849
+ validate(item, subSchema, itemValidationResult, matchingSchemas, context, schemaStack, schemaRoots);
727
850
  validationResult.mergePropertyMatch(itemValidationResult);
728
851
  }
729
852
  validationResult.processedProperties.add(String(index));
@@ -745,19 +868,19 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
745
868
  else {
746
869
  for (; index < node.items.length; index++) {
747
870
  const itemValidationResult = new ValidationResult();
748
- validate(node.items[index], additionalItemSchema, itemValidationResult, matchingSchemas, context);
871
+ validate(node.items[index], additionalItemSchema, itemValidationResult, matchingSchemas, context, schemaStack, schemaRoots);
749
872
  validationResult.mergePropertyMatch(itemValidationResult);
750
873
  validationResult.processedProperties.add(String(index));
751
874
  }
752
875
  }
753
876
  }
754
877
  const containsSchema = asSchema(schema.contains);
755
- if (containsSchema) {
878
+ if (containsSchema && enabled('contains')) {
756
879
  let containsCount = 0;
757
880
  for (let index = 0; index < node.items.length; index++) {
758
881
  const item = node.items[index];
759
882
  const itemValidationResult = new ValidationResult();
760
- validate(item, containsSchema, itemValidationResult, NoOpSchemaCollector.instance, context);
883
+ validate(item, containsSchema, itemValidationResult, NoOpSchemaCollector.instance, context, schemaStack, schemaRoots);
761
884
  if (!itemValidationResult.hasProblems()) {
762
885
  containsCount++;
763
886
  if (context.schemaDraft >= SchemaDraft.v2020_12) {
@@ -771,13 +894,13 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
771
894
  message: schema.errorMessage || l10n.t('Array does not contain required item.')
772
895
  });
773
896
  }
774
- if (isNumber(schema.minContains) && containsCount < schema.minContains) {
897
+ if (isNumber(schema.minContains) && containsCount < schema.minContains && enabled('minContains')) {
775
898
  validationResult.problems.push({
776
899
  location: { offset: node.offset, length: node.length },
777
900
  message: schema.errorMessage || l10n.t('Array has too few items that match the contains contraint. Expected {0} or more.', schema.minContains)
778
901
  });
779
902
  }
780
- if (isNumber(schema.maxContains) && containsCount > schema.maxContains) {
903
+ if (isNumber(schema.maxContains) && containsCount > schema.maxContains && enabled('maxContains')) {
781
904
  validationResult.problems.push({
782
905
  location: { offset: node.offset, length: node.length },
783
906
  message: schema.errorMessage || l10n.t('Array has too many items that match the contains contraint. Expected {0} or less.', schema.maxContains)
@@ -785,7 +908,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
785
908
  }
786
909
  }
787
910
  const unevaluatedItems = schema.unevaluatedItems;
788
- if (unevaluatedItems !== undefined) {
911
+ if (unevaluatedItems !== undefined && enabled('unevaluatedItems')) {
789
912
  for (let i = 0; i < node.items.length; i++) {
790
913
  if (!validationResult.processedProperties.has(String(i))) {
791
914
  if (unevaluatedItems === false) {
@@ -796,7 +919,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
796
919
  }
797
920
  else {
798
921
  const itemValidationResult = new ValidationResult();
799
- validate(node.items[i], schema.unevaluatedItems, itemValidationResult, matchingSchemas, context);
922
+ validate(node.items[i], schema.unevaluatedItems, itemValidationResult, matchingSchemas, context, schemaStack, schemaRoots);
800
923
  validationResult.mergePropertyMatch(itemValidationResult);
801
924
  }
802
925
  }
@@ -804,19 +927,19 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
804
927
  validationResult.propertiesValueMatches++;
805
928
  }
806
929
  }
807
- if (isNumber(schema.minItems) && node.items.length < schema.minItems) {
930
+ if (isNumber(schema.minItems) && node.items.length < schema.minItems && enabled('minItems')) {
808
931
  validationResult.problems.push({
809
932
  location: { offset: node.offset, length: node.length },
810
933
  message: l10n.t('Array has too few items. Expected {0} or more.', schema.minItems)
811
934
  });
812
935
  }
813
- if (isNumber(schema.maxItems) && node.items.length > schema.maxItems) {
936
+ if (isNumber(schema.maxItems) && node.items.length > schema.maxItems && enabled('maxItems')) {
814
937
  validationResult.problems.push({
815
938
  location: { offset: node.offset, length: node.length },
816
939
  message: l10n.t('Array has too many items. Expected {0} or fewer.', schema.maxItems)
817
940
  });
818
941
  }
819
- if (schema.uniqueItems === true) {
942
+ if (schema.uniqueItems === true && enabled('uniqueItems')) {
820
943
  const values = getNodeValue(node);
821
944
  function hasDuplicates() {
822
945
  for (let i = 0; i < values.length - 1; i++) {
@@ -845,13 +968,11 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
845
968
  seenKeys[key] = propertyNode.valueNode;
846
969
  unprocessedProperties.add(key);
847
970
  }
848
- if (Array.isArray(schema.required)) {
971
+ if (Array.isArray(schema.required) && enabled('required')) {
849
972
  for (const propertyName of schema.required) {
850
973
  if (!seenKeys[propertyName]) {
851
- const keyNode = node.parent && node.parent.type === 'property' && node.parent.keyNode;
852
- const location = keyNode ? { offset: keyNode.offset, length: keyNode.length } : { offset: node.offset, length: 1 };
853
974
  validationResult.problems.push({
854
- location: location,
975
+ location: { offset: node.offset, length: node.length },
855
976
  message: l10n.t('Missing property "{0}".', propertyName)
856
977
  });
857
978
  }
@@ -861,7 +982,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
861
982
  unprocessedProperties.delete(prop);
862
983
  validationResult.processedProperties.add(prop);
863
984
  };
864
- if (schema.properties) {
985
+ if (schema.properties && enabled('properties')) {
865
986
  for (const propertyName of Object.keys(schema.properties)) {
866
987
  propertyProcessed(propertyName);
867
988
  const propertySchema = schema.properties[propertyName];
@@ -882,20 +1003,28 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
882
1003
  }
883
1004
  else {
884
1005
  const propertyValidationResult = new ValidationResult();
885
- validate(child, propertySchema, propertyValidationResult, matchingSchemas, context);
1006
+ validate(child, propertySchema, propertyValidationResult, matchingSchemas, context, schemaStack, schemaRoots);
886
1007
  validationResult.mergePropertyMatch(propertyValidationResult);
887
1008
  }
888
1009
  }
889
1010
  }
890
1011
  }
891
- 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();
892
1018
  for (const propertyPattern of Object.keys(schema.patternProperties)) {
893
1019
  const regex = extendedRegExp(propertyPattern);
894
1020
  if (regex) {
895
- const processed = [];
896
- for (const propertyName of unprocessedProperties) {
1021
+ for (const propertyName of allPropertyNames) {
897
1022
  if (regex.test(propertyName)) {
898
- processed.push(propertyName);
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
+ }
899
1028
  const child = seenKeys[propertyName];
900
1029
  if (child) {
901
1030
  const propertySchema = schema.patternProperties[propertyPattern];
@@ -914,18 +1043,19 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
914
1043
  }
915
1044
  else {
916
1045
  const propertyValidationResult = new ValidationResult();
917
- validate(child, propertySchema, propertyValidationResult, matchingSchemas, context);
1046
+ validate(child, propertySchema, propertyValidationResult, matchingSchemas, context, schemaStack, schemaRoots);
918
1047
  validationResult.mergePropertyMatch(propertyValidationResult);
919
1048
  }
920
1049
  }
921
1050
  }
922
1051
  }
923
- processed.forEach(propertyProcessed);
924
1052
  }
925
1053
  }
1054
+ // Mark all matched properties as processed after all patterns have been checked
1055
+ patternPropertyMatches.forEach(propertyProcessed);
926
1056
  }
927
1057
  const additionalProperties = schema.additionalProperties;
928
- if (additionalProperties !== undefined) {
1058
+ if (additionalProperties !== undefined && enabled('additionalProperties')) {
929
1059
  for (const propertyName of unprocessedProperties) {
930
1060
  propertyProcessed(propertyName);
931
1061
  const child = seenKeys[propertyName];
@@ -939,14 +1069,32 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
939
1069
  }
940
1070
  else if (additionalProperties !== true) {
941
1071
  const propertyValidationResult = new ValidationResult();
942
- validate(child, additionalProperties, propertyValidationResult, matchingSchemas, context);
1072
+ validate(child, additionalProperties, propertyValidationResult, matchingSchemas, context, schemaStack, schemaRoots);
943
1073
  validationResult.mergePropertyMatch(propertyValidationResult);
944
1074
  }
945
1075
  }
946
1076
  }
947
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
+ }
948
1096
  const unevaluatedProperties = schema.unevaluatedProperties;
949
- if (unevaluatedProperties !== undefined) {
1097
+ if (unevaluatedProperties !== undefined && enabled('unevaluatedProperties')) {
950
1098
  const processed = [];
951
1099
  for (const propertyName of unprocessedProperties) {
952
1100
  if (!validationResult.processedProperties.has(propertyName)) {
@@ -962,7 +1110,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
962
1110
  }
963
1111
  else if (unevaluatedProperties !== true) {
964
1112
  const propertyValidationResult = new ValidationResult();
965
- validate(child, unevaluatedProperties, propertyValidationResult, matchingSchemas, context);
1113
+ validate(child, unevaluatedProperties, propertyValidationResult, matchingSchemas, context, schemaStack, schemaRoots);
966
1114
  validationResult.mergePropertyMatch(propertyValidationResult);
967
1115
  }
968
1116
  }
@@ -970,7 +1118,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
970
1118
  }
971
1119
  processed.forEach(propertyProcessed);
972
1120
  }
973
- if (isNumber(schema.maxProperties)) {
1121
+ if (isNumber(schema.maxProperties) && enabled('maxProperties')) {
974
1122
  if (node.properties.length > schema.maxProperties) {
975
1123
  validationResult.problems.push({
976
1124
  location: { offset: node.offset, length: node.length },
@@ -978,7 +1126,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
978
1126
  });
979
1127
  }
980
1128
  }
981
- if (isNumber(schema.minProperties)) {
1129
+ if (isNumber(schema.minProperties) && enabled('minProperties')) {
982
1130
  if (node.properties.length < schema.minProperties) {
983
1131
  validationResult.problems.push({
984
1132
  location: { offset: node.offset, length: node.length },
@@ -986,25 +1134,7 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
986
1134
  });
987
1135
  }
988
1136
  }
989
- if (schema.dependentRequired) {
990
- for (const key in schema.dependentRequired) {
991
- const prop = seenKeys[key];
992
- const propertyDeps = schema.dependentRequired[key];
993
- if (prop && Array.isArray(propertyDeps)) {
994
- _validatePropertyDependencies(key, propertyDeps);
995
- }
996
- }
997
- }
998
- if (schema.dependentSchemas) {
999
- for (const key in schema.dependentSchemas) {
1000
- const prop = seenKeys[key];
1001
- const propertyDeps = schema.dependentSchemas[key];
1002
- if (prop && isObject(propertyDeps)) {
1003
- _validatePropertyDependencies(key, propertyDeps);
1004
- }
1005
- }
1006
- }
1007
- if (schema.dependencies) {
1137
+ if (schema.dependencies && enabled('dependencies')) {
1008
1138
  for (const key in schema.dependencies) {
1009
1139
  const prop = seenKeys[key];
1010
1140
  if (prop) {
@@ -1013,11 +1143,11 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
1013
1143
  }
1014
1144
  }
1015
1145
  const propertyNames = asSchema(schema.propertyNames);
1016
- if (propertyNames) {
1146
+ if (propertyNames && enabled('propertyNames')) {
1017
1147
  for (const f of node.properties) {
1018
1148
  const key = f.keyNode;
1019
1149
  if (key) {
1020
- validate(key, propertyNames, validationResult, matchingSchemas, context);
1150
+ validate(key, propertyNames, validationResult, matchingSchemas, context, schemaStack, schemaRoots);
1021
1151
  }
1022
1152
  }
1023
1153
  }
@@ -1039,8 +1169,9 @@ function validate(n, schema, validationResult, matchingSchemas, context) {
1039
1169
  const propertySchema = asSchema(propertyDep);
1040
1170
  if (propertySchema) {
1041
1171
  const propertyValidationResult = new ValidationResult();
1042
- validate(node, propertySchema, propertyValidationResult, matchingSchemas, context);
1172
+ validate(node, propertySchema, propertyValidationResult, matchingSchemas, context, schemaStack, schemaRoots);
1043
1173
  validationResult.mergePropertyMatch(propertyValidationResult);
1174
+ validationResult.mergeProcessedProperties(propertyValidationResult);
1044
1175
  }
1045
1176
  }
1046
1177
  }