/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); import * as Json from 'jsonc-parser'; import { isNumber, equals, isBoolean, isString, isDefined } from '../utils/objects'; import { extendedRegExp } from '../utils/strings'; import { ErrorCode, Diagnostic, DiagnosticSeverity, Range } from '../jsonLanguageTypes'; import * as nls from 'vscode-nls'; var localize = nls.loadMessageBundle(); var formats = { 'color-hex': { errorMessage: localize('colorHexFormatWarning', 'Invalid color format. Use #RGB, #RGBA, #RRGGBB or #RRGGBBAA.'), pattern: /^#([0-9A-Fa-f]{3,4}|([0-9A-Fa-f]{2}){3,4})$/ }, 'date-time': { errorMessage: localize('dateTimeFormatWarning', '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 }, 'date': { errorMessage: localize('dateFormatWarning', 'String is not a RFC3339 date.'), pattern: /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/i }, 'time': { errorMessage: localize('timeFormatWarning', '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 }, 'email': { errorMessage: localize('emailFormatWarning', '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-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ } }; var ASTNodeImpl = /** @class */ (function () { function ASTNodeImpl(parent, offset, length) { if (length === void 0) { length = 0; } this.offset = offset; this.length = length; this.parent = parent; } Object.defineProperty(ASTNodeImpl.prototype, "children", { get: function () { return []; }, enumerable: false, configurable: true }); ASTNodeImpl.prototype.toString = function () { return 'type: ' + this.type + ' (' + this.offset + '/' + this.length + ')' + (this.parent ? ' parent: {' + this.parent.toString() + '}' : ''); }; return ASTNodeImpl; }()); export { ASTNodeImpl }; var NullASTNodeImpl = /** @class */ (function (_super) { __extends(NullASTNodeImpl, _super); function NullASTNodeImpl(parent, offset) { var _this = _super.call(this, parent, offset) || this; _this.type = 'null'; _this.value = null; return _this; } return NullASTNodeImpl; }(ASTNodeImpl)); export { NullASTNodeImpl }; var BooleanASTNodeImpl = /** @class */ (function (_super) { __extends(BooleanASTNodeImpl, _super); function BooleanASTNodeImpl(parent, boolValue, offset) { var _this = _super.call(this, parent, offset) || this; _this.type = 'boolean'; _this.value = boolValue; return _this; } return BooleanASTNodeImpl; }(ASTNodeImpl)); export { BooleanASTNodeImpl }; var ArrayASTNodeImpl = /** @class */ (function (_super) { __extends(ArrayASTNodeImpl, _super); function ArrayASTNodeImpl(parent, offset) { var _this = _super.call(this, parent, offset) || this; _this.type = 'array'; _this.items = []; return _this; } Object.defineProperty(ArrayASTNodeImpl.prototype, "children", { get: function () { return this.items; }, enumerable: false, configurable: true }); return ArrayASTNodeImpl; }(ASTNodeImpl)); export { ArrayASTNodeImpl }; var NumberASTNodeImpl = /** @class */ (function (_super) { __extends(NumberASTNodeImpl, _super); function NumberASTNodeImpl(parent, offset) { var _this = _super.call(this, parent, offset) || this; _this.type = 'number'; _this.isInteger = true; _this.value = Number.NaN; return _this; } return NumberASTNodeImpl; }(ASTNodeImpl)); export { NumberASTNodeImpl }; var StringASTNodeImpl = /** @class */ (function (_super) { __extends(StringASTNodeImpl, _super); function StringASTNodeImpl(parent, offset, length) { var _this = _super.call(this, parent, offset, length) || this; _this.type = 'string'; _this.value = ''; return _this; } return StringASTNodeImpl; }(ASTNodeImpl)); export { StringASTNodeImpl }; var PropertyASTNodeImpl = /** @class */ (function (_super) { __extends(PropertyASTNodeImpl, _super); function PropertyASTNodeImpl(parent, offset, keyNode) { var _this = _super.call(this, parent, offset) || this; _this.type = 'property'; _this.colonOffset = -1; _this.keyNode = keyNode; return _this; } Object.defineProperty(PropertyASTNodeImpl.prototype, "children", { get: function () { return this.valueNode ? [this.keyNode, this.valueNode] : [this.keyNode]; }, enumerable: false, configurable: true }); return PropertyASTNodeImpl; }(ASTNodeImpl)); export { PropertyASTNodeImpl }; var ObjectASTNodeImpl = /** @class */ (function (_super) { __extends(ObjectASTNodeImpl, _super); function ObjectASTNodeImpl(parent, offset) { var _this = _super.call(this, parent, offset) || this; _this.type = 'object'; _this.properties = []; return _this; } Object.defineProperty(ObjectASTNodeImpl.prototype, "children", { get: function () { return this.properties; }, enumerable: false, configurable: true }); return ObjectASTNodeImpl; }(ASTNodeImpl)); export { ObjectASTNodeImpl }; export function asSchema(schema) { if (isBoolean(schema)) { return schema ? {} : { "not": {} }; } return schema; } export var EnumMatch; (function (EnumMatch) { EnumMatch[EnumMatch["Key"] = 0] = "Key"; EnumMatch[EnumMatch["Enum"] = 1] = "Enum"; })(EnumMatch || (EnumMatch = {})); var SchemaCollector = /** @class */ (function () { function SchemaCollector(focusOffset, exclude) { if (focusOffset === void 0) { focusOffset = -1; } this.focusOffset = focusOffset; this.exclude = exclude; this.schemas = []; } SchemaCollector.prototype.add = function (schema) { this.schemas.push(schema); }; SchemaCollector.prototype.merge = function (other) { Array.prototype.push.apply(this.schemas, other.schemas); }; SchemaCollector.prototype.include = function (node) { return (this.focusOffset === -1 || contains(node, this.focusOffset)) && (node !== this.exclude); }; SchemaCollector.prototype.newSub = function () { return new SchemaCollector(-1, this.exclude); }; return SchemaCollector; }()); var NoOpSchemaCollector = /** @class */ (function () { function NoOpSchemaCollector() { } Object.defineProperty(NoOpSchemaCollector.prototype, "schemas", { get: function () { return []; }, enumerable: false, configurable: true }); NoOpSchemaCollector.prototype.add = function (schema) { }; NoOpSchemaCollector.prototype.merge = function (other) { }; NoOpSchemaCollector.prototype.include = function (node) { return true; }; NoOpSchemaCollector.prototype.newSub = function () { return this; }; NoOpSchemaCollector.instance = new NoOpSchemaCollector(); return NoOpSchemaCollector; }()); var ValidationResult = /** @class */ (function () { function ValidationResult() { this.problems = []; this.propertiesMatches = 0; this.propertiesValueMatches = 0; this.primaryValueMatches = 0; this.enumValueMatch = false; this.enumValues = undefined; } ValidationResult.prototype.hasProblems = function () { return !!this.problems.length; }; ValidationResult.prototype.mergeAll = function (validationResults) { for (var _i = 0, validationResults_1 = validationResults; _i < validationResults_1.length; _i++) { var validationResult = validationResults_1[_i]; this.merge(validationResult); } }; ValidationResult.prototype.merge = function (validationResult) { this.problems = this.problems.concat(validationResult.problems); }; ValidationResult.prototype.mergeEnumValues = function (validationResult) { if (!this.enumValueMatch && !validationResult.enumValueMatch && this.enumValues && validationResult.enumValues) { this.enumValues = this.enumValues.concat(validationResult.enumValues); for (var _i = 0, _a = this.problems; _i < _a.length; _i++) { var error = _a[_i]; if (error.code === ErrorCode.EnumValueMismatch) { error.message = localize('enumWarning', 'Value is not accepted. Valid values: {0}.', this.enumValues.map(function (v) { return JSON.stringify(v); }).join(', ')); } } } }; ValidationResult.prototype.mergePropertyMatch = function (propertyValidationResult) { this.merge(propertyValidationResult); this.propertiesMatches++; if (propertyValidationResult.enumValueMatch || !propertyValidationResult.hasProblems() && propertyValidationResult.propertiesMatches) { this.propertiesValueMatches++; } if (propertyValidationResult.enumValueMatch && propertyValidationResult.enumValues && propertyValidationResult.enumValues.length === 1) { this.primaryValueMatches++; } }; ValidationResult.prototype.compare = function (other) { var hasProblems = this.hasProblems(); if (hasProblems !== other.hasProblems()) { return hasProblems ? -1 : 1; } if (this.enumValueMatch !== other.enumValueMatch) { return other.enumValueMatch ? -1 : 1; } if (this.primaryValueMatches !== other.primaryValueMatches) { return this.primaryValueMatches - other.primaryValueMatches; } if (this.propertiesValueMatches !== other.propertiesValueMatches) { return this.propertiesValueMatches - other.propertiesValueMatches; } return this.propertiesMatches - other.propertiesMatches; }; return ValidationResult; }()); export { ValidationResult }; export function newJSONDocument(root, diagnostics) { if (diagnostics === void 0) { diagnostics = []; } return new JSONDocument(root, diagnostics, []); } export function getNodeValue(node) { return Json.getNodeValue(node); } export function getNodePath(node) { return Json.getNodePath(node); } export function contains(node, offset, includeRightBound) { if (includeRightBound === void 0) { includeRightBound = false; } return offset >= node.offset && offset < (node.offset + node.length) || includeRightBound && offset === (node.offset + node.length); } var JSONDocument = /** @class */ (function () { function JSONDocument(root, syntaxErrors, comments) { if (syntaxErrors === void 0) { syntaxErrors = []; } if (comments === void 0) { comments = []; } this.root = root; this.syntaxErrors = syntaxErrors; this.comments = comments; } JSONDocument.prototype.getNodeFromOffset = function (offset, includeRightBound) { if (includeRightBound === void 0) { includeRightBound = false; } if (this.root) { return Json.findNodeAtOffset(this.root, offset, includeRightBound); } return undefined; }; JSONDocument.prototype.visit = function (visitor) { if (this.root) { var doVisit_1 = function (node) { var ctn = visitor(node); var children = node.children; if (Array.isArray(children)) { for (var i = 0; i < children.length && ctn; i++) { ctn = doVisit_1(children[i]); } } return ctn; }; doVisit_1(this.root); } }; JSONDocument.prototype.validate = function (textDocument, schema, severity) { if (severity === void 0) { severity = DiagnosticSeverity.Warning; } if (this.root && schema) { var validationResult = new ValidationResult(); validate(this.root, schema, validationResult, NoOpSchemaCollector.instance); return validationResult.problems.map(function (p) { var _a; var range = Range.create(textDocument.positionAt(p.location.offset), textDocument.positionAt(p.location.offset + p.location.length)); return Diagnostic.create(range, p.message, (_a = p.severity) !== null && _a !== void 0 ? _a : severity, p.code); }); } return undefined; }; JSONDocument.prototype.getMatchingSchemas = function (schema, focusOffset, exclude) { if (focusOffset === void 0) { focusOffset = -1; } var matchingSchemas = new SchemaCollector(focusOffset, exclude); if (this.root && schema) { validate(this.root, schema, new ValidationResult(), matchingSchemas); } return matchingSchemas.schemas; }; return JSONDocument; }()); export { JSONDocument }; function validate(n, schema, validationResult, matchingSchemas) { if (!n || !matchingSchemas.include(n)) { return; } var node = n; switch (node.type) { case 'object': _validateObjectNode(node, schema, validationResult, matchingSchemas); break; case 'array': _validateArrayNode(node, schema, validationResult, matchingSchemas); break; case 'string': _validateStringNode(node, schema, validationResult, matchingSchemas); break; case 'number': _validateNumberNode(node, schema, validationResult, matchingSchemas); break; case 'property': return validate(node.valueNode, schema, validationResult, matchingSchemas); } _validateNode(); matchingSchemas.add({ node: node, schema: schema }); function _validateNode() { function matchesType(type) { return node.type === type || (type === 'integer' && node.type === 'number' && node.isInteger); } if (Array.isArray(schema.type)) { if (!schema.type.some(matchesType)) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: schema.errorMessage || localize('typeArrayMismatchWarning', 'Incorrect type. Expected one of {0}.', schema.type.join(', ')) }); } } else if (schema.type) { if (!matchesType(schema.type)) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: schema.errorMessage || localize('typeMismatchWarning', 'Incorrect type. Expected "{0}".', schema.type) }); } } if (Array.isArray(schema.allOf)) { for (var _i = 0, _a = schema.allOf; _i < _a.length; _i++) { var subSchemaRef = _a[_i]; validate(node, asSchema(subSchemaRef), validationResult, matchingSchemas); } } var notSchema = asSchema(schema.not); if (notSchema) { var subValidationResult = new ValidationResult(); var subMatchingSchemas = matchingSchemas.newSub(); validate(node, notSchema, subValidationResult, subMatchingSchemas); if (!subValidationResult.hasProblems()) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: localize('notSchemaWarning', "Matches a schema that is not allowed.") }); } for (var _b = 0, _c = subMatchingSchemas.schemas; _b < _c.length; _b++) { var ms = _c[_b]; ms.inverted = !ms.inverted; matchingSchemas.add(ms); } } var testAlternatives = function (alternatives, maxOneMatch) { var matches = []; // remember the best match that is used for error messages var bestMatch = undefined; for (var _i = 0, alternatives_1 = alternatives; _i < alternatives_1.length; _i++) { var subSchemaRef = alternatives_1[_i]; var subSchema = asSchema(subSchemaRef); var subValidationResult = new ValidationResult(); var subMatchingSchemas = matchingSchemas.newSub(); validate(node, subSchema, subValidationResult, subMatchingSchemas); if (!subValidationResult.hasProblems()) { matches.push(subSchema); } if (!bestMatch) { bestMatch = { schema: subSchema, validationResult: subValidationResult, matchingSchemas: subMatchingSchemas }; } else { if (!maxOneMatch && !subValidationResult.hasProblems() && !bestMatch.validationResult.hasProblems()) { // no errors, both are equally good matches bestMatch.matchingSchemas.merge(subMatchingSchemas); bestMatch.validationResult.propertiesMatches += subValidationResult.propertiesMatches; bestMatch.validationResult.propertiesValueMatches += subValidationResult.propertiesValueMatches; } else { var compareResult = subValidationResult.compare(bestMatch.validationResult); if (compareResult > 0) { // our node is the best matching so far bestMatch = { schema: subSchema, validationResult: subValidationResult, matchingSchemas: subMatchingSchemas }; } else if (compareResult === 0) { // there's already a best matching but we are as good bestMatch.matchingSchemas.merge(subMatchingSchemas); bestMatch.validationResult.mergeEnumValues(subValidationResult); } } } } if (matches.length > 1 && maxOneMatch) { validationResult.problems.push({ location: { offset: node.offset, length: 1 }, message: localize('oneOfWarning', "Matches multiple schemas when only one must validate.") }); } if (bestMatch) { validationResult.merge(bestMatch.validationResult); validationResult.propertiesMatches += bestMatch.validationResult.propertiesMatches; validationResult.propertiesValueMatches += bestMatch.validationResult.propertiesValueMatches; matchingSchemas.merge(bestMatch.matchingSchemas); } return matches.length; }; if (Array.isArray(schema.anyOf)) { testAlternatives(schema.anyOf, false); } if (Array.isArray(schema.oneOf)) { testAlternatives(schema.oneOf, true); } var testBranch = function (schema) { var subValidationResult = new ValidationResult(); var subMatchingSchemas = matchingSchemas.newSub(); validate(node, asSchema(schema), subValidationResult, subMatchingSchemas); validationResult.merge(subValidationResult); validationResult.propertiesMatches += subValidationResult.propertiesMatches; validationResult.propertiesValueMatches += subValidationResult.propertiesValueMatches; matchingSchemas.merge(subMatchingSchemas); }; var testCondition = function (ifSchema, thenSchema, elseSchema) { var subSchema = asSchema(ifSchema); var subValidationResult = new ValidationResult(); var subMatchingSchemas = matchingSchemas.newSub(); validate(node, subSchema, subValidationResult, subMatchingSchemas); matchingSchemas.merge(subMatchingSchemas); if (!subValidationResult.hasProblems()) { if (thenSchema) { testBranch(thenSchema); } } else if (elseSchema) { testBranch(elseSchema); } }; var ifSchema = asSchema(schema.if); if (ifSchema) { testCondition(ifSchema, asSchema(schema.then), asSchema(schema.else)); } if (Array.isArray(schema.enum)) { var val = getNodeValue(node); var enumValueMatch = false; for (var _d = 0, _e = schema.enum; _d < _e.length; _d++) { var e = _e[_d]; if (equals(val, e)) { enumValueMatch = true; break; } } validationResult.enumValues = schema.enum; validationResult.enumValueMatch = enumValueMatch; if (!enumValueMatch) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, code: ErrorCode.EnumValueMismatch, message: schema.errorMessage || localize('enumWarning', 'Value is not accepted. Valid values: {0}.', schema.enum.map(function (v) { return JSON.stringify(v); }).join(', ')) }); } } if (isDefined(schema.const)) { var val = getNodeValue(node); if (!equals(val, schema.const)) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, code: ErrorCode.EnumValueMismatch, message: schema.errorMessage || localize('constWarning', 'Value must be {0}.', JSON.stringify(schema.const)) }); validationResult.enumValueMatch = false; } else { validationResult.enumValueMatch = true; } validationResult.enumValues = [schema.const]; } if (schema.deprecationMessage && node.parent) { validationResult.problems.push({ location: { offset: node.parent.offset, length: node.parent.length }, severity: DiagnosticSeverity.Warning, message: schema.deprecationMessage, code: ErrorCode.Deprecated }); } } function _validateNumberNode(node, schema, validationResult, matchingSchemas) { var val = node.value; function normalizeFloats(float) { var _a; var parts = /^(-?\d+)(?:\.(\d+))?(?:e([-+]\d+))?$/.exec(float.toString()); return parts && { value: Number(parts[1] + (parts[2] || '')), multiplier: (((_a = parts[2]) === null || _a === void 0 ? void 0 : _a.length) || 0) - (parseInt(parts[3]) || 0) }; } ; if (isNumber(schema.multipleOf)) { var remainder = -1; if (Number.isInteger(schema.multipleOf)) { remainder = val % schema.multipleOf; } else { var normMultipleOf = normalizeFloats(schema.multipleOf); var normValue = normalizeFloats(val); if (normMultipleOf && normValue) { var multiplier = Math.pow(10, Math.abs(normValue.multiplier - normMultipleOf.multiplier)); if (normValue.multiplier < normMultipleOf.multiplier) { normValue.value *= multiplier; } else { normMultipleOf.value *= multiplier; } remainder = normValue.value % normMultipleOf.value; } } if (remainder !== 0) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: localize('multipleOfWarning', 'Value is not divisible by {0}.', schema.multipleOf) }); } } function getExclusiveLimit(limit, exclusive) { if (isNumber(exclusive)) { return exclusive; } if (isBoolean(exclusive) && exclusive) { return limit; } return undefined; } function getLimit(limit, exclusive) { if (!isBoolean(exclusive) || !exclusive) { return limit; } return undefined; } var exclusiveMinimum = getExclusiveLimit(schema.minimum, schema.exclusiveMinimum); if (isNumber(exclusiveMinimum) && val <= exclusiveMinimum) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: localize('exclusiveMinimumWarning', 'Value is below the exclusive minimum of {0}.', exclusiveMinimum) }); } var exclusiveMaximum = getExclusiveLimit(schema.maximum, schema.exclusiveMaximum); if (isNumber(exclusiveMaximum) && val >= exclusiveMaximum) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: localize('exclusiveMaximumWarning', 'Value is above the exclusive maximum of {0}.', exclusiveMaximum) }); } var minimum = getLimit(schema.minimum, schema.exclusiveMinimum); if (isNumber(minimum) && val < minimum) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: localize('minimumWarning', 'Value is below the minimum of {0}.', minimum) }); } var maximum = getLimit(schema.maximum, schema.exclusiveMaximum); if (isNumber(maximum) && val > maximum) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: localize('maximumWarning', 'Value is above the maximum of {0}.', maximum) }); } } function _validateStringNode(node, schema, validationResult, matchingSchemas) { if (isNumber(schema.minLength) && node.value.length < schema.minLength) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: localize('minLengthWarning', 'String is shorter than the minimum length of {0}.', schema.minLength) }); } if (isNumber(schema.maxLength) && node.value.length > schema.maxLength) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: localize('maxLengthWarning', 'String is longer than the maximum length of {0}.', schema.maxLength) }); } if (isString(schema.pattern)) { var regex = extendedRegExp(schema.pattern); if (!regex.test(node.value)) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: schema.patternErrorMessage || schema.errorMessage || localize('patternWarning', 'String does not match the pattern of "{0}".', schema.pattern) }); } } if (schema.format) { switch (schema.format) { case 'uri': case 'uri-reference': { var errorMessage = void 0; if (!node.value) { errorMessage = localize('uriEmpty', 'URI expected.'); } else { var match = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/.exec(node.value); if (!match) { errorMessage = localize('uriMissing', 'URI is expected.'); } else if (!match[2] && schema.format === 'uri') { errorMessage = localize('uriSchemeMissing', 'URI with a scheme is expected.'); } } if (errorMessage) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: schema.patternErrorMessage || schema.errorMessage || localize('uriFormatWarning', 'String is not a URI: {0}', errorMessage) }); } } break; case 'color-hex': case 'date-time': case 'date': case 'time': case 'email': var format = formats[schema.format]; if (!node.value || !format.pattern.exec(node.value)) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: schema.patternErrorMessage || schema.errorMessage || format.errorMessage }); } default: } } } function _validateArrayNode(node, schema, validationResult, matchingSchemas) { if (Array.isArray(schema.items)) { var subSchemas = schema.items; for (var index = 0; index < subSchemas.length; index++) { var subSchemaRef = subSchemas[index]; var subSchema = asSchema(subSchemaRef); var itemValidationResult = new ValidationResult(); var item = node.items[index]; if (item) { validate(item, subSchema, itemValidationResult, matchingSchemas); validationResult.mergePropertyMatch(itemValidationResult); } else if (node.items.length >= subSchemas.length) { validationResult.propertiesValueMatches++; } } if (node.items.length > subSchemas.length) { if (typeof schema.additionalItems === 'object') { for (var i = subSchemas.length; i < node.items.length; i++) { var itemValidationResult = new ValidationResult(); validate(node.items[i], schema.additionalItems, itemValidationResult, matchingSchemas); validationResult.mergePropertyMatch(itemValidationResult); } } else if (schema.additionalItems === false) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: localize('additionalItemsWarning', 'Array has too many items according to schema. Expected {0} or fewer.', subSchemas.length) }); } } } else { var itemSchema = asSchema(schema.items); if (itemSchema) { for (var _i = 0, _a = node.items; _i < _a.length; _i++) { var item = _a[_i]; var itemValidationResult = new ValidationResult(); validate(item, itemSchema, itemValidationResult, matchingSchemas); validationResult.mergePropertyMatch(itemValidationResult); } } } var containsSchema = asSchema(schema.contains); if (containsSchema) { var doesContain = node.items.some(function (item) { var itemValidationResult = new ValidationResult(); validate(item, containsSchema, itemValidationResult, NoOpSchemaCollector.instance); return !itemValidationResult.hasProblems(); }); if (!doesContain) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: schema.errorMessage || localize('requiredItemMissingWarning', 'Array does not contain required item.') }); } } if (isNumber(schema.minItems) && node.items.length < schema.minItems) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: localize('minItemsWarning', 'Array has too few items. Expected {0} or more.', schema.minItems) }); } if (isNumber(schema.maxItems) && node.items.length > schema.maxItems) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: localize('maxItemsWarning', 'Array has too many items. Expected {0} or fewer.', schema.maxItems) }); } if (schema.uniqueItems === true) { var values_1 = getNodeValue(node); var duplicates = values_1.some(function (value, index) { return index !== values_1.lastIndexOf(value); }); if (duplicates) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: localize('uniqueItemsWarning', 'Array has duplicate items.') }); } } } function _validateObjectNode(node, schema, validationResult, matchingSchemas) { var seenKeys = Object.create(null); var unprocessedProperties = []; for (var _i = 0, _a = node.properties; _i < _a.length; _i++) { var propertyNode = _a[_i]; var key = propertyNode.keyNode.value; seenKeys[key] = propertyNode.valueNode; unprocessedProperties.push(key); } if (Array.isArray(schema.required)) { for (var _b = 0, _c = schema.required; _b < _c.length; _b++) { var propertyName = _c[_b]; if (!seenKeys[propertyName]) { var keyNode = node.parent && node.parent.type === 'property' && node.parent.keyNode; var location = keyNode ? { offset: keyNode.offset, length: keyNode.length } : { offset: node.offset, length: 1 }; validationResult.problems.push({ location: location, message: localize('MissingRequiredPropWarning', 'Missing property "{0}".', propertyName) }); } } } var propertyProcessed = function (prop) { var index = unprocessedProperties.indexOf(prop); while (index >= 0) { unprocessedProperties.splice(index, 1); index = unprocessedProperties.indexOf(prop); } }; if (schema.properties) { for (var _d = 0, _e = Object.keys(schema.properties); _d < _e.length; _d++) { var propertyName = _e[_d]; propertyProcessed(propertyName); var propertySchema = schema.properties[propertyName]; var child = seenKeys[propertyName]; if (child) { if (isBoolean(propertySchema)) { if (!propertySchema) { var propertyNode = child.parent; validationResult.problems.push({ location: { offset: propertyNode.keyNode.offset, length: propertyNode.keyNode.length }, message: schema.errorMessage || localize('DisallowedExtraPropWarning', 'Property {0} is not allowed.', propertyName) }); } else { validationResult.propertiesMatches++; validationResult.propertiesValueMatches++; } } else { var propertyValidationResult = new ValidationResult(); validate(child, propertySchema, propertyValidationResult, matchingSchemas); validationResult.mergePropertyMatch(propertyValidationResult); } } } } if (schema.patternProperties) { for (var _f = 0, _g = Object.keys(schema.patternProperties); _f < _g.length; _f++) { var propertyPattern = _g[_f]; var regex = extendedRegExp(propertyPattern); for (var _h = 0, _j = unprocessedProperties.slice(0); _h < _j.length; _h++) { var propertyName = _j[_h]; if (regex.test(propertyName)) { propertyProcessed(propertyName); var child = seenKeys[propertyName]; if (child) { var propertySchema = schema.patternProperties[propertyPattern]; if (isBoolean(propertySchema)) { if (!propertySchema) { var propertyNode = child.parent; validationResult.problems.push({ location: { offset: propertyNode.keyNode.offset, length: propertyNode.keyNode.length }, message: schema.errorMessage || localize('DisallowedExtraPropWarning', 'Property {0} is not allowed.', propertyName) }); } else { validationResult.propertiesMatches++; validationResult.propertiesValueMatches++; } } else { var propertyValidationResult = new ValidationResult(); validate(child, propertySchema, propertyValidationResult, matchingSchemas); validationResult.mergePropertyMatch(propertyValidationResult); } } } } } } if (typeof schema.additionalProperties === 'object') { for (var _k = 0, unprocessedProperties_1 = unprocessedProperties; _k < unprocessedProperties_1.length; _k++) { var propertyName = unprocessedProperties_1[_k]; var child = seenKeys[propertyName]; if (child) { var propertyValidationResult = new ValidationResult(); validate(child, schema.additionalProperties, propertyValidationResult, matchingSchemas); validationResult.mergePropertyMatch(propertyValidationResult); } } } else if (schema.additionalProperties === false) { if (unprocessedProperties.length > 0) { for (var _l = 0, unprocessedProperties_2 = unprocessedProperties; _l < unprocessedProperties_2.length; _l++) { var propertyName = unprocessedProperties_2[_l]; var child = seenKeys[propertyName]; if (child) { var propertyNode = child.parent; validationResult.problems.push({ location: { offset: propertyNode.keyNode.offset, length: propertyNode.keyNode.length }, message: schema.errorMessage || localize('DisallowedExtraPropWarning', 'Property {0} is not allowed.', propertyName) }); } } } } if (isNumber(schema.maxProperties)) { if (node.properties.length > schema.maxProperties) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: localize('MaxPropWarning', 'Object has more properties than limit of {0}.', schema.maxProperties) }); } } if (isNumber(schema.minProperties)) { if (node.properties.length < schema.minProperties) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: localize('MinPropWarning', 'Object has fewer properties than the required number of {0}', schema.minProperties) }); } } if (schema.dependencies) { for (var _m = 0, _o = Object.keys(schema.dependencies); _m < _o.length; _m++) { var key = _o[_m]; var prop = seenKeys[key]; if (prop) { var propertyDep = schema.dependencies[key]; if (Array.isArray(propertyDep)) { for (var _p = 0, propertyDep_1 = propertyDep; _p < propertyDep_1.length; _p++) { var requiredProp = propertyDep_1[_p]; if (!seenKeys[requiredProp]) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, message: localize('RequiredDependentPropWarning', 'Object is missing property {0} required by property {1}.', requiredProp, key) }); } else { validationResult.propertiesValueMatches++; } } } else { var propertySchema = asSchema(propertyDep); if (propertySchema) { var propertyValidationResult = new ValidationResult(); validate(node, propertySchema, propertyValidationResult, matchingSchemas); validationResult.mergePropertyMatch(propertyValidationResult); } } } } } var propertyNames = asSchema(schema.propertyNames); if (propertyNames) { for (var _q = 0, _r = node.properties; _q < _r.length; _q++) { var f = _r[_q]; var key = f.keyNode; if (key) { validate(key, propertyNames, validationResult, NoOpSchemaCollector.instance); } } } } } export function parse(textDocument, config) { var problems = []; var lastProblemOffset = -1; var text = textDocument.getText(); var scanner = Json.createScanner(text, false); var commentRanges = config && config.collectComments ? [] : undefined; function _scanNext() { while (true) { var token_1 = scanner.scan(); _checkScanError(); switch (token_1) { case 12 /* LineCommentTrivia */: case 13 /* BlockCommentTrivia */: if (Array.isArray(commentRanges)) { commentRanges.push(Range.create(textDocument.positionAt(scanner.getTokenOffset()), textDocument.positionAt(scanner.getTokenOffset() + scanner.getTokenLength()))); } break; case 15 /* Trivia */: case 14 /* LineBreakTrivia */: break; default: return token_1; } } } function _accept(token) { if (scanner.getToken() === token) { _scanNext(); return true; } return false; } function _errorAtRange(message, code, startOffset, endOffset, severity) { if (severity === void 0) { severity = DiagnosticSeverity.Error; } if (problems.length === 0 || startOffset !== lastProblemOffset) { var range = Range.create(textDocument.positionAt(startOffset), textDocument.positionAt(endOffset)); problems.push(Diagnostic.create(range, message, severity, code, textDocument.languageId)); lastProblemOffset = startOffset; } } function _error(message, code, node, skipUntilAfter, skipUntil) { if (node === void 0) { node = undefined; } if (skipUntilAfter === void 0) { skipUntilAfter = []; } if (skipUntil === void 0) { skipUntil = []; } var start = scanner.getTokenOffset(); var end = scanner.getTokenOffset() + scanner.getTokenLength(); if (start === end && start > 0) { start--; while (start > 0 && /\s/.test(text.charAt(start))) { start--; } end = start + 1; } _errorAtRange(message, code, start, end); if (node) { _finalize(node, false); } if (skipUntilAfter.length + skipUntil.length > 0) { var token_2 = scanner.getToken(); while (token_2 !== 17 /* EOF */) { if (skipUntilAfter.indexOf(token_2) !== -1) { _scanNext(); break; } else if (skipUntil.indexOf(token_2) !== -1) { break; } token_2 = _scanNext(); } } return node; } function _checkScanError() { switch (scanner.getTokenError()) { case 4 /* InvalidUnicode */: _error(localize('InvalidUnicode', 'Invalid unicode sequence in string.'), ErrorCode.InvalidUnicode); return true; case 5 /* InvalidEscapeCharacter */: _error(localize('InvalidEscapeCharacter', 'Invalid escape character in string.'), ErrorCode.InvalidEscapeCharacter); return true; case 3 /* UnexpectedEndOfNumber */: _error(localize('UnexpectedEndOfNumber', 'Unexpected end of number.'), ErrorCode.UnexpectedEndOfNumber); return true; case 1 /* UnexpectedEndOfComment */: _error(localize('UnexpectedEndOfComment', 'Unexpected end of comment.'), ErrorCode.UnexpectedEndOfComment); return true; case 2 /* UnexpectedEndOfString */: _error(localize('UnexpectedEndOfString', 'Unexpected end of string.'), ErrorCode.UnexpectedEndOfString); return true; case 6 /* InvalidCharacter */: _error(localize('InvalidCharacter', 'Invalid characters in string. Control characters must be escaped.'), ErrorCode.InvalidCharacter); return true; } return false; } function _finalize(node, scanNext) { node.length = scanner.getTokenOffset() + scanner.getTokenLength() - node.offset; if (scanNext) { _scanNext(); } return node; } function _parseArray(parent) { if (scanner.getToken() !== 3 /* OpenBracketToken */) { return undefined; } var node = new ArrayASTNodeImpl(parent, scanner.getTokenOffset()); _scanNext(); // consume OpenBracketToken var count = 0; var needsComma = false; while (scanner.getToken() !== 4 /* CloseBracketToken */ && scanner.getToken() !== 17 /* EOF */) { if (scanner.getToken() === 5 /* CommaToken */) { if (!needsComma) { _error(localize('ValueExpected', 'Value expected'), ErrorCode.ValueExpected); } var commaOffset = scanner.getTokenOffset(); _scanNext(); // consume comma if (scanner.getToken() === 4 /* CloseBracketToken */) { if (needsComma) { _errorAtRange(localize('TrailingComma', 'Trailing comma'), ErrorCode.TrailingComma, commaOffset, commaOffset + 1); } continue; } } else if (needsComma) { _error(localize('ExpectedComma', 'Expected comma'), ErrorCode.CommaExpected); } var item = _parseValue(node); if (!item) { _error(localize('PropertyExpected', 'Value expected'), ErrorCode.ValueExpected, undefined, [], [4 /* CloseBracketToken */, 5 /* CommaToken */]); } else { node.items.push(item); } needsComma = true; } if (scanner.getToken() !== 4 /* CloseBracketToken */) { return _error(localize('ExpectedCloseBracket', 'Expected comma or closing bracket'), ErrorCode.CommaOrCloseBacketExpected, node); } return _finalize(node, true); } var keyPlaceholder = new StringASTNodeImpl(undefined, 0, 0); function _parseProperty(parent, keysSeen) { var node = new PropertyASTNodeImpl(parent, scanner.getTokenOffset(), keyPlaceholder); var key = _parseString(node); if (!key) { if (scanner.getToken() === 16 /* Unknown */) { // give a more helpful error message _error(localize('DoubleQuotesExpected', 'Property keys must be doublequoted'), ErrorCode.Undefined); var keyNode = new StringASTNodeImpl(node, scanner.getTokenOffset(), scanner.getTokenLength()); keyNode.value = scanner.getTokenValue(); key = keyNode; _scanNext(); // consume Unknown } else { return undefined; } } node.keyNode = key; var seen = keysSeen[key.value]; if (seen) { _errorAtRange(localize('DuplicateKeyWarning', "Duplicate object key"), ErrorCode.DuplicateKey, node.keyNode.offset, node.keyNode.offset + node.keyNode.length, DiagnosticSeverity.Warning); if (typeof seen === 'object') { _errorAtRange(localize('DuplicateKeyWarning', "Duplicate object key"), ErrorCode.DuplicateKey, seen.keyNode.offset, seen.keyNode.offset + seen.keyNode.length, DiagnosticSeverity.Warning); } keysSeen[key.value] = true; // if the same key is duplicate again, avoid duplicate error reporting } else { keysSeen[key.value] = node; } if (scanner.getToken() === 6 /* ColonToken */) { node.colonOffset = scanner.getTokenOffset(); _scanNext(); // consume ColonToken } else { _error(localize('ColonExpected', 'Colon expected'), ErrorCode.ColonExpected); if (scanner.getToken() === 10 /* StringLiteral */ && textDocument.positionAt(key.offset + key.length).line < textDocument.positionAt(scanner.getTokenOffset()).line) { node.length = key.length; return node; } } var value = _parseValue(node); if (!value) { return _error(localize('ValueExpected', 'Value expected'), ErrorCode.ValueExpected, node, [], [2 /* CloseBraceToken */, 5 /* CommaToken */]); } node.valueNode = value; node.length = value.offset + value.length - node.offset; return node; } function _parseObject(parent) { if (scanner.getToken() !== 1 /* OpenBraceToken */) { return undefined; } var node = new ObjectASTNodeImpl(parent, scanner.getTokenOffset()); var keysSeen = Object.create(null); _scanNext(); // consume OpenBraceToken var needsComma = false; while (scanner.getToken() !== 2 /* CloseBraceToken */ && scanner.getToken() !== 17 /* EOF */) { if (scanner.getToken() === 5 /* CommaToken */) { if (!needsComma) { _error(localize('PropertyExpected', 'Property expected'), ErrorCode.PropertyExpected); } var commaOffset = scanner.getTokenOffset(); _scanNext(); // consume comma if (scanner.getToken() === 2 /* CloseBraceToken */) { if (needsComma) { _errorAtRange(localize('TrailingComma', 'Trailing comma'), ErrorCode.TrailingComma, commaOffset, commaOffset + 1); } continue; } } else if (needsComma) { _error(localize('ExpectedComma', 'Expected comma'), ErrorCode.CommaExpected); } var property = _parseProperty(node, keysSeen); if (!property) { _error(localize('PropertyExpected', 'Property expected'), ErrorCode.PropertyExpected, undefined, [], [2 /* CloseBraceToken */, 5 /* CommaToken */]); } else { node.properties.push(property); } needsComma = true; } if (scanner.getToken() !== 2 /* CloseBraceToken */) { return _error(localize('ExpectedCloseBrace', 'Expected comma or closing brace'), ErrorCode.CommaOrCloseBraceExpected, node); } return _finalize(node, true); } function _parseString(parent) { if (scanner.getToken() !== 10 /* StringLiteral */) { return undefined; } var node = new StringASTNodeImpl(parent, scanner.getTokenOffset()); node.value = scanner.getTokenValue(); return _finalize(node, true); } function _parseNumber(parent) { if (scanner.getToken() !== 11 /* NumericLiteral */) { return undefined; } var node = new NumberASTNodeImpl(parent, scanner.getTokenOffset()); if (scanner.getTokenError() === 0 /* None */) { var tokenValue = scanner.getTokenValue(); try { var numberValue = JSON.parse(tokenValue); if (!isNumber(numberValue)) { return _error(localize('InvalidNumberFormat', 'Invalid number format.'), ErrorCode.Undefined, node); } node.value = numberValue; } catch (e) { return _error(localize('InvalidNumberFormat', 'Invalid number format.'), ErrorCode.Undefined, node); } node.isInteger = tokenValue.indexOf('.') === -1; } return _finalize(node, true); } function _parseLiteral(parent) { var node; switch (scanner.getToken()) { case 7 /* NullKeyword */: return _finalize(new NullASTNodeImpl(parent, scanner.getTokenOffset()), true); case 8 /* TrueKeyword */: return _finalize(new BooleanASTNodeImpl(parent, true, scanner.getTokenOffset()), true); case 9 /* FalseKeyword */: return _finalize(new BooleanASTNodeImpl(parent, false, scanner.getTokenOffset()), true); default: return undefined; } } function _parseValue(parent) { return _parseArray(parent) || _parseObject(parent) || _parseString(parent) || _parseNumber(parent) || _parseLiteral(parent); } var _root = undefined; var token = _scanNext(); if (token !== 17 /* EOF */) { _root = _parseValue(_root); if (!_root) { _error(localize('Invalid symbol', 'Expected a JSON object, array or literal.'), ErrorCode.Undefined); } else if (scanner.getToken() !== 17 /* EOF */) { _error(localize('End of file expected', 'End of file expected.'), ErrorCode.Undefined); } } return new JSONDocument(_root, problems, commentRanges); }