monaco-editor
Version:
A browser based code editor
1,067 lines (1,066 loc) • 56.1 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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