UNPKG

monaco-editor

Version:
1,067 lines (1,066 loc) • 56.1 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; 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 (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); } return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); import * as Json from './../../jsonc-parser/main.js'; import * as objects from '../utils/objects.js'; import { ErrorCode } from '../jsonLanguageTypes.js'; import * as nls from './../../../fillers/vscode-nls.js'; import Uri from './../../vscode-uri/index.js'; import { Diagnostic, DiagnosticSeverity, Range } from './../../vscode-languageserver-types/main.js'; var localize = nls.loadMessageBundle(); var colorHexPattern = /^#([0-9A-Fa-f]{3,4}|([0-9A-Fa-f]{2}){3,4})$/; var emailPattern = /^(([^<>()\[\]\\.,;:\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) { this.offset = offset; this.length = length; this.parent = parent; } Object.defineProperty(ASTNodeImpl.prototype, "children", { get: function () { return []; }, enumerable: true, 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: true, 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) { var _this = _super.call(this, parent, offset) || this; _this.type = 'property'; _this.colonOffset = -1; return _this; } Object.defineProperty(PropertyASTNodeImpl.prototype, "children", { get: function () { return this.valueNode ? [this.keyNode, this.valueNode] : [this.keyNode]; }, enumerable: true, 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: true, configurable: true }); return ObjectASTNodeImpl; }(ASTNodeImpl)); export { ObjectASTNodeImpl }; export function asSchema(schema) { if (typeof schema === 'boolean') { 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; } if (exclude === void 0) { exclude = null; } this.focusOffset = focusOffset; this.exclude = exclude; this.schemas = []; } SchemaCollector.prototype.add = function (schema) { this.schemas.push(schema); }; SchemaCollector.prototype.merge = function (other) { var _a; (_a = this.schemas).push.apply(_a, 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: true, 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 = null; } ValidationResult.prototype.hasProblems = function () { return !!this.problems.length; }; ValidationResult.prototype.mergeAll = function (validationResults) { var _this = this; validationResults.forEach(function (validationResult) { _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 void 0; }; 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) { if (this.root && schema) { var validationResult = new ValidationResult(); validate(this.root, schema, validationResult, NoOpSchemaCollector.instance); return validationResult.problems.map(function (p) { var range = Range.create(textDocument.positionAt(p.location.offset), textDocument.positionAt(p.location.offset + p.location.length)); return Diagnostic.create(range, p.message, p.severity, p.code); }); } return null; }; JSONDocument.prototype.getMatchingSchemas = function (schema, focusOffset, exclude) { if (focusOffset === void 0) { focusOffset = -1; } if (exclude === void 0) { exclude = null; } 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(node, schema, validationResult, matchingSchemas) { if (!node || !matchingSchemas.include(node)) { return; } 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 }, severity: DiagnosticSeverity.Warning, 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 }, severity: DiagnosticSeverity.Warning, message: schema.errorMessage || localize('typeMismatchWarning', 'Incorrect type. Expected "{0}".', schema.type) }); } } if (Array.isArray(schema.allOf)) { schema.allOf.forEach(function (subSchemaRef) { 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 }, severity: DiagnosticSeverity.Warning, message: localize('notSchemaWarning', "Matches a schema that is not allowed.") }); } subMatchingSchemas.schemas.forEach(function (ms) { 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 = null; alternatives.forEach(function (subSchemaRef) { 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 }, severity: DiagnosticSeverity.Warning, message: localize('oneOfWarning', "Matches multiple schemas when only one must validate.") }); } if (bestMatch !== null) { 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 subSchema = asSchema(schema); var subValidationResult = new ValidationResult(); var subMatchingSchemas = matchingSchemas.newSub(); validate(node, subSchema, 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); if (!subValidationResult.hasProblems()) { if (thenSchema) { testBranch(thenSchema); } } else if (elseSchema) { testBranch(elseSchema); } }; if (schema.if) { testCondition(schema.if, schema.then, schema.else); } if (Array.isArray(schema.enum)) { var val = getNodeValue(node); var enumValueMatch = false; for (var _i = 0, _a = schema.enum; _i < _a.length; _i++) { var e = _a[_i]; if (objects.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 }, severity: DiagnosticSeverity.Warning, 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 (schema.const) { var val = getNodeValue(node); if (!objects.equals(val, schema.const)) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, severity: DiagnosticSeverity.Warning, 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 }); } } function _validateNumberNode(node, schema, validationResult, matchingSchemas) { var val = node.value; if (typeof schema.multipleOf === 'number') { if (val % schema.multipleOf !== 0) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, severity: DiagnosticSeverity.Warning, message: localize('multipleOfWarning', 'Value is not divisible by {0}.', schema.multipleOf) }); } } function getExclusiveLimit(limit, exclusive) { if (typeof exclusive === 'number') { return exclusive; } if (typeof exclusive === 'boolean' && exclusive) { return limit; } return void 0; } function getLimit(limit, exclusive) { if (typeof exclusive !== 'boolean' || !exclusive) { return limit; } return void 0; } var exclusiveMinimum = getExclusiveLimit(schema.minimum, schema.exclusiveMinimum); if (typeof exclusiveMinimum === 'number' && val <= exclusiveMinimum) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, severity: DiagnosticSeverity.Warning, message: localize('exclusiveMinimumWarning', 'Value is below the exclusive minimum of {0}.', exclusiveMinimum) }); } var exclusiveMaximum = getExclusiveLimit(schema.maximum, schema.exclusiveMaximum); if (typeof exclusiveMaximum === 'number' && val >= exclusiveMaximum) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, severity: DiagnosticSeverity.Warning, message: localize('exclusiveMaximumWarning', 'Value is above the exclusive maximum of {0}.', exclusiveMaximum) }); } var minimum = getLimit(schema.minimum, schema.exclusiveMinimum); if (typeof minimum === 'number' && val < minimum) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, severity: DiagnosticSeverity.Warning, message: localize('minimumWarning', 'Value is below the minimum of {0}.', minimum) }); } var maximum = getLimit(schema.maximum, schema.exclusiveMaximum); if (typeof maximum === 'number' && val > maximum) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, severity: DiagnosticSeverity.Warning, message: localize('maximumWarning', 'Value is above the maximum of {0}.', maximum) }); } } function _validateStringNode(node, schema, validationResult, matchingSchemas) { if (schema.minLength && node.value.length < schema.minLength) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, severity: DiagnosticSeverity.Warning, message: localize('minLengthWarning', 'String is shorter than the minimum length of {0}.', schema.minLength) }); } if (schema.maxLength && node.value.length > schema.maxLength) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, severity: DiagnosticSeverity.Warning, message: localize('maxLengthWarning', 'String is longer than the maximum length of {0}.', schema.maxLength) }); } if (schema.pattern) { var regex = new RegExp(schema.pattern); if (!regex.test(node.value)) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, severity: DiagnosticSeverity.Warning, 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 { try { var uri = Uri.parse(node.value); if (!uri.scheme && schema.format === 'uri') { errorMessage = localize('uriSchemeMissing', 'URI with a scheme is expected.'); } } catch (e) { errorMessage = e.message; } } if (errorMessage) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, severity: DiagnosticSeverity.Warning, message: schema.patternErrorMessage || schema.errorMessage || localize('uriFormatWarning', 'String is not a URI: {0}', errorMessage) }); } } break; case 'email': { if (!node.value.match(emailPattern)) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, severity: DiagnosticSeverity.Warning, message: schema.patternErrorMessage || schema.errorMessage || localize('emailFormatWarning', 'String is not an e-mail address.') }); } } break; case 'color-hex': { if (!node.value.match(colorHexPattern)) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, severity: DiagnosticSeverity.Warning, message: schema.patternErrorMessage || schema.errorMessage || localize('colorHexFormatWarning', 'Invalid color format. Use #RGB, #RGBA, #RRGGBB or #RRGGBBAA.') }); } } break; default: } } } function _validateArrayNode(node, schema, validationResult, matchingSchemas) { if (Array.isArray(schema.items)) { var subSchemas_1 = schema.items; subSchemas_1.forEach(function (subSchemaRef, 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_1.length) { validationResult.propertiesValueMatches++; } }); if (node.items.length > subSchemas_1.length) { if (typeof schema.additionalItems === 'object') { for (var i = subSchemas_1.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 }, severity: DiagnosticSeverity.Warning, message: localize('additionalItemsWarning', 'Array has too many items according to schema. Expected {0} or fewer.', subSchemas_1.length) }); } } } else { var itemSchema_1 = asSchema(schema.items); if (itemSchema_1) { node.items.forEach(function (item) { var itemValidationResult = new ValidationResult(); validate(item, itemSchema_1, 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 }, severity: DiagnosticSeverity.Warning, message: schema.errorMessage || localize('requiredItemMissingWarning', 'Array does not contain required item.') }); } } if (schema.minItems && node.items.length < schema.minItems) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, severity: DiagnosticSeverity.Warning, message: localize('minItemsWarning', 'Array has too few items. Expected {0} or more.', schema.minItems) }); } if (schema.maxItems && node.items.length > schema.maxItems) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, severity: DiagnosticSeverity.Warning, 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 }, severity: DiagnosticSeverity.Warning, message: localize('uniqueItemsWarning', 'Array has duplicate items.') }); } } } function _validateObjectNode(node, schema, validationResult, matchingSchemas) { var seenKeys = Object.create(null); var unprocessedProperties = []; node.properties.forEach(function (node) { var key = node.keyNode.value; seenKeys[key] = node.valueNode; unprocessedProperties.push(key); }); if (Array.isArray(schema.required)) { schema.required.forEach(function (propertyName) { 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, severity: DiagnosticSeverity.Warning, 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) { Object.keys(schema.properties).forEach(function (propertyName) { propertyProcessed(propertyName); var propertySchema = schema.properties[propertyName]; var child = seenKeys[propertyName]; if (child) { if (typeof propertySchema === 'boolean') { if (!propertySchema) { var propertyNode = child.parent; validationResult.problems.push({ location: { offset: propertyNode.keyNode.offset, length: propertyNode.keyNode.length }, severity: DiagnosticSeverity.Warning, 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) { Object.keys(schema.patternProperties).forEach(function (propertyPattern) { var regex = new RegExp(propertyPattern); unprocessedProperties.slice(0).forEach(function (propertyName) { if (regex.test(propertyName)) { propertyProcessed(propertyName); var child = seenKeys[propertyName]; if (child) { var propertySchema = schema.patternProperties[propertyPattern]; if (typeof propertySchema === 'boolean') { if (!propertySchema) { var propertyNode = child.parent; validationResult.problems.push({ location: { offset: propertyNode.keyNode.offset, length: propertyNode.keyNode.length }, severity: DiagnosticSeverity.Warning, 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') { unprocessedProperties.forEach(function (propertyName) { 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) { unprocessedProperties.forEach(function (propertyName) { var child = seenKeys[propertyName]; if (child) { var propertyNode = child.parent; validationResult.problems.push({ location: { offset: propertyNode.keyNode.offset, length: propertyNode.keyNode.length }, severity: DiagnosticSeverity.Warning, message: schema.errorMessage || localize('DisallowedExtraPropWarning', 'Property {0} is not allowed.', propertyName) }); } }); } } if (schema.maxProperties) { if (node.properties.length > schema.maxProperties) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, severity: DiagnosticSeverity.Warning, message: localize('MaxPropWarning', 'Object has more properties than limit of {0}.', schema.maxProperties) }); } } if (schema.minProperties) { if (node.properties.length < schema.minProperties) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, severity: DiagnosticSeverity.Warning, message: localize('MinPropWarning', 'Object has fewer properties than the required number of {0}', schema.minProperties) }); } } if (schema.dependencies) { Object.keys(schema.dependencies).forEach(function (key) { var prop = seenKeys[key]; if (prop) { var propertyDep = schema.dependencies[key]; if (Array.isArray(propertyDep)) { propertyDep.forEach(function (requiredProp) { if (!seenKeys[requiredProp]) { validationResult.problems.push({ location: { offset: node.offset, length: node.length }, severity: DiagnosticSeverity.Warning, 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) { node.properties.forEach(function (f) { 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 ? [] : void 0; 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 = null; } 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 null; } 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, count++); if (!item) { _error(localize('PropertyExpected', 'Value expected'), ErrorCode.ValueExpected, null, [], [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); } function _parseProperty(parent, keysSeen) { var node = new PropertyASTNodeImpl(parent, scanner.getTokenOffset()); 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