mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-27 12:04:04 +01:00
[json] language server & client as extension
This commit is contained in:
257
extensions/json/server/src/configuration.ts
Normal file
257
extensions/json/server/src/configuration.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import nls = require('./utils/nls');
|
||||
import {IJSONSchema} from './json-toolbox/jsonSchema';
|
||||
|
||||
import {ISchemaContributions} from './jsonSchemaService';
|
||||
|
||||
export var schemaContributions: ISchemaContributions = {
|
||||
schemaAssociations: {
|
||||
|
||||
},
|
||||
schemas: {
|
||||
// bundle the schema-schema to include (localized) descriptions
|
||||
'http://json-schema.org/draft-04/schema#': {
|
||||
'title': nls.localize('schema.json', 'Describes a JSON file using a schema. See json-schema.org for more info.'),
|
||||
'$schema': 'http://json-schema.org/draft-04/schema#',
|
||||
'definitions': {
|
||||
'schemaArray': {
|
||||
'type': 'array',
|
||||
'minItems': 1,
|
||||
'items': { '$ref': '#' }
|
||||
},
|
||||
'positiveInteger': {
|
||||
'type': 'integer',
|
||||
'minimum': 0
|
||||
},
|
||||
'positiveIntegerDefault0': {
|
||||
'allOf': [{ '$ref': '#/definitions/positiveInteger' }, { 'default': 0 }]
|
||||
},
|
||||
'simpleTypes': {
|
||||
'type': 'string',
|
||||
'enum': ['array', 'boolean', 'integer', 'null', 'number', 'object', 'string']
|
||||
},
|
||||
'stringArray': {
|
||||
'type': 'array',
|
||||
'items': { 'type': 'string' },
|
||||
'minItems': 1,
|
||||
'uniqueItems': true
|
||||
}
|
||||
},
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'id': {
|
||||
'type': 'string',
|
||||
'format': 'uri',
|
||||
'description': nls.localize('schema.json.id', 'A unique identifier for the schema.')
|
||||
},
|
||||
'$schema': {
|
||||
'type': 'string',
|
||||
'format': 'uri',
|
||||
'description': nls.localize('schema.json.$schema', 'The schema to verify this document against ')
|
||||
},
|
||||
'title': {
|
||||
'type': 'string',
|
||||
'description': nls.localize('schema.json.title', 'A descriptive title of the element')
|
||||
},
|
||||
'description': {
|
||||
'type': 'string',
|
||||
'description': nls.localize('schema.json.description', 'A long description of the element. Used in hover menus and suggestions.')
|
||||
},
|
||||
'default': {
|
||||
'description': nls.localize('schema.json.default', 'A default value. Used by suggestions.')
|
||||
},
|
||||
'multipleOf': {
|
||||
'type': 'number',
|
||||
'minimum': 0,
|
||||
'exclusiveMinimum': true,
|
||||
'description': nls.localize('schema.json.multipleOf', 'A number that should cleanly divide the current value (i.e. have no remainder)')
|
||||
},
|
||||
'maximum': {
|
||||
'type': 'number',
|
||||
'description': nls.localize('schema.json.maximum', 'The maximum numerical value, inclusive by default.')
|
||||
},
|
||||
'exclusiveMaximum': {
|
||||
'type': 'boolean',
|
||||
'default': false,
|
||||
'description': nls.localize('schema.json.exclusiveMaximum', 'Makes the maximum property exclusive.')
|
||||
},
|
||||
'minimum': {
|
||||
'type': 'number',
|
||||
'description': nls.localize('schema.json.minimum', 'The minimum numerical value, inclusive by default.')
|
||||
},
|
||||
'exclusiveMinimum': {
|
||||
'type': 'boolean',
|
||||
'default': false,
|
||||
'description': nls.localize('schema.json.exclusiveMininum', 'Makes the minimum property exclusive.')
|
||||
},
|
||||
'maxLength': {
|
||||
'allOf': [
|
||||
{ '$ref': '#/definitions/positiveInteger' }
|
||||
],
|
||||
'description': nls.localize('schema.json.maxLength', 'The maximum length of a string.')
|
||||
},
|
||||
'minLength': {
|
||||
'allOf': [
|
||||
{ '$ref': '#/definitions/positiveIntegerDefault0' }
|
||||
],
|
||||
'description': nls.localize('schema.json.minLength', 'The minimum length of a string.')
|
||||
},
|
||||
'pattern': {
|
||||
'type': 'string',
|
||||
'format': 'regex',
|
||||
'description': nls.localize('schema.json.pattern', 'A regular expression to match the string against. It is not implicitly anchored.')
|
||||
},
|
||||
'additionalItems': {
|
||||
'anyOf': [
|
||||
{ 'type': 'boolean' },
|
||||
{ '$ref': '#' }
|
||||
],
|
||||
'default': {},
|
||||
'description': nls.localize('schema.json.additionalItems', 'For arrays, only when items is set as an array. If it is a schema, then this schema validates items after the ones specified by the items array. If it is false, then additional items will cause validation to fail.')
|
||||
},
|
||||
'items': {
|
||||
'anyOf': [
|
||||
{ '$ref': '#' },
|
||||
{ '$ref': '#/definitions/schemaArray' }
|
||||
],
|
||||
'default': {},
|
||||
'description': nls.localize('schema.json.items', 'For arrays. Can either be a schema to validate every element against or an array of schemas to validate each item against in order (the first schema will validate the first element, the second schema will validate the second element, and so on.')
|
||||
},
|
||||
'maxItems': {
|
||||
'allOf': [
|
||||
{ '$ref': '#/definitions/positiveInteger' }
|
||||
],
|
||||
'description': nls.localize('schema.json.maxItems', 'The maximum number of items that can be inside an array. Inclusive.')
|
||||
},
|
||||
'minItems': {
|
||||
'allOf': [
|
||||
{ '$ref': '#/definitions/positiveIntegerDefault0' }
|
||||
],
|
||||
'description': nls.localize('schema.json.minItems', 'The minimum number of items that can be inside an array. Inclusive.')
|
||||
},
|
||||
'uniqueItems': {
|
||||
'type': 'boolean',
|
||||
'default': false,
|
||||
'description': nls.localize('schema.json.uniqueItems', 'If all of the items in the array must be unique. Defaults to false.')
|
||||
},
|
||||
'maxProperties': {
|
||||
'allOf': [
|
||||
{ '$ref': '#/definitions/positiveInteger' }
|
||||
],
|
||||
'description': nls.localize('schema.json.maxProperties', 'The maximum number of properties an object can have. Inclusive.')
|
||||
},
|
||||
'minProperties': {
|
||||
'allOf': [
|
||||
{ '$ref': '#/definitions/positiveIntegerDefault0' },
|
||||
],
|
||||
'description': nls.localize('schema.json.minProperties', 'The minimum number of properties an object can have. Inclusive.')
|
||||
},
|
||||
'required': {
|
||||
'allOf': [
|
||||
{ '$ref': '#/definitions/stringArray' }
|
||||
],
|
||||
'description': nls.localize('schema.json.required', 'An array of strings that lists the names of all properties required on this object.')
|
||||
},
|
||||
'additionalProperties': {
|
||||
'anyOf': [
|
||||
{ 'type': 'boolean' },
|
||||
{ '$ref': '#' }
|
||||
],
|
||||
'default': {},
|
||||
'description': nls.localize('schema.json.additionalProperties', 'Either a schema or a boolean. If a schema, then used to validate all properties not matched by \'properties\' or \'patternProperties\'. If false, then any properties not matched by either will cause this schema to fail.')
|
||||
},
|
||||
'definitions': {
|
||||
'type': 'object',
|
||||
'additionalProperties': { '$ref': '#' },
|
||||
'default': {},
|
||||
'description': nls.localize('schema.json.definitions', 'Not used for validation. Place subschemas here that you wish to reference inline with $ref')
|
||||
},
|
||||
'properties': {
|
||||
'type': 'object',
|
||||
'additionalProperties': { '$ref': '#' },
|
||||
'default': {},
|
||||
'description': nls.localize('schema.json.properties', 'A map of property names to schemas for each property.')
|
||||
},
|
||||
'patternProperties': {
|
||||
'type': 'object',
|
||||
'additionalProperties': { '$ref': '#' },
|
||||
'default': {},
|
||||
'description': nls.localize('schema.json.patternProperties', 'A map of regular expressions on property names to schemas for matching properties.')
|
||||
},
|
||||
'dependencies': {
|
||||
'type': 'object',
|
||||
'additionalProperties': {
|
||||
'anyOf': [
|
||||
{ '$ref': '#' },
|
||||
{ '$ref': '#/definitions/stringArray' }
|
||||
]
|
||||
},
|
||||
'description': nls.localize('schema.json.dependencies', 'A map of property names to either an array of property names or a schema. An array of property names means the property named in the key depends on the properties in the array being present in the object in order to be valid. If the value is a schema, then the schema is only applied to the object if the property in the key exists on the object.')
|
||||
},
|
||||
'enum': {
|
||||
'type': 'array',
|
||||
'minItems': 1,
|
||||
'uniqueItems': true,
|
||||
'description': nls.localize('schema.json.enum', 'The set of literal values that are valid')
|
||||
},
|
||||
'type': {
|
||||
'anyOf': [
|
||||
{ '$ref': '#/definitions/simpleTypes' },
|
||||
{
|
||||
'type': 'array',
|
||||
'items': { '$ref': '#/definitions/simpleTypes' },
|
||||
'minItems': 1,
|
||||
'uniqueItems': true
|
||||
}
|
||||
],
|
||||
'description': nls.localize('schema.json.type', 'Either a string of one of the basic schema types (number, integer, null, array, object, boolean, string) or an array of strings specifying a subset of those types.')
|
||||
},
|
||||
'format': {
|
||||
'anyOf': [
|
||||
{
|
||||
'type': 'string',
|
||||
'description': nls.localize('schema.json.format', 'Describes the format expected for the value.'),
|
||||
'enum': ['date-time', 'uri', 'email', 'hostname', 'ipv4', 'ipv6', 'regex']
|
||||
}, {
|
||||
'type': 'string'
|
||||
}
|
||||
]
|
||||
},
|
||||
'allOf': {
|
||||
'allOf': [
|
||||
{ '$ref': '#/definitions/schemaArray' }
|
||||
],
|
||||
'description': nls.localize('schema.json.allOf', 'An array of schemas, all of which must match.')
|
||||
},
|
||||
'anyOf': {
|
||||
'allOf': [
|
||||
{ '$ref': '#/definitions/schemaArray' }
|
||||
],
|
||||
'description': nls.localize('schema.json.anyOf', 'An array of schemas, where at least one must match.')
|
||||
},
|
||||
'oneOf': {
|
||||
'allOf': [
|
||||
{ '$ref': '#/definitions/schemaArray' }
|
||||
],
|
||||
'description': nls.localize('schema.json.oneOf', 'An array of schemas, exactly one of which must match.')
|
||||
},
|
||||
'not': {
|
||||
'allOf': [
|
||||
{ '$ref': '#' }
|
||||
],
|
||||
'description': nls.localize('schema.json.not', 'A schema which must not match.')
|
||||
}
|
||||
},
|
||||
'dependencies': {
|
||||
'exclusiveMaximum': ['maximum'],
|
||||
'exclusiveMinimum': ['minimum']
|
||||
},
|
||||
'default': {}
|
||||
}
|
||||
}
|
||||
}
|
||||
790
extensions/json/server/src/json-toolbox/json.ts
Normal file
790
extensions/json/server/src/json-toolbox/json.ts
Normal file
@@ -0,0 +1,790 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export enum ScanError {
|
||||
None,
|
||||
UnexpectedEndOfComment,
|
||||
UnexpectedEndOfString,
|
||||
UnexpectedEndOfNumber,
|
||||
InvalidUnicode,
|
||||
InvalidEscapeCharacter
|
||||
}
|
||||
|
||||
export enum SyntaxKind {
|
||||
Unknown = 0,
|
||||
OpenBraceToken,
|
||||
CloseBraceToken,
|
||||
OpenBracketToken,
|
||||
CloseBracketToken,
|
||||
CommaToken,
|
||||
ColonToken,
|
||||
NullKeyword,
|
||||
TrueKeyword,
|
||||
FalseKeyword,
|
||||
StringLiteral,
|
||||
NumericLiteral,
|
||||
LineCommentTrivia,
|
||||
BlockCommentTrivia,
|
||||
LineBreakTrivia,
|
||||
Trivia,
|
||||
EOF
|
||||
}
|
||||
|
||||
export interface JSONScanner {
|
||||
scan(): SyntaxKind;
|
||||
getPosition(): number;
|
||||
getToken(): SyntaxKind;
|
||||
getTokenValue(): string;
|
||||
getTokenOffset(): number;
|
||||
getTokenLength(): number;
|
||||
getTokenError(): ScanError;
|
||||
}
|
||||
|
||||
export function createScanner(text:string, ignoreTrivia:boolean = false):JSONScanner {
|
||||
|
||||
var pos = 0,
|
||||
len = text.length,
|
||||
value:string = '',
|
||||
tokenOffset = 0,
|
||||
token:SyntaxKind = SyntaxKind.Unknown,
|
||||
scanError:ScanError = ScanError.None;
|
||||
|
||||
function scanHexDigits(count: number, exact?: boolean): number {
|
||||
var digits = 0;
|
||||
var value = 0;
|
||||
while (digits < count || !exact) {
|
||||
var ch = text.charCodeAt(pos);
|
||||
if (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) {
|
||||
value = value * 16 + ch - CharacterCodes._0;
|
||||
}
|
||||
else if (ch >= CharacterCodes.A && ch <= CharacterCodes.F) {
|
||||
value = value * 16 + ch - CharacterCodes.A + 10;
|
||||
}
|
||||
else if (ch >= CharacterCodes.a && ch <= CharacterCodes.f) {
|
||||
value = value * 16 + ch - CharacterCodes.a + 10;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
digits++;
|
||||
}
|
||||
if (digits < count) {
|
||||
value = -1;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function scanNumber(): string {
|
||||
var start = pos;
|
||||
if (text.charCodeAt(pos) === CharacterCodes._0) {
|
||||
pos++;
|
||||
} else {
|
||||
pos++;
|
||||
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
if (pos < text.length && text.charCodeAt(pos) === CharacterCodes.dot) {
|
||||
pos++;
|
||||
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
||||
pos++;
|
||||
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
||||
pos++;
|
||||
}
|
||||
} else {
|
||||
scanError = ScanError.UnexpectedEndOfNumber;
|
||||
return text.substring(start, end);
|
||||
}
|
||||
}
|
||||
var end = pos;
|
||||
if (pos < text.length && (text.charCodeAt(pos) === CharacterCodes.E || text.charCodeAt(pos) === CharacterCodes.e)) {
|
||||
pos++;
|
||||
if (pos < text.length && text.charCodeAt(pos) === CharacterCodes.plus || text.charCodeAt(pos) === CharacterCodes.minus) {
|
||||
pos++;
|
||||
}
|
||||
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
||||
pos++;
|
||||
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
|
||||
pos++;
|
||||
}
|
||||
end = pos;
|
||||
} else {
|
||||
scanError = ScanError.UnexpectedEndOfNumber;
|
||||
}
|
||||
}
|
||||
return text.substring(start, end);
|
||||
}
|
||||
|
||||
function scanString(): string {
|
||||
|
||||
var result = '',
|
||||
start = pos;
|
||||
|
||||
while (true) {
|
||||
if (pos >= len) {
|
||||
result += text.substring(start, pos);
|
||||
scanError = ScanError.UnexpectedEndOfString;
|
||||
break;
|
||||
}
|
||||
var ch = text.charCodeAt(pos);
|
||||
if (ch === CharacterCodes.doubleQuote) {
|
||||
result += text.substring(start, pos);
|
||||
pos++;
|
||||
break;
|
||||
}
|
||||
if (ch === CharacterCodes.backslash) {
|
||||
result += text.substring(start, pos);
|
||||
pos++;
|
||||
if (pos >= len) {
|
||||
scanError = ScanError.UnexpectedEndOfString;
|
||||
break;
|
||||
}
|
||||
ch = text.charCodeAt(pos++);
|
||||
switch (ch) {
|
||||
case CharacterCodes.doubleQuote:
|
||||
result += '\"';
|
||||
break;
|
||||
case CharacterCodes.backslash:
|
||||
result += '\\';
|
||||
break;
|
||||
case CharacterCodes.slash:
|
||||
result += '/';
|
||||
break;
|
||||
case CharacterCodes.b:
|
||||
result += '\b';
|
||||
break;
|
||||
case CharacterCodes.f:
|
||||
result += '\f';
|
||||
break;
|
||||
case CharacterCodes.n:
|
||||
result += '\n';
|
||||
break;
|
||||
case CharacterCodes.r:
|
||||
result += '\r';
|
||||
break;
|
||||
case CharacterCodes.t:
|
||||
result += '\t';
|
||||
break;
|
||||
case CharacterCodes.u:
|
||||
var ch = scanHexDigits(4, true);
|
||||
if (ch >= 0) {
|
||||
result += String.fromCharCode(ch);
|
||||
} else {
|
||||
scanError = ScanError.InvalidUnicode;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
scanError = ScanError.InvalidEscapeCharacter;
|
||||
}
|
||||
start = pos;
|
||||
continue;
|
||||
}
|
||||
if (isLineBreak(ch)) {
|
||||
result += text.substring(start, pos);
|
||||
scanError = ScanError.UnexpectedEndOfString;
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function scanNext():SyntaxKind {
|
||||
|
||||
value = '';
|
||||
scanError = ScanError.None,
|
||||
|
||||
tokenOffset = pos;
|
||||
|
||||
if(pos >= len) {
|
||||
// at the end
|
||||
tokenOffset = len;
|
||||
return token = SyntaxKind.EOF;
|
||||
}
|
||||
|
||||
var code = text.charCodeAt(pos);
|
||||
// trivia: whitespace
|
||||
if (isWhiteSpace(code)) {
|
||||
do {
|
||||
pos++;
|
||||
value += String.fromCharCode(code);
|
||||
code = text.charCodeAt(pos);
|
||||
} while (isWhiteSpace(code));
|
||||
|
||||
return token = SyntaxKind.Trivia;
|
||||
}
|
||||
|
||||
// trivia: newlines
|
||||
if (isLineBreak(code)) {
|
||||
pos++;
|
||||
value += String.fromCharCode(code);
|
||||
if (code === CharacterCodes.carriageReturn && text.charCodeAt(pos) === CharacterCodes.lineFeed) {
|
||||
pos++;
|
||||
value += '\n';
|
||||
}
|
||||
return token = SyntaxKind.LineBreakTrivia;
|
||||
}
|
||||
|
||||
switch(code) {
|
||||
// tokens: []{}:,
|
||||
case CharacterCodes.openBrace:
|
||||
pos++;
|
||||
return token = SyntaxKind.OpenBraceToken;
|
||||
case CharacterCodes.closeBrace:
|
||||
pos++;
|
||||
return token = SyntaxKind.CloseBraceToken;
|
||||
case CharacterCodes.openBracket:
|
||||
pos++;
|
||||
return token = SyntaxKind.OpenBracketToken;
|
||||
case CharacterCodes.closeBracket:
|
||||
pos++;
|
||||
return token = SyntaxKind.CloseBracketToken;
|
||||
case CharacterCodes.colon:
|
||||
pos++;
|
||||
return token = SyntaxKind.ColonToken;
|
||||
case CharacterCodes.comma:
|
||||
pos++;
|
||||
return token = SyntaxKind.CommaToken;
|
||||
|
||||
// strings
|
||||
case CharacterCodes.doubleQuote:
|
||||
pos++;
|
||||
value = scanString();
|
||||
return token = SyntaxKind.StringLiteral;
|
||||
|
||||
// comments
|
||||
case CharacterCodes.slash:
|
||||
var start = pos - 1;
|
||||
// Single-line comment
|
||||
if (text.charCodeAt(pos + 1) === CharacterCodes.slash) {
|
||||
pos += 2;
|
||||
|
||||
while (pos < len) {
|
||||
if (isLineBreak(text.charCodeAt(pos))) {
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
|
||||
}
|
||||
value = text.substring(start, pos);
|
||||
return token = SyntaxKind.LineCommentTrivia;
|
||||
}
|
||||
|
||||
// Multi-line comment
|
||||
if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) {
|
||||
pos += 2;
|
||||
|
||||
var safeLength = len - 1; // For lookahead.
|
||||
var commentClosed = false;
|
||||
while (pos < safeLength) {
|
||||
var ch = text.charCodeAt(pos);
|
||||
|
||||
if (ch === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) {
|
||||
pos += 2;
|
||||
commentClosed = true;
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (!commentClosed) {
|
||||
pos++;
|
||||
scanError = ScanError.UnexpectedEndOfComment;
|
||||
}
|
||||
|
||||
value = text.substring(start, pos);
|
||||
return token = SyntaxKind.BlockCommentTrivia;
|
||||
}
|
||||
// just a single slash
|
||||
value += String.fromCharCode(code);
|
||||
pos++;
|
||||
return token = SyntaxKind.Unknown;
|
||||
|
||||
// numbers
|
||||
case CharacterCodes.minus:
|
||||
value += String.fromCharCode(code);
|
||||
pos++;
|
||||
if (pos === len || !isDigit(text.charCodeAt(pos))) {
|
||||
return token = SyntaxKind.Unknown;
|
||||
}
|
||||
// found a minus, followed by a number so
|
||||
// we fall through to proceed with scanning
|
||||
// numbers
|
||||
case CharacterCodes._0:
|
||||
case CharacterCodes._1:
|
||||
case CharacterCodes._2:
|
||||
case CharacterCodes._3:
|
||||
case CharacterCodes._4:
|
||||
case CharacterCodes._5:
|
||||
case CharacterCodes._6:
|
||||
case CharacterCodes._7:
|
||||
case CharacterCodes._8:
|
||||
case CharacterCodes._9:
|
||||
value += scanNumber();
|
||||
return token = SyntaxKind.NumericLiteral;
|
||||
// literals and unknown symbols
|
||||
default:
|
||||
// is a literal? Read the full word.
|
||||
while (pos < len && isUnknownContentCharacter(code)) {
|
||||
pos++;
|
||||
code = text.charCodeAt(pos);
|
||||
}
|
||||
if (tokenOffset !== pos) {
|
||||
value = text.substring(tokenOffset, pos);
|
||||
// keywords: true, false, null
|
||||
switch (value) {
|
||||
case 'true': return token = SyntaxKind.TrueKeyword;
|
||||
case 'false': return token = SyntaxKind.FalseKeyword;
|
||||
case 'null': return token = SyntaxKind.NullKeyword;
|
||||
}
|
||||
return token = SyntaxKind.Unknown;
|
||||
}
|
||||
// some
|
||||
value += String.fromCharCode(code);
|
||||
pos++;
|
||||
return token = SyntaxKind.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
function isUnknownContentCharacter(code: CharacterCodes) {
|
||||
if (isWhiteSpace(code) || isLineBreak(code)) {
|
||||
return false;
|
||||
}
|
||||
switch (code) {
|
||||
case CharacterCodes.closeBrace:
|
||||
case CharacterCodes.closeBracket:
|
||||
case CharacterCodes.openBrace:
|
||||
case CharacterCodes.openBracket:
|
||||
case CharacterCodes.doubleQuote:
|
||||
case CharacterCodes.colon:
|
||||
case CharacterCodes.comma:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function scanNextNonTrivia():SyntaxKind {
|
||||
var result : SyntaxKind;
|
||||
do {
|
||||
result = scanNext();
|
||||
} while (result >= SyntaxKind.LineCommentTrivia && result <= SyntaxKind.Trivia);
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
getPosition: () => pos,
|
||||
scan: ignoreTrivia ? scanNextNonTrivia : scanNext,
|
||||
getToken: () => token,
|
||||
getTokenValue: () => value,
|
||||
getTokenOffset: () => tokenOffset,
|
||||
getTokenLength: () => pos - tokenOffset,
|
||||
getTokenError: () => scanError
|
||||
};
|
||||
}
|
||||
|
||||
function isWhiteSpace(ch: number): boolean {
|
||||
return ch === CharacterCodes.space || ch === CharacterCodes.tab || ch === CharacterCodes.verticalTab || ch === CharacterCodes.formFeed ||
|
||||
ch === CharacterCodes.nonBreakingSpace || ch === CharacterCodes.ogham || ch >= CharacterCodes.enQuad && ch <= CharacterCodes.zeroWidthSpace ||
|
||||
ch === CharacterCodes.narrowNoBreakSpace || ch === CharacterCodes.mathematicalSpace || ch === CharacterCodes.ideographicSpace || ch === CharacterCodes.byteOrderMark;
|
||||
}
|
||||
|
||||
function isLineBreak(ch: number): boolean {
|
||||
return ch === CharacterCodes.lineFeed || ch === CharacterCodes.carriageReturn || ch === CharacterCodes.lineSeparator || ch === CharacterCodes.paragraphSeparator;
|
||||
}
|
||||
|
||||
function isDigit(ch: number): boolean {
|
||||
return ch >= CharacterCodes._0 && ch <= CharacterCodes._9;
|
||||
}
|
||||
|
||||
export function isLetter(ch: number): boolean {
|
||||
return ch >= CharacterCodes.a && ch <= CharacterCodes.z || ch >= CharacterCodes.A && ch <= CharacterCodes.Z;
|
||||
}
|
||||
|
||||
enum CharacterCodes {
|
||||
nullCharacter = 0,
|
||||
maxAsciiCharacter = 0x7F,
|
||||
|
||||
lineFeed = 0x0A, // \n
|
||||
carriageReturn = 0x0D, // \r
|
||||
lineSeparator = 0x2028,
|
||||
paragraphSeparator = 0x2029,
|
||||
|
||||
// REVIEW: do we need to support this? The scanner doesn't, but our IText does. This seems
|
||||
// like an odd disparity? (Or maybe it's completely fine for them to be different).
|
||||
nextLine = 0x0085,
|
||||
|
||||
// Unicode 3.0 space characters
|
||||
space = 0x0020, // " "
|
||||
nonBreakingSpace = 0x00A0, //
|
||||
enQuad = 0x2000,
|
||||
emQuad = 0x2001,
|
||||
enSpace = 0x2002,
|
||||
emSpace = 0x2003,
|
||||
threePerEmSpace = 0x2004,
|
||||
fourPerEmSpace = 0x2005,
|
||||
sixPerEmSpace = 0x2006,
|
||||
figureSpace = 0x2007,
|
||||
punctuationSpace = 0x2008,
|
||||
thinSpace = 0x2009,
|
||||
hairSpace = 0x200A,
|
||||
zeroWidthSpace = 0x200B,
|
||||
narrowNoBreakSpace = 0x202F,
|
||||
ideographicSpace = 0x3000,
|
||||
mathematicalSpace = 0x205F,
|
||||
ogham = 0x1680,
|
||||
|
||||
_ = 0x5F,
|
||||
$ = 0x24,
|
||||
|
||||
_0 = 0x30,
|
||||
_1 = 0x31,
|
||||
_2 = 0x32,
|
||||
_3 = 0x33,
|
||||
_4 = 0x34,
|
||||
_5 = 0x35,
|
||||
_6 = 0x36,
|
||||
_7 = 0x37,
|
||||
_8 = 0x38,
|
||||
_9 = 0x39,
|
||||
|
||||
a = 0x61,
|
||||
b = 0x62,
|
||||
c = 0x63,
|
||||
d = 0x64,
|
||||
e = 0x65,
|
||||
f = 0x66,
|
||||
g = 0x67,
|
||||
h = 0x68,
|
||||
i = 0x69,
|
||||
j = 0x6A,
|
||||
k = 0x6B,
|
||||
l = 0x6C,
|
||||
m = 0x6D,
|
||||
n = 0x6E,
|
||||
o = 0x6F,
|
||||
p = 0x70,
|
||||
q = 0x71,
|
||||
r = 0x72,
|
||||
s = 0x73,
|
||||
t = 0x74,
|
||||
u = 0x75,
|
||||
v = 0x76,
|
||||
w = 0x77,
|
||||
x = 0x78,
|
||||
y = 0x79,
|
||||
z = 0x7A,
|
||||
|
||||
A = 0x41,
|
||||
B = 0x42,
|
||||
C = 0x43,
|
||||
D = 0x44,
|
||||
E = 0x45,
|
||||
F = 0x46,
|
||||
G = 0x47,
|
||||
H = 0x48,
|
||||
I = 0x49,
|
||||
J = 0x4A,
|
||||
K = 0x4B,
|
||||
L = 0x4C,
|
||||
M = 0x4D,
|
||||
N = 0x4E,
|
||||
O = 0x4F,
|
||||
P = 0x50,
|
||||
Q = 0x51,
|
||||
R = 0x52,
|
||||
S = 0x53,
|
||||
T = 0x54,
|
||||
U = 0x55,
|
||||
V = 0x56,
|
||||
W = 0x57,
|
||||
X = 0x58,
|
||||
Y = 0x59,
|
||||
Z = 0x5a,
|
||||
|
||||
ampersand = 0x26, // &
|
||||
asterisk = 0x2A, // *
|
||||
at = 0x40, // @
|
||||
backslash = 0x5C, // \
|
||||
bar = 0x7C, // |
|
||||
caret = 0x5E, // ^
|
||||
closeBrace = 0x7D, // }
|
||||
closeBracket = 0x5D, // ]
|
||||
closeParen = 0x29, // )
|
||||
colon = 0x3A, // :
|
||||
comma = 0x2C, // ,
|
||||
dot = 0x2E, // .
|
||||
doubleQuote = 0x22, // "
|
||||
equals = 0x3D, // =
|
||||
exclamation = 0x21, // !
|
||||
greaterThan = 0x3E, // >
|
||||
lessThan = 0x3C, // <
|
||||
minus = 0x2D, // -
|
||||
openBrace = 0x7B, // {
|
||||
openBracket = 0x5B, // [
|
||||
openParen = 0x28, // (
|
||||
percent = 0x25, // %
|
||||
plus = 0x2B, // +
|
||||
question = 0x3F, // ?
|
||||
semicolon = 0x3B, // ;
|
||||
singleQuote = 0x27, // '
|
||||
slash = 0x2F, // /
|
||||
tilde = 0x7E, // ~
|
||||
|
||||
backspace = 0x08, // \b
|
||||
formFeed = 0x0C, // \f
|
||||
byteOrderMark = 0xFEFF,
|
||||
tab = 0x09, // \t
|
||||
verticalTab = 0x0B, // \v
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Takes JSON with JavaScript-style comments and remove
|
||||
* them. Optionally replaces every none-newline character
|
||||
* of comments with a replaceCharacter
|
||||
*/
|
||||
export function stripComments(text:string, replaceCh?:string):string {
|
||||
|
||||
var _scanner = createScanner(text),
|
||||
parts: string[] = [],
|
||||
kind:SyntaxKind,
|
||||
offset = 0,
|
||||
pos:number;
|
||||
|
||||
do {
|
||||
pos = _scanner.getPosition();
|
||||
kind = _scanner.scan();
|
||||
switch (kind) {
|
||||
case SyntaxKind.LineCommentTrivia:
|
||||
case SyntaxKind.BlockCommentTrivia:
|
||||
case SyntaxKind.EOF:
|
||||
if(offset !== pos) {
|
||||
parts.push(text.substring(offset, pos));
|
||||
}
|
||||
if(replaceCh !== void 0) {
|
||||
parts.push(_scanner.getTokenValue().replace(/[^\r\n]/g, replaceCh));
|
||||
}
|
||||
offset = _scanner.getPosition();
|
||||
break;
|
||||
}
|
||||
} while(kind !== SyntaxKind.EOF);
|
||||
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
export enum ParseErrorCode {
|
||||
InvalidSymbol,
|
||||
InvalidNumberFormat,
|
||||
PropertyNameExpected,
|
||||
ValueExpected,
|
||||
ColonExpected,
|
||||
CommaExpected,
|
||||
CloseBraceExpected,
|
||||
CloseBracketExpected,
|
||||
EndOfFileExpected
|
||||
}
|
||||
|
||||
export function getParseErrorMessage(errorCode: ParseErrorCode) : string {
|
||||
switch (errorCode) {
|
||||
case ParseErrorCode.InvalidSymbol: return 'Invalid symbol';
|
||||
case ParseErrorCode.InvalidNumberFormat: return 'Invalid number format';
|
||||
case ParseErrorCode.PropertyNameExpected: return 'Property name expected';
|
||||
case ParseErrorCode.ValueExpected: return 'Value expected';
|
||||
case ParseErrorCode.ColonExpected: return 'Colon expected';
|
||||
case ParseErrorCode.CommaExpected: return 'Comma expected';
|
||||
case ParseErrorCode.CloseBraceExpected: return 'Closing brace expected';
|
||||
case ParseErrorCode.CloseBracketExpected: return 'Closing bracket expected';
|
||||
case ParseErrorCode.EndOfFileExpected: return 'End of file expected';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function parse(text:string, errors: { error:ParseErrorCode; }[] = []) : any {
|
||||
var noMatch = Object();
|
||||
var _scanner = createScanner(text, true);
|
||||
|
||||
function scanNext() : SyntaxKind {
|
||||
var token = _scanner.scan();
|
||||
while (token === SyntaxKind.Unknown) {
|
||||
handleError(ParseErrorCode.InvalidSymbol);
|
||||
token = _scanner.scan();
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
function handleError(error:ParseErrorCode, skipUntilAfter: SyntaxKind[] = [], skipUntil: SyntaxKind[] = []) : void {
|
||||
errors.push({ error: error });
|
||||
if (skipUntilAfter.length + skipUntil.length > 0) {
|
||||
var token = _scanner.getToken();
|
||||
while (token !== SyntaxKind.EOF) {
|
||||
if (skipUntilAfter.indexOf(token) !== -1) {
|
||||
scanNext();
|
||||
break;
|
||||
} else if (skipUntil.indexOf(token) !== -1) {
|
||||
break;
|
||||
}
|
||||
token = scanNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseString() : any {
|
||||
if (_scanner.getToken() !== SyntaxKind.StringLiteral) {
|
||||
return noMatch;
|
||||
}
|
||||
var value = _scanner.getTokenValue();
|
||||
scanNext();
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseLiteral() : any {
|
||||
var value : any;
|
||||
switch (_scanner.getToken()) {
|
||||
case SyntaxKind.NumericLiteral:
|
||||
try {
|
||||
value = JSON.parse(_scanner.getTokenValue());
|
||||
if (typeof value !== 'number') {
|
||||
handleError(ParseErrorCode.InvalidNumberFormat);
|
||||
value = 0;
|
||||
}
|
||||
} catch (e) {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.NullKeyword:
|
||||
value = null;
|
||||
break;
|
||||
case SyntaxKind.TrueKeyword:
|
||||
value = true;
|
||||
break;
|
||||
case SyntaxKind.FalseKeyword:
|
||||
value = false;
|
||||
break;
|
||||
default:
|
||||
return noMatch;
|
||||
}
|
||||
scanNext();
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseProperty(result: any) : any {
|
||||
var key = parseString();
|
||||
if (key === noMatch) {
|
||||
handleError(ParseErrorCode.PropertyNameExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken] );
|
||||
return false;
|
||||
}
|
||||
if (_scanner.getToken() === SyntaxKind.ColonToken) {
|
||||
scanNext(); // consume colon
|
||||
|
||||
var value = parseValue();
|
||||
if (value !== noMatch) {
|
||||
result[key] = value;
|
||||
} else {
|
||||
handleError(ParseErrorCode.ValueExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken] );
|
||||
}
|
||||
} else {
|
||||
handleError(ParseErrorCode.ColonExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken] );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function parseObject() : any {
|
||||
if (_scanner.getToken() !== SyntaxKind.OpenBraceToken) {
|
||||
return noMatch;
|
||||
}
|
||||
var obj = {};
|
||||
scanNext(); // consume open brace
|
||||
|
||||
var needsComma = false;
|
||||
while (_scanner.getToken() !== SyntaxKind.CloseBraceToken && _scanner.getToken() !== SyntaxKind.EOF) {
|
||||
if (_scanner.getToken() === SyntaxKind.CommaToken) {
|
||||
if (!needsComma) {
|
||||
handleError(ParseErrorCode.ValueExpected, [], [] );
|
||||
}
|
||||
scanNext(); // consume comma
|
||||
} else if (needsComma) {
|
||||
handleError(ParseErrorCode.CommaExpected, [], [] );
|
||||
}
|
||||
var propertyParsed = parseProperty(obj);
|
||||
if (!propertyParsed) {
|
||||
handleError(ParseErrorCode.ValueExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken] );
|
||||
}
|
||||
needsComma = true;
|
||||
}
|
||||
|
||||
if (_scanner.getToken() !== SyntaxKind.CloseBraceToken) {
|
||||
handleError(ParseErrorCode.CloseBraceExpected, [SyntaxKind.CloseBraceToken], []);
|
||||
} else {
|
||||
scanNext(); // consume close brace
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
function parseArray() : any {
|
||||
if (_scanner.getToken() !== SyntaxKind.OpenBracketToken) {
|
||||
return noMatch;
|
||||
}
|
||||
var arr: any[] = [];
|
||||
scanNext(); // consume open bracket
|
||||
|
||||
var needsComma = false;
|
||||
while (_scanner.getToken() !== SyntaxKind.CloseBracketToken && _scanner.getToken() !== SyntaxKind.EOF) {
|
||||
if (_scanner.getToken() === SyntaxKind.CommaToken) {
|
||||
if (!needsComma) {
|
||||
handleError(ParseErrorCode.ValueExpected, [], [] );
|
||||
}
|
||||
scanNext(); // consume comma
|
||||
} else if (needsComma) {
|
||||
handleError(ParseErrorCode.CommaExpected, [], [] );
|
||||
}
|
||||
var value = parseValue();
|
||||
if (value === noMatch) {
|
||||
handleError(ParseErrorCode.ValueExpected, [], [SyntaxKind.CloseBracketToken, SyntaxKind.CommaToken] );
|
||||
} else {
|
||||
arr.push(value);
|
||||
}
|
||||
needsComma = true;
|
||||
}
|
||||
|
||||
if (_scanner.getToken() !== SyntaxKind.CloseBracketToken) {
|
||||
handleError(ParseErrorCode.CloseBracketExpected, [SyntaxKind.CloseBracketToken], []);
|
||||
} else {
|
||||
scanNext(); // consume close bracket
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
function parseValue() : any {
|
||||
var result = parseArray();
|
||||
if (result !== noMatch) {
|
||||
return result;
|
||||
}
|
||||
result = parseObject();
|
||||
if (result !== noMatch) {
|
||||
return result;
|
||||
}
|
||||
result = parseString();
|
||||
if (result !== noMatch) {
|
||||
return result;
|
||||
}
|
||||
return parseLiteral();
|
||||
}
|
||||
|
||||
scanNext();
|
||||
var value = parseValue();
|
||||
if (value === noMatch) {
|
||||
handleError(ParseErrorCode.ValueExpected, [], []);
|
||||
return void 0;
|
||||
}
|
||||
if (_scanner.getToken() !== SyntaxKind.EOF) {
|
||||
handleError(ParseErrorCode.EndOfFileExpected, [], []);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
46
extensions/json/server/src/json-toolbox/jsonSchema.ts
Normal file
46
extensions/json/server/src/json-toolbox/jsonSchema.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export interface IJSONSchema {
|
||||
id?:string;
|
||||
$schema?: string;
|
||||
type?:any;
|
||||
title?:string;
|
||||
default?:any;
|
||||
definitions?:IJSONSchemaMap;
|
||||
description?:string;
|
||||
properties?: IJSONSchemaMap;
|
||||
patternProperties?:IJSONSchemaMap;
|
||||
additionalProperties?:any;
|
||||
minProperties?:number;
|
||||
maxProperties?:number;
|
||||
dependencies?:any;
|
||||
items?:any;
|
||||
minItems?:number;
|
||||
maxItems?:number;
|
||||
uniqueItems?:boolean;
|
||||
additionalItems?:boolean;
|
||||
pattern?:string;
|
||||
minLength?:number;
|
||||
maxLength?:number;
|
||||
minimum?:number;
|
||||
maximum?:number;
|
||||
exclusiveMinimum?:boolean;
|
||||
exclusiveMaximum?:boolean;
|
||||
multipleOf?:number;
|
||||
required?:string[];
|
||||
$ref?:string;
|
||||
anyOf?:IJSONSchema[];
|
||||
allOf?:IJSONSchema[];
|
||||
oneOf?:IJSONSchema[];
|
||||
not?:IJSONSchema;
|
||||
enum?:any[];
|
||||
format?: string;
|
||||
}
|
||||
|
||||
export interface IJSONSchemaMap {
|
||||
[name: string]:IJSONSchema;
|
||||
}
|
||||
398
extensions/json/server/src/jsonCompletion.ts
Normal file
398
extensions/json/server/src/jsonCompletion.ts
Normal file
@@ -0,0 +1,398 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
|
||||
import URI from './utils/uri';
|
||||
import Parser = require('./jsonParser');
|
||||
import SchemaService = require('./jsonSchemaService');
|
||||
import JsonSchema = require('./json-toolbox/jsonSchema');
|
||||
import nls = require('./utils/nls');
|
||||
|
||||
import {CompletionItem, CompletionItemKind, CompletionOptions, ITextDocument, TextDocumentIdentifier, TextDocumentPosition, Range, TextEdit} from 'vscode-languageserver';
|
||||
|
||||
import {LinesModel} from './utils/lines';
|
||||
|
||||
export interface ISuggestionsCollector {
|
||||
add(suggestion: CompletionItem): void;
|
||||
error(message: string): void;
|
||||
}
|
||||
|
||||
export class JSONCompletion {
|
||||
|
||||
private schemaService: SchemaService.IJSONSchemaService;
|
||||
|
||||
constructor(schemaService: SchemaService.IJSONSchemaService) {
|
||||
this.schemaService = schemaService;
|
||||
}
|
||||
|
||||
public doSuggest(document: ITextDocument, textDocumentPosition: TextDocumentPosition, lines: LinesModel, doc: Parser.JSONDocument): Promise<CompletionItem[]> {
|
||||
|
||||
var offset = lines.offsetAt(textDocumentPosition.position);
|
||||
var node = doc.getNodeFromOffsetEndInclusive(offset);
|
||||
|
||||
var overwriteRange = null;
|
||||
var result: CompletionItem[] = [];
|
||||
|
||||
if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
|
||||
overwriteRange = Range.create(lines.positionAt(node.start), lines.positionAt(node.end));
|
||||
}
|
||||
|
||||
var proposed: { [key: string]: boolean } = {};
|
||||
var collector: ISuggestionsCollector = {
|
||||
add: (suggestion: CompletionItem) => {
|
||||
if (!proposed[suggestion.label]) {
|
||||
proposed[suggestion.label] = true;
|
||||
if (overwriteRange) {
|
||||
suggestion.textEdit = TextEdit.replace(overwriteRange, suggestion.insertText);
|
||||
}
|
||||
|
||||
result.push(suggestion);
|
||||
}
|
||||
},
|
||||
error: (message: string) => {
|
||||
console.log(message);
|
||||
}
|
||||
};
|
||||
|
||||
return this.schemaService.getSchemaForResource(textDocumentPosition.uri, doc).then((schema) => {
|
||||
var addValue = true;
|
||||
var currentKey = '';
|
||||
var currentProperty: Parser.PropertyASTNode = null;
|
||||
if (node) {
|
||||
|
||||
if (node.type === 'string') {
|
||||
var stringNode = <Parser.StringASTNode>node;
|
||||
if (stringNode.isKey) {
|
||||
addValue = !(node.parent && ((<Parser.PropertyASTNode>node.parent).value));
|
||||
currentProperty = node.parent ? <Parser.PropertyASTNode>node.parent : null;
|
||||
currentKey = document.getText().substr(node.start + 1, node.end - node.start - 1);
|
||||
if (node.parent) {
|
||||
node = node.parent.parent;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// proposals for properties
|
||||
if (node && node.type === 'object') {
|
||||
// don't suggest keys when the cursor is just before the opening curly brace
|
||||
if (node.start === offset) {
|
||||
return result;
|
||||
}
|
||||
// don't suggest properties that are already present
|
||||
var properties = (<Parser.ObjectASTNode>node).properties;
|
||||
properties.forEach(p => {
|
||||
if (!currentProperty || currentProperty !== p) {
|
||||
proposed[p.key.value] = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (schema) {
|
||||
// property proposals with schema
|
||||
var isLast = properties.length === 0 || offset >= properties[properties.length - 1].start;
|
||||
this.getPropertySuggestions(schema, doc, node, currentKey, addValue, isLast, collector);
|
||||
} else if (node.parent) {
|
||||
// property proposals without schema
|
||||
this.getSchemaLessPropertySuggestions(doc, node, collector);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// proposals for values
|
||||
if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
|
||||
node = node.parent;
|
||||
}
|
||||
|
||||
if (schema) {
|
||||
// value proposals with schema
|
||||
this.getValueSuggestions(schema, doc, node, offset, collector);
|
||||
} else {
|
||||
// value proposals without schema
|
||||
this.getSchemaLessValueSuggestions(doc, node, offset, document, collector);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private getPropertySuggestions(schema: SchemaService.ResolvedSchema, doc: Parser.JSONDocument, node: Parser.ASTNode, currentWord: string, addValue: boolean, isLast: boolean, collector: ISuggestionsCollector): void {
|
||||
var matchingSchemas: Parser.IApplicableSchema[] = [];
|
||||
doc.validate(schema.schema, matchingSchemas, node.start);
|
||||
|
||||
matchingSchemas.forEach((s) => {
|
||||
if (s.node === node && !s.inverted) {
|
||||
var schemaProperties = s.schema.properties;
|
||||
if (schemaProperties) {
|
||||
Object.keys(schemaProperties).forEach((key: string) => {
|
||||
var propertySchema = schemaProperties[key];
|
||||
collector.add({ kind: CompletionItemKind.Property, label: key, insertText: this.getSnippetForProperty(key, propertySchema, addValue, isLast), documentation: propertySchema.description || '' });
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getSchemaLessPropertySuggestions(doc: Parser.JSONDocument, node: Parser.ASTNode, collector: ISuggestionsCollector): void {
|
||||
var collectSuggestionsForSimilarObject = (obj: Parser.ObjectASTNode) => {
|
||||
obj.properties.forEach((p) => {
|
||||
var key = p.key.value;
|
||||
collector.add({ kind: CompletionItemKind.Property, label: key, insertText: this.getSnippetForSimilarProperty(key, p.value), documentation: '' });
|
||||
});
|
||||
};
|
||||
if (node.parent.type === 'property') {
|
||||
// if the object is a property value, check the tree for other objects that hang under a property of the same name
|
||||
var parentKey = (<Parser.PropertyASTNode>node.parent).key.value;
|
||||
doc.visit((n) => {
|
||||
if (n.type === 'property' && (<Parser.PropertyASTNode>n).key.value === parentKey && (<Parser.PropertyASTNode>n).value && (<Parser.PropertyASTNode>n).value.type === 'object') {
|
||||
collectSuggestionsForSimilarObject(<Parser.ObjectASTNode>(<Parser.PropertyASTNode>n).value);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} else if (node.parent.type === 'array') {
|
||||
// if the object is in an array, use all other array elements as similar objects
|
||||
(<Parser.ArrayASTNode>node.parent).items.forEach((n) => {
|
||||
if (n.type === 'object' && n !== node) {
|
||||
collectSuggestionsForSimilarObject(<Parser.ObjectASTNode>n);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getSchemaLessValueSuggestions(doc: Parser.JSONDocument, node: Parser.ASTNode, offset: number, document: ITextDocument, collector: ISuggestionsCollector): void {
|
||||
var collectSuggestionsForValues = (value: Parser.ASTNode) => {
|
||||
var content = this.getMatchingSnippet(value, document);
|
||||
collector.add({ kind: this.getSuggestionKind(value.type), label: content, insertText: content, documentation: '' });
|
||||
if (value.type === 'boolean') {
|
||||
this.addBooleanSuggestion(!value.getValue(), collector);
|
||||
}
|
||||
};
|
||||
|
||||
if (!node) {
|
||||
collector.add({ kind: this.getSuggestionKind('object'), label: 'Empty object', insertText: '{\n\t{{}}\n}', documentation: '' });
|
||||
collector.add({ kind: this.getSuggestionKind('array'), label: 'Empty array', insertText: '[\n\t{{}}\n]', documentation: '' });
|
||||
} else {
|
||||
if (node.type === 'property' && offset > (<Parser.PropertyASTNode>node).colonOffset) {
|
||||
var valueNode = (<Parser.PropertyASTNode>node).value;
|
||||
if (valueNode && offset > valueNode.end) {
|
||||
return;
|
||||
}
|
||||
// suggest values at the same key
|
||||
var parentKey = (<Parser.PropertyASTNode>node).key.value;
|
||||
doc.visit((n) => {
|
||||
if (n.type === 'property' && (<Parser.PropertyASTNode>n).key.value === parentKey && (<Parser.PropertyASTNode>n).value) {
|
||||
collectSuggestionsForValues((<Parser.PropertyASTNode>n).value);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
if (node.type === 'array') {
|
||||
if (node.parent && node.parent.type === 'property') {
|
||||
// suggest items of an array at the same key
|
||||
var parentKey = (<Parser.PropertyASTNode>node.parent).key.value;
|
||||
doc.visit((n) => {
|
||||
if (n.type === 'property' && (<Parser.PropertyASTNode>n).key.value === parentKey && (<Parser.PropertyASTNode>n).value && (<Parser.PropertyASTNode>n).value.type === 'array') {
|
||||
((<Parser.ArrayASTNode>(<Parser.PropertyASTNode>n).value).items).forEach((n) => {
|
||||
collectSuggestionsForValues(<Parser.ObjectASTNode>n);
|
||||
});
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
// suggest items in the same array
|
||||
(<Parser.ArrayASTNode>node).items.forEach((n) => {
|
||||
collectSuggestionsForValues(<Parser.ObjectASTNode>n);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private getValueSuggestions(schema: SchemaService.ResolvedSchema, doc: Parser.JSONDocument, node: Parser.ASTNode, offset: number, collector: ISuggestionsCollector): void {
|
||||
|
||||
if (!node) {
|
||||
this.addDefaultSuggestion(schema.schema, collector);
|
||||
} else {
|
||||
var parentKey: string = null;
|
||||
if (node && (node.type === 'property') && offset > (<Parser.PropertyASTNode>node).colonOffset) {
|
||||
var valueNode = (<Parser.PropertyASTNode>node).value;
|
||||
if (valueNode && offset > valueNode.end) {
|
||||
return; // we are past the value node
|
||||
}
|
||||
parentKey = (<Parser.PropertyASTNode>node).key.value;
|
||||
node = node.parent;
|
||||
}
|
||||
if (node && (parentKey !== null || node.type === 'array')) {
|
||||
var matchingSchemas: Parser.IApplicableSchema[] = [];
|
||||
doc.validate(schema.schema, matchingSchemas, node.start);
|
||||
|
||||
matchingSchemas.forEach((s) => {
|
||||
if (s.node === node && !s.inverted && s.schema) {
|
||||
if (s.schema.items) {
|
||||
this.addDefaultSuggestion(s.schema.items, collector);
|
||||
this.addEnumSuggestion(s.schema.items, collector);
|
||||
}
|
||||
if (s.schema.properties) {
|
||||
var propertySchema = s.schema.properties[parentKey];
|
||||
if (propertySchema) {
|
||||
this.addDefaultSuggestion(propertySchema, collector);
|
||||
this.addEnumSuggestion(propertySchema, collector);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private addBooleanSuggestion(value: boolean, collector: ISuggestionsCollector): void {
|
||||
collector.add({ kind: this.getSuggestionKind('boolean'), label: value ? 'true' : 'false', insertText: this.getSnippetForValue(value), documentation: '' });
|
||||
}
|
||||
|
||||
private addEnumSuggestion(schema: JsonSchema.IJSONSchema, collector: ISuggestionsCollector): void {
|
||||
if (Array.isArray(schema.enum)) {
|
||||
schema.enum.forEach((enm) => collector.add({ kind: this.getSuggestionKind(schema.type), label: this.getLabelForValue(enm), insertText: this.getSnippetForValue(enm), documentation: '' }));
|
||||
} else if (schema.type === 'boolean') {
|
||||
this.addBooleanSuggestion(true, collector);
|
||||
this.addBooleanSuggestion(false, collector);
|
||||
}
|
||||
if (Array.isArray(schema.allOf)) {
|
||||
schema.allOf.forEach((s) => this.addEnumSuggestion(s, collector));
|
||||
}
|
||||
if (Array.isArray(schema.anyOf)) {
|
||||
schema.anyOf.forEach((s) => this.addEnumSuggestion(s, collector));
|
||||
}
|
||||
if (Array.isArray(schema.oneOf)) {
|
||||
schema.oneOf.forEach((s) => this.addEnumSuggestion(s, collector));
|
||||
}
|
||||
}
|
||||
|
||||
private addDefaultSuggestion(schema: JsonSchema.IJSONSchema, collector: ISuggestionsCollector): void {
|
||||
if (schema.default) {
|
||||
collector.add({
|
||||
kind: this.getSuggestionKind(schema.type),
|
||||
label: this.getLabelForValue(schema.default),
|
||||
insertText: this.getSnippetForValue(schema.default),
|
||||
detail: nls.localize('json.suggest.default', 'Default value'),
|
||||
});
|
||||
}
|
||||
if (Array.isArray(schema.allOf)) {
|
||||
schema.allOf.forEach((s) => this.addDefaultSuggestion(s, collector));
|
||||
}
|
||||
if (Array.isArray(schema.anyOf)) {
|
||||
schema.anyOf.forEach((s) => this.addDefaultSuggestion(s, collector));
|
||||
}
|
||||
if (Array.isArray(schema.oneOf)) {
|
||||
schema.oneOf.forEach((s) => this.addDefaultSuggestion(s, collector));
|
||||
}
|
||||
}
|
||||
|
||||
private getLabelForValue(value: any): string {
|
||||
var label = JSON.stringify(value);
|
||||
label = label.replace('{{', '').replace('}}', '');
|
||||
if (label.length > 57) {
|
||||
return label.substr(0, 57).trim() + '...';
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
private getSnippetForValue(value: any): string {
|
||||
var snippet = JSON.stringify(value, null, '\t');
|
||||
switch (typeof value) {
|
||||
case 'object':
|
||||
if (value === null) {
|
||||
return '{{null}}';
|
||||
}
|
||||
return snippet;
|
||||
case 'string':
|
||||
return '"{{' + snippet.substr(1, snippet.length - 2) + '}}"';
|
||||
case 'number':
|
||||
case 'boolean':
|
||||
return '{{' + snippet + '}}';
|
||||
}
|
||||
return snippet;
|
||||
}
|
||||
|
||||
private getSuggestionKind(type: any): CompletionItemKind {
|
||||
if (Array.isArray(type)) {
|
||||
var array = <any[]>type;
|
||||
type = array.length > 0 ? array[0] : null;
|
||||
}
|
||||
if (!type) {
|
||||
return CompletionItemKind.Text;
|
||||
}
|
||||
switch (type) {
|
||||
case 'string': return CompletionItemKind.Text;
|
||||
case 'object': return CompletionItemKind.Module;
|
||||
case 'property': return CompletionItemKind.Property;
|
||||
default: return CompletionItemKind.Value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private getMatchingSnippet(node: Parser.ASTNode, document: ITextDocument): string {
|
||||
switch (node.type) {
|
||||
case 'array':
|
||||
return '[]';
|
||||
case 'object':
|
||||
return '{}';
|
||||
default:
|
||||
var content = document.getText().substr(node.start, node.end - node.start);
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
private getSnippetForProperty(key: string, propertySchema: JsonSchema.IJSONSchema, addValue: boolean, isLast: boolean): string {
|
||||
|
||||
var result = '"' + key + '"';
|
||||
if (!addValue) {
|
||||
return result;
|
||||
}
|
||||
result += ': ';
|
||||
|
||||
var defaultVal = propertySchema.default;
|
||||
if (typeof defaultVal !== 'undefined') {
|
||||
result = result + this.getSnippetForValue(defaultVal);
|
||||
} else if (propertySchema.enum && propertySchema.enum.length > 0) {
|
||||
result = result + this.getSnippetForValue(propertySchema.enum[0]);
|
||||
} else {
|
||||
switch (propertySchema.type) {
|
||||
case 'boolean':
|
||||
result += '{{false}}';
|
||||
break;
|
||||
case 'string':
|
||||
result += '"{{}}"';
|
||||
break;
|
||||
case 'object':
|
||||
result += '{\n\t{{}}\n}';
|
||||
break;
|
||||
case 'array':
|
||||
result += '[\n\t{{}}\n]';
|
||||
break;
|
||||
case 'number':
|
||||
result += '{{0}}';
|
||||
break;
|
||||
case 'null':
|
||||
result += '{{null}}';
|
||||
break;
|
||||
default:
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (!isLast) {
|
||||
result += ',';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private getSnippetForSimilarProperty(key: string, templateValue: Parser.ASTNode): string {
|
||||
return '"' + key + '"';
|
||||
}
|
||||
}
|
||||
85
extensions/json/server/src/jsonDocumentSymbols.ts
Normal file
85
extensions/json/server/src/jsonDocumentSymbols.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Parser = require('./jsonParser');
|
||||
import SchemaService = require('./jsonSchemaService');
|
||||
import Strings = require('./utils/strings');
|
||||
|
||||
import {SymbolInformation, SymbolKind, ITextDocument, Range, Location} from 'vscode-languageserver';
|
||||
|
||||
import {LinesModel} from './utils/lines';
|
||||
|
||||
export class JSONDocumentSymbols {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public compute(document: ITextDocument, lines: LinesModel, doc: Parser.JSONDocument): Promise<SymbolInformation[]> {
|
||||
|
||||
let root = doc.root;
|
||||
if (!root) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
// special handling for key bindings
|
||||
let resourceString = document.uri;
|
||||
if ((resourceString === 'inmemory://defaults/keybindings.json') || Strings.endsWith(resourceString.toLowerCase(), '/user/keybindings.json')) {
|
||||
if (root.type === 'array') {
|
||||
let result: SymbolInformation[] = [];
|
||||
(<Parser.ArrayASTNode>root).items.forEach((item) => {
|
||||
if (item.type === 'object') {
|
||||
let property = (<Parser.ObjectASTNode>item).getFirstProperty('key');
|
||||
if (property && property.value) {
|
||||
let location = Location.create(document.uri, Range.create(lines.positionAt(item.start), lines.positionAt(item.end)));
|
||||
result.push({ name: property.value.getValue(), kind: SymbolKind.Function, location: location });
|
||||
}
|
||||
}
|
||||
});
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
|
||||
let collectOutlineEntries = (result: SymbolInformation[], node: Parser.ASTNode, containerName: string): SymbolInformation[] => {
|
||||
if (node.type === 'array') {
|
||||
(<Parser.ArrayASTNode>node).items.forEach((node: Parser.ASTNode) => {
|
||||
collectOutlineEntries(result, node, containerName);
|
||||
});
|
||||
} else if (node.type === 'object') {
|
||||
let objectNode = <Parser.ObjectASTNode>node;
|
||||
|
||||
objectNode.properties.forEach((property: Parser.PropertyASTNode) => {
|
||||
let location = Location.create(document.uri, Range.create(lines.positionAt(property.start), lines.positionAt(property.end)));
|
||||
let valueNode = property.value;
|
||||
if (valueNode) {
|
||||
let childContainerName = containerName ? containerName + '.' + property.key.name : property.key.name;
|
||||
result.push({ name: property.key.getValue(), kind: this.getSymbolKind(valueNode.type), location: location, containerName: containerName });
|
||||
collectOutlineEntries(result, valueNode, childContainerName);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
let result = collectOutlineEntries([], root, void 0);
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
private getSymbolKind(nodeType: string): SymbolKind {
|
||||
switch (nodeType) {
|
||||
case 'object':
|
||||
return SymbolKind.Module;
|
||||
case 'string':
|
||||
return SymbolKind.String;
|
||||
case 'number':
|
||||
return SymbolKind.Number;
|
||||
case 'array':
|
||||
return SymbolKind.Array;
|
||||
case 'boolean':
|
||||
return SymbolKind.Boolean;
|
||||
default: // 'null'
|
||||
return SymbolKind.Variable;
|
||||
}
|
||||
}
|
||||
}
|
||||
180
extensions/json/server/src/jsonFormatter.ts
Normal file
180
extensions/json/server/src/jsonFormatter.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Json = require('./json-toolbox/json');
|
||||
import {ITextDocument, Range, Position, FormattingOptions, TextEdit} from 'vscode-languageserver';
|
||||
import {LinesModel} from './utils/lines';
|
||||
|
||||
export function format(document: ITextDocument, lines: LinesModel, range: Range, options: FormattingOptions): TextEdit[] {
|
||||
const documentText = document.getText();
|
||||
let initialIndentLevel: number;
|
||||
let value: string;
|
||||
let rangeOffset: number;
|
||||
if (range) {
|
||||
let startPosition = Position.create(range.start.line, 0);
|
||||
rangeOffset = lines.offsetAt(startPosition);
|
||||
|
||||
let endOffset = lines.offsetAt(Position.create(range.end.line + 1, 0));
|
||||
let endLineStart = lines.offsetAt(Position.create(range.end.line, 0));
|
||||
while (endOffset > endLineStart && isEOL(documentText, endOffset - 1)) {
|
||||
endOffset--;
|
||||
}
|
||||
range = Range.create(startPosition, lines.positionAt(endOffset));
|
||||
value = documentText.substring(rangeOffset, endOffset);
|
||||
initialIndentLevel = computeIndentLevel(value, 0, options);
|
||||
} else {
|
||||
value = documentText;
|
||||
range = Range.create(Position.create(0, 0), lines.positionAt(value.length));
|
||||
initialIndentLevel = 0;
|
||||
rangeOffset = 0;
|
||||
}
|
||||
let eol = getEOL(document, lines);
|
||||
|
||||
let lineBreak = false;
|
||||
let indentLevel = 0;
|
||||
let indentValue: string;
|
||||
if (options.insertSpaces) {
|
||||
indentValue = repeat(' ', options.tabSize);
|
||||
} else {
|
||||
indentValue = '\t';
|
||||
}
|
||||
|
||||
let scanner = Json.createScanner(value, false);
|
||||
|
||||
function newLineAndIndent(): string {
|
||||
return eol + repeat(indentValue, initialIndentLevel + indentLevel);
|
||||
}
|
||||
function scanNext(): Json.SyntaxKind {
|
||||
let token = scanner.scan();
|
||||
lineBreak = false;
|
||||
while (token === Json.SyntaxKind.Trivia || token === Json.SyntaxKind.LineBreakTrivia) {
|
||||
lineBreak = lineBreak || (token === Json.SyntaxKind.LineBreakTrivia);
|
||||
token = scanner.scan();
|
||||
}
|
||||
return token;
|
||||
}
|
||||
let editOperations: TextEdit[] = [];
|
||||
function addEdit(text: string, startOffset: number, endOffset: number) {
|
||||
if (documentText.substring(startOffset, endOffset) !== text) {
|
||||
let replaceRange = Range.create(lines.positionAt(startOffset), lines.positionAt(endOffset));
|
||||
editOperations.push(TextEdit.replace(replaceRange, text));
|
||||
}
|
||||
}
|
||||
|
||||
let firstToken = scanNext();
|
||||
if (firstToken !== Json.SyntaxKind.EOF) {
|
||||
let firstTokenStart = scanner.getTokenOffset() + rangeOffset;
|
||||
let initialIndent = repeat(indentValue, initialIndentLevel);
|
||||
addEdit(initialIndent, rangeOffset, firstTokenStart);
|
||||
}
|
||||
|
||||
while (firstToken !== Json.SyntaxKind.EOF) {
|
||||
let firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + rangeOffset;
|
||||
let secondToken = scanNext();
|
||||
|
||||
while (!lineBreak && (secondToken === Json.SyntaxKind.LineCommentTrivia || secondToken === Json.SyntaxKind.BlockCommentTrivia)) {
|
||||
// comments on the same line: keep them on the same line, but ignore them otherwise
|
||||
let commentTokenStart = scanner.getTokenOffset() + rangeOffset;
|
||||
addEdit(' ', firstTokenEnd, commentTokenStart);
|
||||
firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + rangeOffset;
|
||||
secondToken = scanNext();
|
||||
}
|
||||
let replaceContent = '';
|
||||
if (secondToken === Json.SyntaxKind.CloseBraceToken) {
|
||||
if (firstToken !== Json.SyntaxKind.OpenBraceToken) {
|
||||
indentLevel--;
|
||||
replaceContent = newLineAndIndent();
|
||||
}
|
||||
} else if (secondToken === Json.SyntaxKind.CloseBracketToken) {
|
||||
if (firstToken !== Json.SyntaxKind.OpenBracketToken) {
|
||||
indentLevel--;
|
||||
replaceContent = newLineAndIndent();
|
||||
}
|
||||
} else {
|
||||
switch (firstToken) {
|
||||
case Json.SyntaxKind.OpenBracketToken:
|
||||
case Json.SyntaxKind.OpenBraceToken:
|
||||
indentLevel++;
|
||||
replaceContent = newLineAndIndent();
|
||||
break;
|
||||
case Json.SyntaxKind.CommaToken:
|
||||
case Json.SyntaxKind.LineCommentTrivia:
|
||||
replaceContent = newLineAndIndent();
|
||||
break;
|
||||
case Json.SyntaxKind.BlockCommentTrivia:
|
||||
if (lineBreak) {
|
||||
replaceContent = newLineAndIndent();
|
||||
} else {
|
||||
// symbol following comment on the same line: keep on same line, separate with ' '
|
||||
replaceContent = ' ';
|
||||
}
|
||||
break;
|
||||
case Json.SyntaxKind.ColonToken:
|
||||
replaceContent = ' ';
|
||||
break;
|
||||
case Json.SyntaxKind.NullKeyword:
|
||||
case Json.SyntaxKind.TrueKeyword:
|
||||
case Json.SyntaxKind.FalseKeyword:
|
||||
case Json.SyntaxKind.NumericLiteral:
|
||||
if (secondToken === Json.SyntaxKind.NullKeyword || secondToken === Json.SyntaxKind.FalseKeyword || secondToken === Json.SyntaxKind.NumericLiteral) {
|
||||
replaceContent = ' ';
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (lineBreak && (secondToken === Json.SyntaxKind.LineCommentTrivia || secondToken === Json.SyntaxKind.BlockCommentTrivia)) {
|
||||
replaceContent = newLineAndIndent();
|
||||
}
|
||||
|
||||
}
|
||||
let secondTokenStart = scanner.getTokenOffset() + rangeOffset;
|
||||
addEdit(replaceContent, firstTokenEnd, secondTokenStart);
|
||||
firstToken = secondToken;
|
||||
}
|
||||
return editOperations;
|
||||
}
|
||||
|
||||
function repeat(s: string, count: number): string {
|
||||
let result = '';
|
||||
for (let i = 0; i < count; i++) {
|
||||
result += s;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function computeIndentLevel(content: string, offset: number, options: FormattingOptions): number {
|
||||
let i = 0;
|
||||
let nChars = 0;
|
||||
let tabSize = options.tabSize || 4;
|
||||
while (i < content.length) {
|
||||
let ch = content.charAt(i);
|
||||
if (ch === ' ') {
|
||||
nChars++;
|
||||
} else if (ch === '\t') {
|
||||
nChars += tabSize;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return Math.floor(nChars / tabSize);
|
||||
}
|
||||
|
||||
function getEOL(document: ITextDocument, lines: LinesModel): string {
|
||||
let text = document.getText();
|
||||
if (lines.lineCount > 1) {
|
||||
let to = lines.offsetAt(Position.create(1, 0));
|
||||
let from = to;
|
||||
while (from > 0 && isEOL(text, from - 1)) {
|
||||
from--;
|
||||
}
|
||||
return text.substr(from, to - from);
|
||||
}
|
||||
return '\n';
|
||||
}
|
||||
|
||||
function isEOL(text: string, offset: number) {
|
||||
return '\r\n'.indexOf(text.charAt(offset)) !== -1;
|
||||
}
|
||||
70
extensions/json/server/src/jsonHover.ts
Normal file
70
extensions/json/server/src/jsonHover.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
|
||||
import Parser = require('./jsonParser');
|
||||
import SchemaService = require('./jsonSchemaService');
|
||||
|
||||
import {Hover, ITextDocument, TextDocumentPosition, Range, MarkedString, RemoteConsole} from 'vscode-languageserver';
|
||||
|
||||
import {LinesModel} from './utils/lines';
|
||||
|
||||
export class JSONHover {
|
||||
|
||||
private schemaService: SchemaService.IJSONSchemaService;
|
||||
|
||||
constructor(schemaService: SchemaService.IJSONSchemaService) {
|
||||
this.schemaService = schemaService;
|
||||
}
|
||||
|
||||
public doHover(document: ITextDocument, textDocumentPosition: TextDocumentPosition, lines: LinesModel, doc: Parser.JSONDocument): Promise<Hover> {
|
||||
|
||||
let offset = lines.offsetAt(textDocumentPosition.position);
|
||||
let node = doc.getNodeFromOffset(offset);
|
||||
let originalNode = node;
|
||||
|
||||
// use the property description when hovering over an object key
|
||||
if (node && node.type === 'string') {
|
||||
let stringNode = <Parser.StringASTNode>node;
|
||||
if (stringNode.isKey) {
|
||||
let propertyNode = <Parser.PropertyASTNode>node.parent;
|
||||
node = propertyNode.value;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!node) {
|
||||
return Promise.resolve(void 0);
|
||||
}
|
||||
|
||||
return this.schemaService.getSchemaForResource(textDocumentPosition.uri, doc).then((schema) => {
|
||||
if (schema) {
|
||||
let matchingSchemas: Parser.IApplicableSchema[] = [];
|
||||
doc.validate(schema.schema, matchingSchemas, node.start);
|
||||
|
||||
let description: string = null;
|
||||
let contributonId: string = null;
|
||||
matchingSchemas.every((s) => {
|
||||
if (s.node === node && !s.inverted && s.schema) {
|
||||
description = description || s.schema.description;
|
||||
contributonId = contributonId || s.schema.id;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (description) {
|
||||
let range = Range.create(lines.positionAt(node.start), lines.positionAt(node.end));
|
||||
let result: Hover = {
|
||||
contents: [description],
|
||||
range: range
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return void 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
37
extensions/json/server/src/jsonLocation.ts
Normal file
37
extensions/json/server/src/jsonLocation.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export class JSONLocation {
|
||||
private segments: string[];
|
||||
|
||||
constructor(segments: string[]) {
|
||||
this.segments = segments;
|
||||
}
|
||||
|
||||
public append(segment: string): JSONLocation {
|
||||
return new JSONLocation(this.segments.concat(segment));
|
||||
}
|
||||
|
||||
public getSegments() {
|
||||
return this.segments;
|
||||
}
|
||||
|
||||
public matches(segments: string[]) {
|
||||
let k = 0;
|
||||
for (let i = 0; k < segments.length && i < this.segments.length; i++) {
|
||||
if (segments[k] === this.segments[i] || segments[k] === '*') {
|
||||
k++;
|
||||
} else if (segments[k] !== '**') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return k === segments.length;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return '[' + this.segments.join('][') + ']';
|
||||
}
|
||||
}
|
||||
1015
extensions/json/server/src/jsonParser.ts
Normal file
1015
extensions/json/server/src/jsonParser.ts
Normal file
File diff suppressed because it is too large
Load Diff
501
extensions/json/server/src/jsonSchemaService.ts
Normal file
501
extensions/json/server/src/jsonSchemaService.ts
Normal file
@@ -0,0 +1,501 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import nls = require('./utils/nls');
|
||||
import Json = require('./json-toolbox/json');
|
||||
import {IJSONSchema} from './json-toolbox/jsonSchema';
|
||||
import {IXHROptions, IXHRResponse, getErrorStatusDescription} from './utils/httpRequest';
|
||||
import URI from './utils/uri';
|
||||
import Strings = require('./utils/strings');
|
||||
import {parse as parseURL} from 'url';
|
||||
import path = require('path');
|
||||
import Parser = require('./jsonParser');
|
||||
|
||||
export interface IJSONSchemaService {
|
||||
|
||||
/**
|
||||
* Registers a schema file in the current workspace to be applicable to files that match the pattern
|
||||
*/
|
||||
registerExternalSchema(uri: string, filePatterns?: string[], unresolvedSchema?: IJSONSchema): ISchemaHandle;
|
||||
|
||||
/**
|
||||
* Clears all cached schema files
|
||||
*/
|
||||
clearExternalSchemas(): void;
|
||||
|
||||
/**
|
||||
* Registers contributed schemas
|
||||
*/
|
||||
setSchemaContributions(schemaContributions: ISchemaContributions): void;
|
||||
|
||||
/**
|
||||
* Looks up the appropriate schema for the given URI
|
||||
*/
|
||||
getSchemaForResource(resource: string, document: Parser.JSONDocument): Promise<ResolvedSchema>;
|
||||
}
|
||||
|
||||
export interface ISchemaContributions {
|
||||
schemas?: { [id: string]: IJSONSchema };
|
||||
schemaAssociations?: { [pattern: string]: string[] };
|
||||
}
|
||||
|
||||
export interface ISchemaHandle {
|
||||
/**
|
||||
* The schema id
|
||||
*/
|
||||
url: string;
|
||||
|
||||
/**
|
||||
* The schema from the file, with potential $ref references
|
||||
*/
|
||||
getUnresolvedSchema(): Promise<UnresolvedSchema>;
|
||||
|
||||
/**
|
||||
* The schema from the file, with references resolved
|
||||
*/
|
||||
getResolvedSchema(): Promise<ResolvedSchema>;
|
||||
}
|
||||
|
||||
|
||||
class FilePatternAssociation {
|
||||
|
||||
private schemas: string[];
|
||||
private combinedSchemaId: string;
|
||||
private patternRegExp: RegExp;
|
||||
private combinedSchema: ISchemaHandle;
|
||||
|
||||
constructor(pattern: string) {
|
||||
this.combinedSchemaId = 'local://combinedSchema/' + encodeURIComponent(pattern);
|
||||
try {
|
||||
this.patternRegExp = new RegExp(Strings.convertSimple2RegExpPattern(pattern) + '$');
|
||||
} catch (e) {
|
||||
// invalid pattern
|
||||
this.patternRegExp = null;
|
||||
}
|
||||
this.schemas = [];
|
||||
this.combinedSchema = null;
|
||||
}
|
||||
|
||||
public addSchema(id: string) {
|
||||
this.schemas.push(id);
|
||||
this.combinedSchema = null;
|
||||
}
|
||||
|
||||
public matchesPattern(fileName: string): boolean {
|
||||
return this.patternRegExp && this.patternRegExp.test(fileName);
|
||||
}
|
||||
|
||||
public getCombinedSchema(service: JSONSchemaService): ISchemaHandle {
|
||||
if (!this.combinedSchema) {
|
||||
this.combinedSchema = service.createCombinedSchema(this.combinedSchemaId, this.schemas);
|
||||
}
|
||||
return this.combinedSchema;
|
||||
}
|
||||
}
|
||||
|
||||
class SchemaHandle implements ISchemaHandle {
|
||||
|
||||
public url: string;
|
||||
|
||||
private resolvedSchema: Promise<ResolvedSchema>;
|
||||
private unresolvedSchema: Promise<UnresolvedSchema>;
|
||||
private service: JSONSchemaService;
|
||||
|
||||
constructor(service: JSONSchemaService, url: string, unresolvedSchemaContent?: IJSONSchema) {
|
||||
this.service = service;
|
||||
this.url = url;
|
||||
if (unresolvedSchemaContent) {
|
||||
this.unresolvedSchema = Promise.resolve(new UnresolvedSchema(unresolvedSchemaContent));
|
||||
}
|
||||
}
|
||||
|
||||
public getUnresolvedSchema(): Promise<UnresolvedSchema> {
|
||||
if (!this.unresolvedSchema) {
|
||||
this.unresolvedSchema = this.service.loadSchema(this.url);
|
||||
}
|
||||
return this.unresolvedSchema;
|
||||
}
|
||||
|
||||
public getResolvedSchema(): Promise<ResolvedSchema> {
|
||||
if (!this.resolvedSchema) {
|
||||
this.resolvedSchema = this.getUnresolvedSchema().then(unresolved => {
|
||||
return this.service.resolveSchemaContent(unresolved);
|
||||
});
|
||||
}
|
||||
return this.resolvedSchema;
|
||||
}
|
||||
|
||||
public clearSchema(): void {
|
||||
this.resolvedSchema = null;
|
||||
this.unresolvedSchema = null;
|
||||
}
|
||||
}
|
||||
|
||||
export class UnresolvedSchema {
|
||||
public schema: IJSONSchema;
|
||||
public errors: string[];
|
||||
|
||||
constructor(schema: IJSONSchema, errors: string[] = []) {
|
||||
this.schema = schema;
|
||||
this.errors = errors;
|
||||
}
|
||||
}
|
||||
|
||||
export class ResolvedSchema {
|
||||
public schema: IJSONSchema;
|
||||
public errors: string[];
|
||||
|
||||
constructor(schema: IJSONSchema, errors: string[] = []) {
|
||||
this.schema = schema;
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
public getSection(path: string[]): IJSONSchema {
|
||||
return this.getSectionRecursive(path, this.schema);
|
||||
}
|
||||
|
||||
private getSectionRecursive(path: string[], schema: IJSONSchema): IJSONSchema {
|
||||
if (!schema || path.length === 0) {
|
||||
return schema;
|
||||
}
|
||||
let next = path.shift();
|
||||
|
||||
if (schema.properties && schema.properties[next]) {
|
||||
return this.getSectionRecursive(path, schema.properties[next]);
|
||||
} else if (schema.patternProperties) {
|
||||
Object.keys(schema.patternProperties).forEach((pattern) => {
|
||||
let regex = new RegExp(pattern);
|
||||
if (regex.test(next)) {
|
||||
return this.getSectionRecursive(path, schema.patternProperties[pattern]);
|
||||
}
|
||||
});
|
||||
} else if (schema.additionalProperties) {
|
||||
return this.getSectionRecursive(path, schema.additionalProperties);
|
||||
} else if (next.match('[0-9]+')) {
|
||||
if (schema.items) {
|
||||
return this.getSectionRecursive(path, schema.items);
|
||||
} else if (Array.isArray(schema.items)) {
|
||||
try {
|
||||
let index = parseInt(next, 10);
|
||||
if (schema.items[index]) {
|
||||
return this.getSectionRecursive(path, schema.items[index]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ITelemetryService {
|
||||
log(key: string, data: any): void;
|
||||
}
|
||||
|
||||
export interface IWorkspaceContextService {
|
||||
toResource(workspaceRelativePath: string): string;
|
||||
}
|
||||
|
||||
export interface IRequestService {
|
||||
(options: IXHROptions): Promise<IXHRResponse>
|
||||
}
|
||||
|
||||
export class JSONSchemaService implements IJSONSchemaService {
|
||||
|
||||
private contributionSchemas: { [id: string]: SchemaHandle };
|
||||
private contributionAssociations: { [id: string]: string[] };
|
||||
|
||||
private schemasById: { [id: string]: SchemaHandle };
|
||||
private filePatternAssociations: FilePatternAssociation[];
|
||||
private filePatternAssociationById: { [id: string]: FilePatternAssociation };
|
||||
|
||||
private contextService: IWorkspaceContextService;
|
||||
private callOnDispose: Function[];
|
||||
private telemetryService: ITelemetryService;
|
||||
private requestService: IRequestService;
|
||||
|
||||
constructor(requestService: IRequestService, contextService?: IWorkspaceContextService, telemetryService?: ITelemetryService) {
|
||||
this.contextService = contextService;
|
||||
this.requestService = requestService;
|
||||
this.telemetryService = telemetryService;
|
||||
this.callOnDispose = [];
|
||||
|
||||
this.contributionSchemas = {};
|
||||
this.contributionAssociations = {};
|
||||
this.schemasById = {};
|
||||
this.filePatternAssociations = [];
|
||||
this.filePatternAssociationById = {};
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
while (this.callOnDispose.length > 0) {
|
||||
this.callOnDispose.pop()();
|
||||
}
|
||||
}
|
||||
|
||||
public onResourceChange(uri: string): boolean {
|
||||
let schemaFile = this.schemasById[uri];
|
||||
if (schemaFile) {
|
||||
schemaFile.clearSchema();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private normalizeId(id: string) {
|
||||
if (id.length > 0 && id.charAt(id.length - 1) === '#') {
|
||||
return id.substring(0, id.length - 1);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public setSchemaContributions(schemaContributions: ISchemaContributions): void {
|
||||
if (schemaContributions.schemas) {
|
||||
let schemas = schemaContributions.schemas;
|
||||
for (let id in schemas) {
|
||||
let normalizedId = this.normalizeId(id);
|
||||
this.contributionSchemas[normalizedId] = this.addSchemaHandle(normalizedId, schemas[id]);
|
||||
}
|
||||
}
|
||||
if (schemaContributions.schemaAssociations) {
|
||||
let schemaAssociations = schemaContributions.schemaAssociations;
|
||||
for (let pattern in schemaAssociations) {
|
||||
let associations = schemaAssociations[pattern];
|
||||
this.contributionAssociations[pattern] = associations;
|
||||
|
||||
var fpa = this.getOrAddFilePatternAssociation(pattern);
|
||||
associations.forEach(schemaId => {
|
||||
let id = this.normalizeId(schemaId);
|
||||
fpa.addSchema(id)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private addSchemaHandle(id: string, unresolvedSchemaContent?: IJSONSchema): SchemaHandle {
|
||||
let schemaHandle = new SchemaHandle(this, id, unresolvedSchemaContent);
|
||||
this.schemasById[id] = schemaHandle;
|
||||
return schemaHandle;
|
||||
}
|
||||
|
||||
private getOrAddSchemaHandle(id: string, unresolvedSchemaContent?: IJSONSchema): ISchemaHandle {
|
||||
return this.schemasById[id] || this.addSchemaHandle(id, unresolvedSchemaContent);
|
||||
}
|
||||
|
||||
private getOrAddFilePatternAssociation(pattern: string) {
|
||||
let fpa = this.filePatternAssociationById[pattern];
|
||||
if (!fpa) {
|
||||
fpa = new FilePatternAssociation(pattern);
|
||||
this.filePatternAssociationById[pattern] = fpa;
|
||||
this.filePatternAssociations.push(fpa);
|
||||
}
|
||||
return fpa;
|
||||
}
|
||||
|
||||
public registerExternalSchema(uri: string, filePatterns: string[] = null, unresolvedSchemaContent?: IJSONSchema): ISchemaHandle {
|
||||
let id = this.normalizeId(uri);
|
||||
|
||||
if (filePatterns) {
|
||||
filePatterns.forEach(pattern => {
|
||||
this.getOrAddFilePatternAssociation(pattern).addSchema(uri);
|
||||
});
|
||||
}
|
||||
return unresolvedSchemaContent ? this.addSchemaHandle(id, unresolvedSchemaContent) : this.getOrAddSchemaHandle(id);
|
||||
}
|
||||
|
||||
public clearExternalSchemas(): void {
|
||||
this.schemasById = {};
|
||||
this.filePatternAssociations = [];
|
||||
this.filePatternAssociationById = {};
|
||||
|
||||
for (let id in this.contributionSchemas) {
|
||||
this.schemasById[id] = this.contributionSchemas[id];
|
||||
}
|
||||
for (let pattern in this.contributionAssociations) {
|
||||
var fpa = this.getOrAddFilePatternAssociation(pattern);
|
||||
|
||||
this.contributionAssociations[pattern].forEach(schemaId => {
|
||||
let id = this.normalizeId(schemaId);
|
||||
fpa.addSchema(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public getResolvedSchema(schemaId: string): Promise<ResolvedSchema> {
|
||||
let id = this.normalizeId(schemaId);
|
||||
let schemaHandle = this.schemasById[id];
|
||||
if (schemaHandle) {
|
||||
return schemaHandle.getResolvedSchema();
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
public loadSchema(url: string): Promise<UnresolvedSchema> {
|
||||
if (this.telemetryService && Strings.startsWith(url, 'https://schema.management.azure.com')) {
|
||||
this.telemetryService.log('json.schema', {
|
||||
schemaURL: url
|
||||
});
|
||||
}
|
||||
|
||||
return this.requestService({ url: url, followRedirects: 5 }).then(
|
||||
request => {
|
||||
let content = request.responseText;
|
||||
if (!content) {
|
||||
let errorMessage = nls.localize('json.schema.nocontent', 'Unable to load schema from \'{0}\': No content.', toDisplayString(url));
|
||||
return new UnresolvedSchema(<IJSONSchema>{}, [errorMessage]);
|
||||
}
|
||||
|
||||
let schemaContent: IJSONSchema = {};
|
||||
let jsonErrors = [];
|
||||
schemaContent = Json.parse(content, jsonErrors);
|
||||
let errors = jsonErrors.length ? [nls.localize('json.schema.invalidFormat', 'Unable to parse content from \'{0}\': {1}.', toDisplayString(url), jsonErrors[0])] : [];
|
||||
return new UnresolvedSchema(schemaContent, errors);
|
||||
},
|
||||
(error: IXHRResponse) => {
|
||||
let errorMessage = nls.localize('json.schema.unabletoload', 'Unable to load schema from \'{0}\': {1}', toDisplayString(url), error.responseText || getErrorStatusDescription(error.status) || error.toString());
|
||||
return new UnresolvedSchema(<IJSONSchema>{}, [errorMessage]);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public resolveSchemaContent(schemaToResolve: UnresolvedSchema): Promise<ResolvedSchema> {
|
||||
|
||||
let resolveErrors: string[] = schemaToResolve.errors.slice(0);
|
||||
let schema = schemaToResolve.schema;
|
||||
|
||||
let findSection = (schema: IJSONSchema, path: string): any => {
|
||||
if (!path) {
|
||||
return schema;
|
||||
}
|
||||
let current: any = schema;
|
||||
path.substr(1).split('/').some((part) => {
|
||||
current = current[part];
|
||||
return !current;
|
||||
});
|
||||
return current;
|
||||
};
|
||||
|
||||
let resolveLink = (node: any, linkedSchema: IJSONSchema, linkPath: string): void => {
|
||||
let section = findSection(linkedSchema, linkPath);
|
||||
if (section) {
|
||||
for (let key in section) {
|
||||
if (section.hasOwnProperty(key) && !node.hasOwnProperty(key)) {
|
||||
node[key] = section[key];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resolveErrors.push(nls.localize('json.schema.invalidref', '$ref \'{0}\' in {1} can not be resolved.', linkPath, linkedSchema.id));
|
||||
}
|
||||
delete node.$ref;
|
||||
}
|
||||
|
||||
let resolveExternalLink = (node: any, uri: string, linkPath: string): Promise<any> => {
|
||||
return this.getOrAddSchemaHandle(uri).getUnresolvedSchema().then(unresolvedSchema => {
|
||||
if (unresolvedSchema.errors.length) {
|
||||
let loc = linkPath ? uri + '#' + linkPath : uri;
|
||||
resolveErrors.push(nls.localize('json.schema.problemloadingref', 'Problems loading reference \'{0}\': {1}', loc, unresolvedSchema.errors[0]));
|
||||
}
|
||||
resolveLink(node, unresolvedSchema.schema, linkPath);
|
||||
return resolveRefs(node, unresolvedSchema.schema);
|
||||
});
|
||||
}
|
||||
|
||||
let resolveRefs = (node: any, parentSchema: any): Promise<any> => {
|
||||
let toWalk = [node];
|
||||
let seen: any[] = [];
|
||||
|
||||
let openPromises: Promise<any>[] = [];
|
||||
|
||||
while (toWalk.length) {
|
||||
let next = toWalk.pop();
|
||||
if (seen.indexOf(next) >= 0) {
|
||||
continue;
|
||||
}
|
||||
seen.push(next);
|
||||
if (Array.isArray(next)) {
|
||||
next.forEach(item => {
|
||||
toWalk.push(item);
|
||||
});
|
||||
} else if (next) {
|
||||
if (next.$ref) {
|
||||
let segments = next.$ref.split('#', 2);
|
||||
if (segments[0].length > 0) {
|
||||
openPromises.push(resolveExternalLink(next, segments[0], segments[1]));
|
||||
continue;
|
||||
} else {
|
||||
resolveLink(next, parentSchema, segments[1]);
|
||||
}
|
||||
}
|
||||
for (let key in next) {
|
||||
toWalk.push(next[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.all(openPromises);
|
||||
}
|
||||
|
||||
return resolveRefs(schema, schema).then(_ => new ResolvedSchema(schema, resolveErrors));
|
||||
}
|
||||
|
||||
public getSchemaForResource(resource: string, document: Parser.JSONDocument): Promise<ResolvedSchema> {
|
||||
|
||||
// first use $schema if present
|
||||
if (document && document.root && document.root.type === 'object') {
|
||||
let schemaProperties = (<Parser.ObjectASTNode>document.root).properties.filter((p) => (p.key.value === '$schema') && !!p.value);
|
||||
if (schemaProperties.length > 0) {
|
||||
let schemeId = <string>schemaProperties[0].value.getValue();
|
||||
if (!Strings.startsWith(schemeId, 'http://') && !Strings.startsWith(schemeId, 'https://') && !Strings.startsWith(schemeId, 'file://')) {
|
||||
if (this.contextService) {
|
||||
let resourceURL = this.contextService.toResource(schemeId);
|
||||
if (resourceURL) {
|
||||
schemeId = resourceURL.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (schemeId) {
|
||||
let id = this.normalizeId(schemeId);
|
||||
return this.getOrAddSchemaHandle(id).getResolvedSchema();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// then check for matching file names, last to first
|
||||
for (let i = this.filePatternAssociations.length - 1; i >= 0; i--) {
|
||||
let entry = this.filePatternAssociations[i];
|
||||
if (entry.matchesPattern(resource)) {
|
||||
return entry.getCombinedSchema(this).getResolvedSchema();
|
||||
}
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
public createCombinedSchema(combinedSchemaId: string, schemaIds: string[]): ISchemaHandle {
|
||||
if (schemaIds.length === 1) {
|
||||
return this.getOrAddSchemaHandle(schemaIds[0]);
|
||||
} else {
|
||||
let combinedSchema: IJSONSchema = {
|
||||
allOf: schemaIds.map(schemaId => ({ $ref: schemaId }))
|
||||
}
|
||||
return this.addSchemaHandle(combinedSchemaId, combinedSchema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toDisplayString(url: string) {
|
||||
try {
|
||||
let uri = URI.parse(url);
|
||||
if (uri.scheme === 'file') {
|
||||
return uri.fsPath;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
return url;
|
||||
}
|
||||
237
extensions/json/server/src/server.ts
Normal file
237
extensions/json/server/src/server.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {
|
||||
IPCMessageReader, IPCMessageWriter,
|
||||
createConnection, IConnection, TextDocumentSyncKind,
|
||||
TextDocuments, ITextDocument, Diagnostic, DiagnosticSeverity,
|
||||
InitializeParams, InitializeResult, TextDocumentIdentifier, TextDocumentPosition,
|
||||
CompletionItem, CompletionItemKind, Files, Hover, SymbolInformation, TextEdit, DocumentFormattingParams,
|
||||
DocumentRangeFormattingParams, NotificationType
|
||||
} from 'vscode-languageserver';
|
||||
|
||||
import {xhr, IXHROptions, IXHRResponse} from './utils/httpRequest';
|
||||
import path = require('path');
|
||||
import fs = require('fs');
|
||||
import URI from './utils/uri';
|
||||
import Strings = require('./utils/strings');
|
||||
import {create as createLinesModel, LinesModel} from './utils/lines';
|
||||
import {IWorkspaceContextService, ITelemetryService, JSONSchemaService, ISchemaContributions} from './jsonSchemaService';
|
||||
import {parse as parseJSON, ObjectASTNode, JSONDocument} from './jsonParser';
|
||||
import {JSONCompletion} from './jsonCompletion';
|
||||
import {JSONHover} from './jsonHover';
|
||||
import {JSONDocumentSymbols} from './jsonDocumentSymbols';
|
||||
import {format as formatJSON} from './jsonFormatter';
|
||||
import {schemaContributions} from './configuration';
|
||||
|
||||
namespace TelemetryNotification {
|
||||
export const type: NotificationType<{ key: string, data: any }> = { get method() { return 'telemetry'; } };
|
||||
}
|
||||
|
||||
// Create a connection for the server. The connection uses for
|
||||
// stdin / stdout for message passing
|
||||
let connection: IConnection = createConnection(new IPCMessageReader(process), new IPCMessageWriter(process));
|
||||
|
||||
// Create a simple text document manager. The text document manager
|
||||
// supports full document sync only
|
||||
let documents: TextDocuments = new TextDocuments();
|
||||
// Make the text document manager listen on the connection
|
||||
// for open, change and close text document events
|
||||
documents.listen(connection);
|
||||
|
||||
|
||||
// After the server has started the client sends an initilize request. The server receives
|
||||
// in the passed params the rootPath of the workspace plus the client capabilites.
|
||||
let workspaceRoot: URI;
|
||||
connection.onInitialize((params): InitializeResult => {
|
||||
workspaceRoot = URI.parse(params.rootPath);
|
||||
return {
|
||||
capabilities: {
|
||||
// Tell the client that the server works in FULL text document sync mode
|
||||
textDocumentSync: documents.syncKind,
|
||||
completionProvider: { resolveProvider: false },
|
||||
hoverProvider: true,
|
||||
documentSymbolProvider: true,
|
||||
documentRangeFormattingProvider: true,
|
||||
documentFormattingProvider: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let workspaceContext = {
|
||||
toResource: (workspaceRelativePath: string) => {
|
||||
if (typeof workspaceRelativePath === 'string' && workspaceRoot) {
|
||||
return URI.file(path.join(workspaceRoot.fsPath, workspaceRelativePath)).toString();
|
||||
}
|
||||
return workspaceRelativePath;
|
||||
}
|
||||
}
|
||||
|
||||
let telemetry = {
|
||||
log: (key: string, data: any) => {
|
||||
connection.sendNotification(TelemetryNotification.type, { key, data });
|
||||
}
|
||||
}
|
||||
|
||||
let request = (options: IXHROptions): Promise<IXHRResponse> => {
|
||||
if (options.url.indexOf('file://') === 0) {
|
||||
let fsPath = URI.parse(options.url).fsPath;
|
||||
return new Promise<IXHRResponse>((c, e) => {
|
||||
fs.readFile(fsPath, 'UTF-8', (err, result) => {
|
||||
err ? e({ responseText: '', status: 404 }) : c({ responseText: result.toString(), status: 200 })
|
||||
});
|
||||
});
|
||||
}
|
||||
return xhr(options);
|
||||
}
|
||||
|
||||
let jsonSchemaService = new JSONSchemaService(request, workspaceContext, telemetry);
|
||||
jsonSchemaService.setSchemaContributions(schemaContributions);
|
||||
|
||||
let jsonCompletion = new JSONCompletion(jsonSchemaService);
|
||||
let jsonHover = new JSONHover(jsonSchemaService);
|
||||
let jsonDocumentSymbols = new JSONDocumentSymbols();
|
||||
|
||||
// The content of a text document has changed. This event is emitted
|
||||
// when the text document first opened or when its content has changed.
|
||||
documents.onDidChangeContent((change) => {
|
||||
validateTextDocument(change.document);
|
||||
});
|
||||
|
||||
// The settings interface describe the server relevant settings part
|
||||
interface Settings {
|
||||
json: JSONSettings;
|
||||
}
|
||||
|
||||
interface JSONSettings {
|
||||
schemas: [{ fileMatch: string[], url: string, schema: any }];
|
||||
}
|
||||
|
||||
// The settings have changed. Is send on server activation as well.
|
||||
connection.onDidChangeConfiguration((change) => {
|
||||
let jsonSettings = (<Settings>change.settings).json;
|
||||
|
||||
if (jsonSettings && jsonSettings.schemas) {
|
||||
jsonSchemaService.clearExternalSchemas();
|
||||
jsonSettings.schemas.forEach((schema) => {
|
||||
if (schema.url && (schema.fileMatch || schema.schema)) {
|
||||
let url = schema.url;
|
||||
if (!Strings.startsWith(url, 'http://') && !Strings.startsWith(url, 'https://') && !Strings.startsWith(url, 'file://')) {
|
||||
let resourceURL = workspaceContext.toResource(url);
|
||||
if (resourceURL) {
|
||||
url = resourceURL.toString();
|
||||
}
|
||||
}
|
||||
if (url) {
|
||||
jsonSchemaService.registerExternalSchema(url, schema.fileMatch, schema.schema);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Revalidate any open text documents
|
||||
documents.all().forEach(validateTextDocument);
|
||||
});
|
||||
|
||||
function validateTextDocument(textDocument: ITextDocument): void {
|
||||
let jsonDocument = getJSONDocument(textDocument);
|
||||
jsonSchemaService.getSchemaForResource(textDocument.uri, jsonDocument).then(schema => {
|
||||
if (schema) {
|
||||
if (schema.errors.length && jsonDocument.root) {
|
||||
let astRoot = jsonDocument.root;
|
||||
let property = astRoot.type === 'object' ? (<ObjectASTNode>astRoot).getFirstProperty('$schema') : null;
|
||||
if (property) {
|
||||
let node = property.value || property;
|
||||
jsonDocument.warnings.push({ location: { start: node.start, end: node.end }, message: schema.errors[0] });
|
||||
} else {
|
||||
jsonDocument.warnings.push({ location: { start: astRoot.start, end: astRoot.start + 1 }, message: schema.errors[0] });
|
||||
}
|
||||
} else {
|
||||
jsonDocument.validate(schema.schema);
|
||||
}
|
||||
}
|
||||
|
||||
let diagnostics: Diagnostic[] = [];
|
||||
let lineModel = getLinesModel(textDocument);
|
||||
let added: { [signature: string]: boolean } = {};
|
||||
jsonDocument.errors.concat(jsonDocument.warnings).forEach((error, idx) => {
|
||||
// remove duplicated messages
|
||||
let signature = error.location.start + ' ' + error.location.end + ' ' + error.message;
|
||||
if (!added[signature]) {
|
||||
added[signature] = true;
|
||||
let range = {
|
||||
start: lineModel.positionAt(error.location.start),
|
||||
end: lineModel.positionAt(error.location.end)
|
||||
};
|
||||
diagnostics.push({
|
||||
severity: idx >= jsonDocument.errors.length ? DiagnosticSeverity.Warning : DiagnosticSeverity.Error,
|
||||
range: range,
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
// Send the computed diagnostics to VSCode.
|
||||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
|
||||
});
|
||||
}
|
||||
|
||||
connection.onDidChangeWatchedFiles((change) => {
|
||||
// Monitored files have change in VSCode
|
||||
let hasChanges = false;
|
||||
change.changes.forEach(c => {
|
||||
if (jsonSchemaService.onResourceChange(c.uri)) {
|
||||
hasChanges = true;
|
||||
}
|
||||
});
|
||||
if (hasChanges) {
|
||||
documents.all().forEach(validateTextDocument);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function getLinesModel(document: ITextDocument): LinesModel {
|
||||
return createLinesModel(document.getText());
|
||||
}
|
||||
|
||||
function getJSONDocument(document: ITextDocument): JSONDocument {
|
||||
return parseJSON(document.getText());
|
||||
}
|
||||
|
||||
connection.onCompletion((textDocumentPosition: TextDocumentPosition): Thenable<CompletionItem[]> => {
|
||||
let document = documents.get(textDocumentPosition.uri);
|
||||
let lines = getLinesModel(document);
|
||||
let jsonDocument = getJSONDocument(document);
|
||||
return jsonCompletion.doSuggest(document, textDocumentPosition, lines, jsonDocument);
|
||||
});
|
||||
|
||||
connection.onHover((textDocumentPosition: TextDocumentPosition): Thenable<Hover> => {
|
||||
let document = documents.get(textDocumentPosition.uri);
|
||||
let lines = getLinesModel(document);
|
||||
let jsonDocument = getJSONDocument(document);
|
||||
return jsonHover.doHover(document, textDocumentPosition, lines, jsonDocument);
|
||||
});
|
||||
|
||||
connection.onDocumentSymbol((textDocumentIdentifier: TextDocumentIdentifier): Thenable<SymbolInformation[]> => {
|
||||
let document = documents.get(textDocumentIdentifier.uri);
|
||||
let lines = getLinesModel(document);
|
||||
let jsonDocument = getJSONDocument(document);
|
||||
return jsonDocumentSymbols.compute(document, lines, jsonDocument);
|
||||
});
|
||||
|
||||
connection.onDocumentFormatting((formatParams: DocumentFormattingParams) => {
|
||||
let document = documents.get(formatParams.textDocument.uri);
|
||||
let lines = getLinesModel(document);
|
||||
return formatJSON(document, lines, null, formatParams.options);
|
||||
});
|
||||
|
||||
connection.onDocumentRangeFormatting((formatParams: DocumentRangeFormattingParams) => {
|
||||
let document = documents.get(formatParams.textDocument.uri);
|
||||
let lines = getLinesModel(document);
|
||||
return formatJSON(document, lines, formatParams.range, formatParams.options);
|
||||
});
|
||||
|
||||
// Listen on the connection
|
||||
connection.listen();
|
||||
448
extensions/json/server/src/tests/completion.test.ts
Normal file
448
extensions/json/server/src/tests/completion.test.ts
Normal file
@@ -0,0 +1,448 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import assert = require('assert');
|
||||
import Parser = require('../jsonParser');
|
||||
import SchemaService = require('../jsonSchemaService');
|
||||
import JsonSchema = require('../json-toolbox/jsonSchema');
|
||||
import {JSONCompletion} from '../jsonCompletion';
|
||||
import {IXHROptions, IXHRResponse} from '../utils/httpRequest';
|
||||
import {create as createLinesModel} from '../utils/lines';
|
||||
|
||||
import {CompletionItem, CompletionItemKind, CompletionOptions, ITextDocument, TextDocumentIdentifier, TextDocumentPosition, Range, Position, TextEdit} from 'vscode-languageserver';
|
||||
|
||||
suite('JSON Completion', () => {
|
||||
|
||||
var requestService = function(options: IXHROptions): Promise<IXHRResponse> {
|
||||
return Promise.reject<IXHRResponse>({ responseText: '', status: 404 });
|
||||
}
|
||||
|
||||
var assertSuggestion = function(completions: CompletionItem[], label: string, documentation?: string) {
|
||||
var matches = completions.filter(function(completion: CompletionItem) {
|
||||
return completion.label === label && (!documentation || completion.documentation === documentation);
|
||||
}).length;
|
||||
assert.equal(matches, 1, label + " should only existing once");
|
||||
};
|
||||
|
||||
var testSuggestionsFor = function(value: string, stringAfter: string, schema?: JsonSchema.IJSONSchema): Promise<CompletionItem[]> {
|
||||
var uri = 'test://test.json';
|
||||
var idx = stringAfter ? value.indexOf(stringAfter) : 0;
|
||||
|
||||
var schemaService = new SchemaService.JSONSchemaService(requestService);
|
||||
var completionProvider = new JSONCompletion(schemaService);
|
||||
if (schema) {
|
||||
var id = "http://myschemastore/test1";
|
||||
schemaService.registerExternalSchema(id, ["*.json"], schema);
|
||||
}
|
||||
|
||||
var document = {
|
||||
getText: () => value,
|
||||
uri: uri
|
||||
}
|
||||
var textDocumentLocation = TextDocumentPosition.create(uri, Position.create(0, idx));
|
||||
var lines = createLinesModel(value);
|
||||
var jsonDoc = Parser.parse(value);
|
||||
return completionProvider.doSuggest(document, textDocumentLocation, lines, jsonDoc);
|
||||
};
|
||||
|
||||
|
||||
|
||||
test('Complete keys no schema', function(testDone) {
|
||||
Promise.all([
|
||||
testSuggestionsFor('[ { "name": "John", "age": 44 }, { /**/ }', '/**/').then((result) => {
|
||||
assert.strictEqual(result.length, 2);
|
||||
assertSuggestion(result, 'name');
|
||||
assertSuggestion(result, 'age');
|
||||
}),
|
||||
testSuggestionsFor('[ { "name": "John", "age": 44 }, { "/**/ }', '/**/').then((result) => {
|
||||
assert.strictEqual(result.length, 2);
|
||||
assertSuggestion(result, 'name');
|
||||
assertSuggestion(result, 'age');
|
||||
}),
|
||||
testSuggestionsFor('[ { "name": "John", "age": 44 }, { "n/**/ }', '/**/').then((result) => {
|
||||
assert.strictEqual(result.length, 2);
|
||||
assertSuggestion(result, 'name');
|
||||
}),
|
||||
testSuggestionsFor('[ { "name": "John", "age": 44 }, { "name/**/" }', '/**/').then((result) => {
|
||||
assert.strictEqual(result.length, 2);
|
||||
assertSuggestion(result, 'name');
|
||||
}),
|
||||
testSuggestionsFor('[ { "name": "John", "address": { "street" : "MH Road", "number" : 5 } }, { "name": "Jack", "address": { "street" : "100 Feet Road", /**/ }', '/**/').then((result) => {
|
||||
assert.strictEqual(result.length, 1);
|
||||
assertSuggestion(result, 'number');
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('Complete values no schema', function(testDone) {
|
||||
Promise.all([
|
||||
testSuggestionsFor('[ { "name": "John", "age": 44 }, { "name": /**/', '/**/').then((result) => {
|
||||
assert.strictEqual(result.length, 1);
|
||||
assertSuggestion(result, '"John"');
|
||||
}),
|
||||
testSuggestionsFor('[ { "data": { "key": 1, "data": true } }, { "data": /**/', '/**/').then((result) => {
|
||||
assert.strictEqual(result.length, 3);
|
||||
assertSuggestion(result, '{}');
|
||||
assertSuggestion(result, 'true');
|
||||
assertSuggestion(result, 'false');
|
||||
}),
|
||||
testSuggestionsFor('[ { "data": "foo" }, { "data": "bar" }, { "data": "/**/" } ]', '/**/').then((result) => {
|
||||
assert.strictEqual(result.length, 3);
|
||||
assertSuggestion(result, '"foo"');
|
||||
assertSuggestion(result, '"bar"');
|
||||
assertSuggestion(result, '"/**/"');
|
||||
}),
|
||||
testSuggestionsFor('[ { "data": "foo" }, { "data": "bar" }, { "data": "f/**/" } ]', '/**/').then((result) => {
|
||||
assert.strictEqual(result.length, 3);
|
||||
assertSuggestion(result, '"foo"');
|
||||
assertSuggestion(result, '"bar"');
|
||||
assertSuggestion(result, '"f/**/"');
|
||||
}),
|
||||
testSuggestionsFor('[ { "data": "foo" }, { "data": "bar" }, { "data": "xoo"/**/ } ]', '/**/').then((result) => {
|
||||
assert.strictEqual(result.length, 3);
|
||||
assertSuggestion(result, '"xoo"');
|
||||
}),
|
||||
testSuggestionsFor('[ { "data": "foo" }, { "data": "bar" }, { "data": "xoo" /**/ } ]', '/**/').then((result) => {
|
||||
assert.strictEqual(result.length, 0);
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('Complete keys with schema', function(testDone) {
|
||||
var schema: JsonSchema.IJSONSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'a': {
|
||||
type: 'number',
|
||||
description: 'A'
|
||||
},
|
||||
'b': {
|
||||
type: 'string',
|
||||
description: 'B'
|
||||
},
|
||||
'c': {
|
||||
type: 'boolean',
|
||||
description: 'C'
|
||||
}
|
||||
}
|
||||
};
|
||||
Promise.all([
|
||||
testSuggestionsFor('{/**/}', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 3);
|
||||
assertSuggestion(result, 'a', 'A');
|
||||
assertSuggestion(result, 'b', 'B');
|
||||
assertSuggestion(result, 'c', 'C');
|
||||
}),
|
||||
testSuggestionsFor('{ "/**/}', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 3);
|
||||
assertSuggestion(result, 'a', 'A');
|
||||
assertSuggestion(result, 'b', 'B');
|
||||
assertSuggestion(result, 'c', 'C');
|
||||
}),
|
||||
testSuggestionsFor('{ "a/**/}', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 3);
|
||||
assertSuggestion(result, 'a', 'A');
|
||||
}),
|
||||
testSuggestionsFor('{ "a" = 1;/**/}', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 2);
|
||||
assertSuggestion(result, 'b', 'B');
|
||||
assertSuggestion(result, 'c', 'C');
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
|
||||
});
|
||||
|
||||
test('Complete value with schema', function(testDone) {
|
||||
|
||||
var schema: JsonSchema.IJSONSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'a': {
|
||||
enum: ['John', 'Jeff', 'George']
|
||||
}
|
||||
}
|
||||
};
|
||||
Promise.all([
|
||||
testSuggestionsFor('{ "a": /**/ }', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 3);
|
||||
assertSuggestion(result, '"John"');
|
||||
assertSuggestion(result, '"Jeff"');
|
||||
assertSuggestion(result, '"George"');
|
||||
}),
|
||||
|
||||
testSuggestionsFor('{ "a": "J/**/ }', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 3);
|
||||
assertSuggestion(result, '"John"');
|
||||
assertSuggestion(result, '"Jeff"');
|
||||
}),
|
||||
|
||||
testSuggestionsFor('{ "a": "John"/**/ }', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 3);
|
||||
assertSuggestion(result, '"John"');
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('Complete with nested schema', function(testDone) {
|
||||
|
||||
var content = '{/**/}';
|
||||
var schema: JsonSchema.IJSONSchema = {
|
||||
oneOf: [{
|
||||
type: 'object',
|
||||
properties: {
|
||||
'a': {
|
||||
type: 'number',
|
||||
description: 'A'
|
||||
},
|
||||
'b': {
|
||||
type: 'string',
|
||||
description: 'B'
|
||||
},
|
||||
}
|
||||
}, {
|
||||
type: 'array'
|
||||
}]
|
||||
};
|
||||
Promise.all([
|
||||
testSuggestionsFor(content, '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 2);
|
||||
assertSuggestion(result, 'a', 'A');
|
||||
assertSuggestion(result, 'b', 'B');
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('Complete with required anyOf', function(testDone) {
|
||||
|
||||
var schema: JsonSchema.IJSONSchema = {
|
||||
anyOf: [{
|
||||
type: 'object',
|
||||
required: ['a', 'b'],
|
||||
properties: {
|
||||
'a': {
|
||||
type: 'string',
|
||||
description: 'A'
|
||||
},
|
||||
'b': {
|
||||
type: 'string',
|
||||
description: 'B'
|
||||
},
|
||||
}
|
||||
}, {
|
||||
type: 'object',
|
||||
required: ['c', 'd'],
|
||||
properties: {
|
||||
'c': {
|
||||
type: 'string',
|
||||
description: 'C'
|
||||
},
|
||||
'd': {
|
||||
type: 'string',
|
||||
description: 'D'
|
||||
},
|
||||
}
|
||||
}]
|
||||
};
|
||||
Promise.all([
|
||||
testSuggestionsFor('{/**/}', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 4);
|
||||
assertSuggestion(result, 'a', 'A');
|
||||
assertSuggestion(result, 'b', 'B');
|
||||
assertSuggestion(result, 'c', 'C');
|
||||
assertSuggestion(result, 'd', 'D');
|
||||
}),
|
||||
testSuggestionsFor('{ "a": "", /**/}', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 1);
|
||||
assertSuggestion(result, 'b', 'B');
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('Complete with anyOf', function(testDone) {
|
||||
|
||||
var schema: JsonSchema.IJSONSchema = {
|
||||
anyOf: [{
|
||||
type: 'object',
|
||||
properties: {
|
||||
'type': {
|
||||
enum: ['house']
|
||||
},
|
||||
'b': {
|
||||
type: 'string'
|
||||
},
|
||||
}
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'type': {
|
||||
enum: ['appartment']
|
||||
},
|
||||
'c': {
|
||||
type: 'string'
|
||||
},
|
||||
}
|
||||
}]
|
||||
};
|
||||
Promise.all([
|
||||
testSuggestionsFor('{/**/}', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 3);
|
||||
assertSuggestion(result, 'type');
|
||||
assertSuggestion(result, 'b');
|
||||
assertSuggestion(result, 'c');
|
||||
}),
|
||||
testSuggestionsFor('{ "type": "appartment", /**/}', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 1);
|
||||
assertSuggestion(result, 'c');
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('Complete with oneOf', function(testDone) {
|
||||
|
||||
var schema: JsonSchema.IJSONSchema = {
|
||||
oneOf: [{
|
||||
type: 'object',
|
||||
allOf: [{
|
||||
properties: {
|
||||
'a': {
|
||||
type: 'string',
|
||||
description: 'A'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
anyOf: [{
|
||||
properties: {
|
||||
'b1': {
|
||||
type: 'string',
|
||||
description: 'B1'
|
||||
}
|
||||
},
|
||||
}, {
|
||||
properties: {
|
||||
'b2': {
|
||||
type: 'string',
|
||||
description: 'B2'
|
||||
}
|
||||
},
|
||||
}]
|
||||
}]
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'c': {
|
||||
type: 'string',
|
||||
description: 'C'
|
||||
},
|
||||
'd': {
|
||||
type: 'string',
|
||||
description: 'D'
|
||||
},
|
||||
}
|
||||
}]
|
||||
};
|
||||
Promise.all([
|
||||
testSuggestionsFor('{/**/}', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 5);
|
||||
assertSuggestion(result, 'a', 'A');
|
||||
assertSuggestion(result, 'b1', 'B1');
|
||||
assertSuggestion(result, 'b2', 'B2');
|
||||
assertSuggestion(result, 'c', 'C');
|
||||
assertSuggestion(result, 'd', 'D');
|
||||
}),
|
||||
testSuggestionsFor('{ "b1": "", /**/}', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 2);
|
||||
assertSuggestion(result, 'a', 'A');
|
||||
assertSuggestion(result, 'b2', 'B2');
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('Complete with oneOf and enums', function(testDone) {
|
||||
|
||||
var schema: JsonSchema.IJSONSchema = {
|
||||
oneOf: [{
|
||||
type: 'object',
|
||||
properties: {
|
||||
'type': {
|
||||
type: 'string',
|
||||
enum: ['1', '2']
|
||||
},
|
||||
'a': {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'x': {
|
||||
type: 'string'
|
||||
},
|
||||
'y': {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
"required": ['x', 'y']
|
||||
},
|
||||
'b': {}
|
||||
},
|
||||
}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'type': {
|
||||
type: 'string',
|
||||
enum: ['3']
|
||||
},
|
||||
'a': {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'x': {
|
||||
type: 'string'
|
||||
},
|
||||
'z': {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
"required": ['x', 'z']
|
||||
},
|
||||
'c': {}
|
||||
},
|
||||
}]
|
||||
};
|
||||
Promise.all([
|
||||
testSuggestionsFor('{/**/}', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 4);
|
||||
assertSuggestion(result, 'type');
|
||||
assertSuggestion(result, 'a');
|
||||
assertSuggestion(result, 'b');
|
||||
assertSuggestion(result, 'c');
|
||||
}),
|
||||
testSuggestionsFor('{ "type": /**/}', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 3);
|
||||
assertSuggestion(result, '"1"');
|
||||
assertSuggestion(result, '"2"');
|
||||
assertSuggestion(result, '"3"');
|
||||
}),
|
||||
testSuggestionsFor('{ "a": { "x": "", "y": "" }, "type": /**/}', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 2);
|
||||
assertSuggestion(result, '"1"');
|
||||
assertSuggestion(result, '"2"');
|
||||
}),
|
||||
testSuggestionsFor('{ "type": "1", "a" : { /**/ }', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 2);
|
||||
assertSuggestion(result, 'x');
|
||||
assertSuggestion(result, 'y');
|
||||
}),
|
||||
testSuggestionsFor('{ "type": "1", "a" : { "x": "", "z":"" }, /**/', '/**/', schema).then((result) => {
|
||||
// both alternatives have errors: intellisense proposes all options
|
||||
assert.strictEqual(result.length, 2);
|
||||
assertSuggestion(result, 'b');
|
||||
assertSuggestion(result, 'c');
|
||||
}),
|
||||
testSuggestionsFor('{ "a" : { "x": "", "z":"" }, /**/', '/**/', schema).then((result) => {
|
||||
assert.strictEqual(result.length, 2);
|
||||
assertSuggestion(result, 'type');
|
||||
assertSuggestion(result, 'c');
|
||||
}),
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
});
|
||||
102
extensions/json/server/src/tests/documentSymbols.test.ts
Normal file
102
extensions/json/server/src/tests/documentSymbols.test.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import assert = require('assert');
|
||||
import Parser = require('../jsonParser');
|
||||
import SchemaService = require('../jsonSchemaService');
|
||||
import JsonSchema = require('../json-toolbox/jsonSchema');
|
||||
import {JSONCompletion} from '../jsonCompletion';
|
||||
import {IXHROptions, IXHRResponse} from '../utils/httpRequest';
|
||||
import {create as createLinesModel} from '../utils/lines';
|
||||
import {JSONDocumentSymbols} from '../jsonDocumentSymbols';
|
||||
|
||||
import {SymbolInformation, SymbolKind, TextDocumentIdentifier, TextDocumentPosition, Range, Position, TextEdit} from 'vscode-languageserver';
|
||||
|
||||
suite('JSON Document Symbols', () => {
|
||||
|
||||
function getOutline(value: string): Promise<SymbolInformation[]> {
|
||||
var uri = 'test://test.json';
|
||||
|
||||
var symbolProvider = new JSONDocumentSymbols();
|
||||
|
||||
var document = {
|
||||
getText: () => value,
|
||||
uri: uri
|
||||
}
|
||||
var lines = createLinesModel(value);
|
||||
var jsonDoc = Parser.parse(value);
|
||||
return symbolProvider.compute(document, lines, jsonDoc);
|
||||
}
|
||||
|
||||
var assertOutline: any = function(actual: SymbolInformation[], expected: any[], message: string) {
|
||||
assert.equal(actual.length, expected.length, message);
|
||||
for (var i = 0; i < expected.length; i++) {
|
||||
assert.equal(actual[i].name, expected[i].label, message);
|
||||
assert.equal(actual[i].kind, expected[i].kind, message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
test('Base types', function(testDone) {
|
||||
var content = '{ "key1": 1, "key2": "foo", "key3" : true }';
|
||||
|
||||
var expected = [
|
||||
{ label: 'key1', kind: SymbolKind.Number },
|
||||
{ label: 'key2', kind: SymbolKind.String },
|
||||
{ label: 'key3', kind: SymbolKind.Boolean },
|
||||
];
|
||||
|
||||
getOutline(content).then((entries: SymbolInformation[]) => {
|
||||
assertOutline(entries, expected);
|
||||
}).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('Arrays', function(testDone) {
|
||||
var content = '{ "key1": 1, "key2": [ 1, 2, 3 ], "key3" : [ { "k1": 1 }, {"k2": 2 } ] }';
|
||||
|
||||
var expected = [
|
||||
{ label: 'key1', kind: SymbolKind.Number },
|
||||
{ label: 'key2', kind: SymbolKind.Array },
|
||||
{ label: 'key3', kind: SymbolKind.Array },
|
||||
{ label: 'k1', kind: SymbolKind.Number },
|
||||
{ label: 'k2', kind: SymbolKind.Number }
|
||||
];
|
||||
|
||||
getOutline(content).then((entries: SymbolInformation[]) => {
|
||||
assertOutline(entries, expected);
|
||||
}).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('Objects', function(testDone) {
|
||||
var content = '{ "key1": { "key2": true }, "key3" : { "k1": { } }';
|
||||
|
||||
var expected = [
|
||||
{ label: 'key1', kind: SymbolKind.Module },
|
||||
{ label: 'key2', kind: SymbolKind.Boolean },
|
||||
{ label: 'key3', kind: SymbolKind.Module },
|
||||
{ label: 'k1', kind: SymbolKind.Module }
|
||||
];
|
||||
|
||||
getOutline(content).then((entries: SymbolInformation[]) => {
|
||||
assertOutline(entries, expected);
|
||||
}).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('Outline - object with syntax error', function(testDone) {
|
||||
var content = '{ "key1": { "key2": true, "key3":, "key4": false } }';
|
||||
|
||||
var expected = [
|
||||
{ label: 'key1', kind: SymbolKind.Module },
|
||||
{ label: 'key2', kind: SymbolKind.Boolean },
|
||||
{ label: 'key4', kind: SymbolKind.Boolean },
|
||||
];
|
||||
|
||||
getOutline(content).then((entries: SymbolInformation[]) => {
|
||||
assertOutline(entries, expected);
|
||||
}).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
});
|
||||
62
extensions/json/server/src/tests/fixtures/Microsoft.Authorization.json
vendored
Normal file
62
extensions/json/server/src/tests/fixtures/Microsoft.Authorization.json
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"id": "http://schema.management.azure.com/schemas/2015-01-01/Microsoft.Authorization.json",
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "Microsoft.Authorization",
|
||||
"description": "Microsoft Microsoft.Authorization Resource Types",
|
||||
"definitions": {
|
||||
"locks": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"Microsoft.Authorization/locks"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2015-01-01"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"maxLength": 64,
|
||||
"description": "Name of the lock"
|
||||
},
|
||||
"dependsOn": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Collection of resources this resource depends on"
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"level": {
|
||||
"enum": [
|
||||
"CannotDelete",
|
||||
"ReadOnly"
|
||||
],
|
||||
"description": "Microsoft.Authorization/locks: level - specifies the type of lock to apply to the scope. CanNotDelete allows modification but prevents deletion, ReadOnly prevents modification or deletion."
|
||||
},
|
||||
"notes": {
|
||||
"type": "string",
|
||||
"maxLength": 512,
|
||||
"description": "Microsoft.Authorization/locks: notes - user defined notes for the lock"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"level"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"type",
|
||||
"apiVersion",
|
||||
"properties"
|
||||
],
|
||||
"description": "Microsoft.Authorization/locks resource"
|
||||
}
|
||||
}
|
||||
}
|
||||
835
extensions/json/server/src/tests/fixtures/Microsoft.Compute.json
vendored
Normal file
835
extensions/json/server/src/tests/fixtures/Microsoft.Compute.json
vendored
Normal file
@@ -0,0 +1,835 @@
|
||||
{
|
||||
"id": "http://schema.management.azure.com/schemas/2015-08-01/Microsoft.Compute.json#",
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "Microsoft.Compute",
|
||||
"description": "Microsoft Compute Resource Types",
|
||||
"resourceDefinitions": {
|
||||
"availabilitySets": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"Microsoft.Compute/availabilitySets"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2015-05-01-preview",
|
||||
"2015-06-15"
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"platformUpdateDomainCount": {
|
||||
"type": "number"
|
||||
},
|
||||
"platformFaultDomainCount": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"apiVersion",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"virtualMachines": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"Microsoft.Compute/virtualMachines"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2015-05-01-preview",
|
||||
"2015-06-15"
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"properties": {
|
||||
"availabilitySet": {
|
||||
"$ref": "#/definitions/id"
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"$ref": "#/definitions/hardwareProfile"
|
||||
},
|
||||
"storageProfile": {
|
||||
"$ref": "#/definitions/storageProfile"
|
||||
},
|
||||
"osProfile": {
|
||||
"$ref": "#/definitions/osProfile"
|
||||
},
|
||||
"networkProfile": {
|
||||
"$ref": "#/definitions/networkProfile"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"hardwareProfile",
|
||||
"storageProfile",
|
||||
"networkProfile"
|
||||
]
|
||||
},
|
||||
"resources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/resourceBase"
|
||||
},
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/Microsoft.Authorization.json#/definitions/locks"
|
||||
},
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/Microsoft.Resources.json#/definitions/links"
|
||||
},
|
||||
{
|
||||
"$ref": "#/resourceDefinitions/extensionsChild"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Microsoft.Compute/virtualMachines: Resource Definition for Virtual Machines."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"apiVersion",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"virtualMachineScaleSets": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"Microsoft.Compute/virtualMachineScaleSets"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2015-05-01-preview",
|
||||
"2015-06-15"
|
||||
]
|
||||
},
|
||||
"sku": {
|
||||
"$ref": "#/definitions/sku"
|
||||
},
|
||||
"properties": {
|
||||
"properties": {
|
||||
"upgradePolicy": {
|
||||
"$ref": "#/definitions/upgradePolicy"
|
||||
},
|
||||
"virtualMachineProfile": {
|
||||
"$ref": "#/definitions/virtualMachineProfile"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"upgradePolicy",
|
||||
"virtualMachineProfile"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sku",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"Microsoft.Compute/virtualMachines/extensions"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2015-05-01-preview",
|
||||
"2015-06-15"
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"properties": {
|
||||
"publisher": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"typeHandlerVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"settings": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"publisher",
|
||||
"type",
|
||||
"typeHandlerVersion",
|
||||
"settings"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"apiVersion",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"extensionsChild": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"extensions"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2015-05-01-preview",
|
||||
"2015-06-15"
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"properties": {
|
||||
"publisher": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"typeHandlerVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"settings": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"publisher",
|
||||
"type",
|
||||
"typeHandlerVersion",
|
||||
"settings"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"apiVersion",
|
||||
"properties"
|
||||
]
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"id": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"networkInterfaces": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"properties": {
|
||||
"properties": {
|
||||
"primary": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"primary"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"hardwareProfile": {
|
||||
"properties": {
|
||||
"vmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"vmSize"
|
||||
]
|
||||
},
|
||||
"imageReference": {
|
||||
"properties": {
|
||||
"publisher": {
|
||||
"type": "string"
|
||||
},
|
||||
"offer": {
|
||||
"type": "string"
|
||||
},
|
||||
"sku": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"default": "latest"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"publisher",
|
||||
"offer",
|
||||
"sku",
|
||||
"version"
|
||||
]
|
||||
},
|
||||
"vhd": {
|
||||
"properties": {
|
||||
"uri": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"uri"
|
||||
]
|
||||
},
|
||||
"osDisk": {
|
||||
"properties": {
|
||||
"osType": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"vhd": {
|
||||
"$ref": "#/definitions/vhd"
|
||||
},
|
||||
"image": {
|
||||
"$ref": "#/definitions/vhd"
|
||||
},
|
||||
"caching": {
|
||||
"type": "string"
|
||||
},
|
||||
"createOption": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"vhd",
|
||||
"createOption"
|
||||
]
|
||||
},
|
||||
"vhdUri": {
|
||||
"properties": {
|
||||
"uri": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"uri"
|
||||
]
|
||||
},
|
||||
"dataDisk": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"diskSizeGB": {
|
||||
"type": "string"
|
||||
},
|
||||
"lun": {
|
||||
"type": "number"
|
||||
},
|
||||
"vhd": {
|
||||
"$ref": "#/definitions/vhdUri"
|
||||
},
|
||||
"caching": {
|
||||
"type": "string"
|
||||
},
|
||||
"createOption": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"lun",
|
||||
"vhd",
|
||||
"createOption"
|
||||
]
|
||||
},
|
||||
"storageProfile": {
|
||||
"properties": {
|
||||
"imageReference": {
|
||||
"$ref": "#/definitions/imageReference"
|
||||
},
|
||||
"osDisk": {
|
||||
"$ref": "#/definitions/osDisk"
|
||||
},
|
||||
"dataDisks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/dataDisk"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"osDisk"
|
||||
]
|
||||
},
|
||||
"winRMListener": {
|
||||
"properties": {
|
||||
"protocol": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"http",
|
||||
"https"
|
||||
]
|
||||
},
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression"
|
||||
}
|
||||
]
|
||||
},
|
||||
"certificateUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"protocol",
|
||||
"certificateUrl"
|
||||
]
|
||||
},
|
||||
"winRM": {
|
||||
"properties": {
|
||||
"listeners": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/winRMListener"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"listeners"
|
||||
]
|
||||
},
|
||||
"additionalUnattendContent": {
|
||||
"properties": {
|
||||
"pass": {
|
||||
"type": "string"
|
||||
},
|
||||
"component": {
|
||||
"type": "string"
|
||||
},
|
||||
"settingName": {
|
||||
"type": "string"
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"pass",
|
||||
"component",
|
||||
"settingName",
|
||||
"content"
|
||||
]
|
||||
},
|
||||
"windowsConfiguration": {
|
||||
"properties": {
|
||||
"provisionVMAgent": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"winRM": {
|
||||
"$ref": "#/definitions/winRM"
|
||||
},
|
||||
"additionalUnattendContent": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/additionalUnattendContent"
|
||||
}
|
||||
},
|
||||
"enableAutomaticUpdates": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"publicKey": {
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"keyData": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ssh": {
|
||||
"properties": {
|
||||
"publicKeys": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/publicKey"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"linuxConfiguration": {
|
||||
"properties": {
|
||||
"disablePasswordAuthentication": {
|
||||
"type": "string"
|
||||
},
|
||||
"ssh": {
|
||||
"$ref": "#/definitions/ssh"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"vaultCertificateUrl": {
|
||||
"properties": {
|
||||
"certificateUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"certificateUrl"
|
||||
]
|
||||
},
|
||||
"secret": {
|
||||
"properties": {
|
||||
"sourceVault": {
|
||||
"$ref": "#/definitions/id"
|
||||
},
|
||||
"vaultCertificates": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/vaultCertificateUrl"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"sourceVault",
|
||||
"vaultCertificates"
|
||||
]
|
||||
},
|
||||
"osProfile": {
|
||||
"properties": {
|
||||
"computerName": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"customData": {
|
||||
"type": "string"
|
||||
},
|
||||
"windowsConfiguration": {
|
||||
"$ref": "#/definitions/windowsConfiguration"
|
||||
},
|
||||
"linuxConfiguration": {
|
||||
"$ref": "#/definitions/linuxConfiguration"
|
||||
},
|
||||
"secrets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/secret"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"computerName",
|
||||
"adminUsername",
|
||||
"adminPassword"
|
||||
]
|
||||
},
|
||||
"networkProfile": {
|
||||
"properties": {
|
||||
"networkInterfaces": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/networkInterfaces"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"networkInterfaces"
|
||||
]
|
||||
},
|
||||
"sku": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"tier": {
|
||||
"type": "string"
|
||||
},
|
||||
"capacity": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"capacity"
|
||||
]
|
||||
},
|
||||
"upgradePolicy": {
|
||||
"properties": {
|
||||
"mode": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"mode"
|
||||
]
|
||||
},
|
||||
"virtualMachineScaleSetOsProfile": {
|
||||
"properties": {
|
||||
"computerNamePrefix": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminUsername": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminPassword": {
|
||||
"type": "string"
|
||||
},
|
||||
"customData": {
|
||||
"type": "string"
|
||||
},
|
||||
"windowsConfiguration": {
|
||||
"$ref": "#/definitions/windowsConfiguration"
|
||||
},
|
||||
"linuxConfiguration": {
|
||||
"$ref": "#/definitions/linuxConfiguration"
|
||||
},
|
||||
"secrets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/secret"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"computerNamePrefix",
|
||||
"adminUsername",
|
||||
"adminPassword"
|
||||
]
|
||||
},
|
||||
"virtualMachineScaleSetOSDisk": {
|
||||
"properties": {
|
||||
"osType": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"vhdContainers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"caching": {
|
||||
"type": "string"
|
||||
},
|
||||
"createOption": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"createOption"
|
||||
]
|
||||
},
|
||||
"virtualMachineScaleSetStorageProfile": {
|
||||
"properties": {
|
||||
"imageReference": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/definitions/imageReference"
|
||||
},
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression"
|
||||
}
|
||||
]
|
||||
},
|
||||
"osDisk": {
|
||||
"$ref": "#/definitions/virtualMachineScaleSetOSDisk"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"osDisk"
|
||||
]
|
||||
},
|
||||
"virtualMachineScaleSetExtension": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"properties": {
|
||||
"properties": {
|
||||
"publisher": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"typeHandlerVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"settings": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"publisher",
|
||||
"type",
|
||||
"typeHandlerVersion"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"virtualMachineScaleSetExtensionProfile": {
|
||||
"properties": {
|
||||
"extensions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/virtualMachineScaleSetExtension"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ipConfiguration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"subnet": {
|
||||
"$ref": "#/definitions/id"
|
||||
},
|
||||
"loadBalancerBackendAddressPools": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/id"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"networkInterfaceConfiguration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"primary": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"ipConfigurations": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ipConfiguration"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"primary",
|
||||
"ipConfigurations"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"virtualMachineScaleSetNetworkProfile": {
|
||||
"properties": {
|
||||
"networkInterfaceConfigurations": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/networkInterfaceConfiguration"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"networkInterfaceConfigurations"
|
||||
]
|
||||
},
|
||||
"virtualMachineProfile": {
|
||||
"properties": {
|
||||
"osProfile": {
|
||||
"$ref": "#/definitions/virtualMachineScaleSetOsProfile"
|
||||
},
|
||||
"storageProfile": {
|
||||
"$ref": "#/definitions/virtualMachineScaleSetStorageProfile"
|
||||
},
|
||||
"extensionProfile": {
|
||||
"$ref": "#/definitions/virtualMachineScaleSetExtensionProfile"
|
||||
},
|
||||
"networkProfile": {
|
||||
"$ref": "#/definitions/virtualMachineScaleSetNetworkProfile"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"osProfile",
|
||||
"storageProfile",
|
||||
"networkProfile"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
179
extensions/json/server/src/tests/fixtures/Microsoft.Resources.json
vendored
Normal file
179
extensions/json/server/src/tests/fixtures/Microsoft.Resources.json
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
{
|
||||
"id": "http://schema.management.azure.com/schemas/2015-01-01/Microsoft.Resources.json",
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "Microsoft.Resources",
|
||||
"description": "Microsoft Resources Resource Types",
|
||||
"definitions": {
|
||||
"deployments": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"Microsoft.Resources/deployments"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2015-01-01"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the deployment"
|
||||
},
|
||||
"dependsOn": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Collection of resources this deployment depends on"
|
||||
},
|
||||
"properties": {
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mode": {
|
||||
"enum": [ "Incremental" ],
|
||||
"description": "Deployment mode"
|
||||
}
|
||||
},
|
||||
"required": [ "mode" ]
|
||||
},
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"templateLink": {
|
||||
"$ref": "#/definitions/templateLink"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"template": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"parametersLink": {
|
||||
"$ref": "#/definitions/parametersLink"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#/definitions/parameter"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"apiVersion",
|
||||
"name",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"templateLink": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uri": {
|
||||
"type": "string",
|
||||
"description": "URI referencing the deployment template"
|
||||
},
|
||||
"contentVersion": {
|
||||
"type": "string",
|
||||
"pattern": "(^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)",
|
||||
"description": "If included it must match the contentVersion in the template"
|
||||
}
|
||||
},
|
||||
"required": [ "uri" ],
|
||||
"description": "Template file reference in a deployment"
|
||||
},
|
||||
"parametersLink": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uri": {
|
||||
"type": "string",
|
||||
"description": "URI referencing the deployment template parameters"
|
||||
},
|
||||
"contentVersion": {
|
||||
"type": "string",
|
||||
"pattern": "(^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)",
|
||||
"description": "If included it must match the contentVersion in the parameters file"
|
||||
}
|
||||
},
|
||||
"required": [ "uri" ],
|
||||
"description": "Parameter file reference in a deployment"
|
||||
},
|
||||
"links": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"Microsoft.Resources/links"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2015-01-01"
|
||||
]
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"maxLength": 64,
|
||||
"description": "Name of the link"
|
||||
},
|
||||
"dependsOn": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Collection of resources this link depends on"
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"targetId": {
|
||||
"type": "string",
|
||||
"description": "Target resource id to link to"
|
||||
},
|
||||
"notes": {
|
||||
"type": "string",
|
||||
"maxLength": 512,
|
||||
"description": "Notes for this link"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"targetId"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"apiVersion",
|
||||
"name",
|
||||
"properties"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
376
extensions/json/server/src/tests/fixtures/Microsoft.Sql.json
vendored
Normal file
376
extensions/json/server/src/tests/fixtures/Microsoft.Sql.json
vendored
Normal file
@@ -0,0 +1,376 @@
|
||||
{
|
||||
"id": "http://schema.management.azure.com/schemas/2014-04-01-preview/Microsoft.Sql.json#",
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "Microsoft.SQLDatabase",
|
||||
"description": "Microsoft SQL Database Resource Types",
|
||||
"definitions": {
|
||||
"servers": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"Microsoft.Sql/servers"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2014-04-01-preview"
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"version": {
|
||||
"enum": [
|
||||
"2.0",
|
||||
"12.0"
|
||||
],
|
||||
"description": "Microsoft.Sql/server: Azure SQL DB server version"
|
||||
},
|
||||
"administratorLogin": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Sql/server: administrator login name"
|
||||
},
|
||||
"administratorLoginPassword": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Sql/server: administrator login password"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"administratorLogin",
|
||||
"administratorLoginPassword"
|
||||
]
|
||||
},
|
||||
"resources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/resourceBase"
|
||||
},
|
||||
{
|
||||
"oneOf": [
|
||||
{ "$ref": "http://schema.management.azure.com/schemas/2015-01-01/Microsoft.Authorization.json#/definitions/locks" },
|
||||
{ "$ref": "http://schema.management.azure.com/schemas/2015-01-01/Microsoft.Resources.json#/definitions/links" },
|
||||
{ "$ref": "#/definitions/databasesChild" },
|
||||
{ "$ref": "#/definitions/firewallrulesChild" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Microsoft.Sql/servers: Child resources to define databases and firewall rules."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"apiVersion",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"databasesBaseCommon": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"edition": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"Web",
|
||||
"Business",
|
||||
"Basic",
|
||||
"Standard",
|
||||
"Premium"
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Microsoft.Sql/server/databases: Optional. Edition of the database to be created. If omitted, the default is Web on server version 2.0 or Standard on server version 12.0."
|
||||
},
|
||||
"collation": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"SQL_Latin1_General_Cp437_CS_AS_KI_WI",
|
||||
"SQL_Latin1_General_Cp437_CI_AS_KI_WI",
|
||||
"SQL_Latin1_General_Pref_Cp437_CI_AS_KI_WI",
|
||||
"SQL_Latin1_General_Cp437_CI_AI_KI_WI",
|
||||
"SQL_Latin1_General_Cp437_BIN",
|
||||
"SQL_Latin1_General_Cp850_BIN",
|
||||
"SQL_Latin1_General_Cp850_CS_AS_KI_WI",
|
||||
"SQL_Latin1_General_Cp850_CI_AS_KI_WI",
|
||||
"SQL_Latin1_General_Cp850_CI_AI_KI_WI",
|
||||
"SQL_Latin1_General_Pref_Cp850_CI_AS_KI_WI",
|
||||
"SQL_1xCompat_Cp850_CI_AS_KI_WI",
|
||||
"SQL_Latin1_General_Cp1_CS_AS_KI_WI",
|
||||
"SQL_Latin1_General_Cp1_CI_AS_KI_WI",
|
||||
"SQL_Latin1_General_Pref_Cp1_CI_AS_KI_WI",
|
||||
"SQL_Latin1_General_Cp1_CI_AI_KI_WI",
|
||||
"SQL_AltDiction_Cp850_CS_AS_KI_WI",
|
||||
"SQL_AltDiction_Pref_Cp850_CI_AS_KI_WI",
|
||||
"SQL_AltDiction_Cp850_CI_AI_KI_WI",
|
||||
"SQL_Scandainavian_Pref_Cp850_CI_AS_KI_WI",
|
||||
"SQL_Scandainavian_Cp850_CS_AS_KI_WI",
|
||||
"SQL_Scandainavian_Cp850_CI_AS_KI_WI",
|
||||
"SQL_AltDiction_Cp850_CI_AS_KI_WI",
|
||||
"SQL_Latin1_General_1250_BIN",
|
||||
"SQL_Latin1_General_Cp1250_CS_AS_KI_WI",
|
||||
"SQL_Latin1_General_Cp1250_CI_AS_KI_WI",
|
||||
"SQL_Czech_Cp1250_CS_AS_KI_WI",
|
||||
"SQL_Czech_Cp1250_CI_AS_KI_WI",
|
||||
"SQL_Hungarian_Cp1250_CS_AS_KI_WI",
|
||||
"SQL_Hungarian_Cp1250_CI_AS_KI_WI",
|
||||
"SQL_Polish_Cp1250_CS_AS_KI_WI",
|
||||
"SQL_Polish_Cp1250_CI_AS_KI_WI",
|
||||
"SQL_Romanian_Cp1250_CS_AS_KI_WI",
|
||||
"SQL_Romanian_Cp1250_CI_AS_KI_WI",
|
||||
"SQL_Croatian_Cp1250_CS_AS_KI_WI",
|
||||
"SQL_Croatian_Cp1250_CI_AS_KI_WI",
|
||||
"SQL_Slovak_Cp1250_CS_AS_KI_WI",
|
||||
"SQL_Slovak_Cp1250_CI_AS_KI_WI",
|
||||
"SQL_Slovenian_Cp1250_CS_AS_KI_WI",
|
||||
"SQL_Slovenian_Cp1250_CI_AS_KI_WI",
|
||||
"SQL_Latin1_General_1251_BIN",
|
||||
"SQL_Latin1_General_Cp1251_CS_AS_KI_WI",
|
||||
"SQL_Latin1_General_Cp1251_CI_AS_KI_WI",
|
||||
"SQL_Ukrainian_Cp1251_CS_AS_KI_WI",
|
||||
"SQL_Ukrainian_Cp1251_CI_AS_KI_WI",
|
||||
"SQL_Latin1_General_1253_BIN",
|
||||
"SQL_Latin1_General_Cp1253_CS_AS_KI_WI",
|
||||
"SQL_Latin1_General_Cp1253_CI_AS_KI_WI",
|
||||
"SQL_Latin1_General_Cp1253_CI_AI_KI_WI",
|
||||
"SQL_Latin1_General_1254_BIN",
|
||||
"SQL_Latin1_General_Cp1254_CS_AS_KI_WI",
|
||||
"SQL_Latin1_General_Cp1254_CI_AS_KI_WI",
|
||||
"SQL_Latin1_General_1255_BIN",
|
||||
"SQL_Latin1_General_Cp1255_CS_AS_KI_WI",
|
||||
"SQL_Latin1_General_Cp1255_CI_AS_KI_WI",
|
||||
"SQL_Latin1_General_1256_BIN",
|
||||
"SQL_Latin1_General_Cp1256_CS_AS_KI_WI",
|
||||
"SQL_Latin1_General_Cp1256_CI_AS_KI_WI",
|
||||
"SQL_Latin1_General_1257_BIN",
|
||||
"SQL_Latin1_General_Cp1257_CS_AS_KI_WI",
|
||||
"SQL_Latin1_General_Cp1257_CI_AS_KI_WI",
|
||||
"SQL_Estonian_Cp1257_CS_AS_KI_WI",
|
||||
"SQL_Estonian_Cp1257_CI_AS_KI_WI",
|
||||
"SQL_Latvian_Cp1257_CS_AS_KI_WI",
|
||||
"SQL_Latvian_Cp1257_CI_AS_KI_WI",
|
||||
"SQL_Lithuanian_Cp1257_CS_AS_KI_WI",
|
||||
"SQL_Lithuanian_Cp1257_CI_AS_KI_WI",
|
||||
"SQL_Danish_Pref_Cp1_CI_AS_KI_WI",
|
||||
"SQL_SwedishPhone_Pref_Cp1_CI_AS_KI_WI",
|
||||
"SQL_SwedishStd_Pref_Cp1_CI_AS_KI_WI",
|
||||
"SQL_Icelandic_Pref_Cp1_CI_AS_KI_WI"
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Microsoft.Sql/server/databases: Database collation"
|
||||
},
|
||||
"maxSizeBytes": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"104857600",
|
||||
"524288000",
|
||||
"1073741824",
|
||||
"2147483648",
|
||||
"5368709120",
|
||||
"10737418240",
|
||||
"21474836480",
|
||||
"32212254720",
|
||||
"42949672960",
|
||||
"53687091200",
|
||||
"107374182400",
|
||||
"161061273600",
|
||||
"214748364800",
|
||||
"268435456000",
|
||||
"322122547200",
|
||||
"429496729600",
|
||||
"536870912000"
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Microsoft.Sql/server/databases: Sets the maximum size, in bytes, for the database. This value must be within the range of allowed values for Edition."
|
||||
},
|
||||
"requestedServiceObjectiveId": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"910B4FCB-8A29-4C3E-958F-F7BA794388B2",
|
||||
"DD6D99BB-F193-4EC1-86F2-43D3BCCBC49C",
|
||||
"F1173C43-91BD-4AAA-973C-54E79E15235B",
|
||||
"1B1EBD4D-D903-4BAA-97F9-4EA675F5E928",
|
||||
"455330E1-00CD-488B-B5FA-177C226F28B7",
|
||||
"789681B8-CA10-4EB0-BDF2-E0B050601B40",
|
||||
"7203483A-C4FB-4304-9E9F-17C71C904F5D",
|
||||
"A7D1B92D-C987-4375-B54D-2B1D0E0F5BB0",
|
||||
"A7C4C615-CFB1-464B-B252-925BE0A19446"
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Microsoft.Sql/server/databases: The GUID corresponding to the performance level for Edition. Shared = 910B4FCB-8A29-4C3E-958F-F7BA794388B2, Basic = DD6D99BB-F193-4EC1-86F2-43D3BCCBC49C, S0 = F1173C43-91BD-4AAA-973C-54E79E15235B, S1 = 1B1EBD4D-D903-4BAA-97F9-4EA675F5E928, S2 = 455330E1-00CD-488B-B5FA-177C226F28B7, S3 = 789681B8-CA10-4EB0-BDF2-E0B050601B40, P1 = 7203483A-C4FB-4304-9E9F-17C71C904F5D, P2 = A7D1B92D-C987-4375-B54D-2B1D0E0F5BB0, P3 = A7C4C615-CFB1-464B-B252-925BE0A19446"
|
||||
}
|
||||
}
|
||||
},
|
||||
"databasesBaseAll": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createMode": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"Copy",
|
||||
"OnlineSecondary",
|
||||
"OfflineSecondary",
|
||||
"Recovery",
|
||||
"PointInTimeRestore",
|
||||
"Restore"
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Microsoft.Sql/server/databases: Defines that databases is created as a Point-In-Time restoration of another database."
|
||||
},
|
||||
"sourceDatabaseId": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Sql/server/databases: The URI of the source database."
|
||||
},
|
||||
"restorePointInTime": {
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/UTC",
|
||||
"description": "Microsoft.Sql/server/databases: The point in time for the restore."
|
||||
},
|
||||
"sourceDatabaseDeletionDate": {
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/UTC",
|
||||
"description": "Microsoft.Sql/server/databases: The deletion date time of the source database."
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
"databasesBase": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/databasesBaseCommon"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/databasesBaseAll"
|
||||
}
|
||||
]
|
||||
},
|
||||
"databasesChild": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"databases"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2014-04-01-preview"
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"$ref": "#/definitions/databasesBase"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"apiVersion",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"databases": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"Microsoft.Sql/servers/databases"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2014-04-01-preview"
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"$ref": "#/definitions/databasesBase"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"apiVersion",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"firewallrulesBase": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"endIpAddress": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Sql/server/firewallrules: ending IP address"
|
||||
},
|
||||
"startIpAddress": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Sql/server/firewallrules: starting IP address"
|
||||
}
|
||||
}
|
||||
},
|
||||
"firewallrulesChild": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"firewallrules"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2014-04-01-preview"
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"$ref": "#/definitions/firewallrulesBase"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"apiVersion",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"firewallrules": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"Microsoft.Sql/servers/firewallrules"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2014-04-01-preview"
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"$ref": "#/definitions/firewallrulesBase"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"apiVersion",
|
||||
"properties"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
332
extensions/json/server/src/tests/fixtures/Microsoft.Web.json
vendored
Normal file
332
extensions/json/server/src/tests/fixtures/Microsoft.Web.json
vendored
Normal file
@@ -0,0 +1,332 @@
|
||||
{
|
||||
"id": "http://schema.management.azure.com/schemas/2014-06-01/Microsoft.Web.json",
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "Microsoft.Web",
|
||||
"description": "Microsoft Web Resource Types",
|
||||
"definitions": {
|
||||
"serverfarms": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"Microsoft.Web/serverfarms"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2014-06-01"
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Web/serverfarms: Name of the server farm."
|
||||
},
|
||||
"sku": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"Free",
|
||||
"Shared",
|
||||
"Basic",
|
||||
"Standard"
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Microsoft.Web/serverfarms: Server farm sku."
|
||||
},
|
||||
"workerSize": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"Small",
|
||||
"Medium",
|
||||
"Large"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 2
|
||||
}
|
||||
],
|
||||
"description": "Microsoft.Web/serverfarms: The instance size."
|
||||
},
|
||||
"numberOfWorkers": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 10
|
||||
}
|
||||
],
|
||||
"description": "Microsoft.Web/serverfarms: The instance count, which is the number of virtual machines dedicated to the farm. Supported values are 1-10."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"apiVersion",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"Microsoft.Web/sites/config",
|
||||
"config"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2014-06-01"
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"connectionStrings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ConnectionString": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Web/sites/config: connection string"
|
||||
},
|
||||
"Name": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Web/sites/config: connection string name"
|
||||
},
|
||||
"Type": {
|
||||
"type": "integer",
|
||||
"description": "Microsoft.Web/sites/config: connection string type"
|
||||
}
|
||||
}
|
||||
},
|
||||
"uniqueItems": true,
|
||||
"description": "Microsoft.Web/sites/config: Connection strings for database and other external resources."
|
||||
},
|
||||
"phpVersion": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Web/sites/config: PHP version (an empty string disables PHP)."
|
||||
},
|
||||
"netFrameworkVersion": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Web/sites/config: The .Net Framework version."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Microsoft.Web/sites: Configuration settings for a web site.",
|
||||
"required": [
|
||||
"type",
|
||||
"apiVersion",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"Microsoft.Web/sites/extensions",
|
||||
"extensions"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2014-06-01"
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"packageUri": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Web/sites/extensions: uri of package"
|
||||
},
|
||||
"dbType": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Web/sites/extensions: type of database"
|
||||
},
|
||||
"connectionString": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Web/sites/extensions: connection string"
|
||||
},
|
||||
"setParameters": {
|
||||
"type": "object",
|
||||
"description": "Microsoft.Web/sites/extensions: parameters"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"apiVersion",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"sites": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"Microsoft.Web/sites"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2014-06-01"
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Web/sites: The name of web site."
|
||||
},
|
||||
"serverFarm": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Web/sites: The name of server farm site belongs to."
|
||||
},
|
||||
"hostnames": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Microsoft.Web/sites: An array of strings that contains the public hostnames for the site, including custom domains."
|
||||
},
|
||||
"enabledHostnames": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Microsoft.Web/sites: An array of strings that contains enabled hostnames for the site. By default, these are <SiteName>.azurewebsites.net and <SiteName>.scm.azurewebsites.net."
|
||||
},
|
||||
"hostNameSslStates": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Web/sites/hostNameSslStates: The URL of the web site."
|
||||
},
|
||||
"sslState": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"Disabled",
|
||||
"IpBasedEnabled",
|
||||
"SniEnabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 2
|
||||
},
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression"
|
||||
}
|
||||
],
|
||||
"description": "Microsoft.Web/sites/hostNameSslStates. The SSL state."
|
||||
},
|
||||
"thumbprint": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Web/sites/hostNameSslStates: A string that contains the thumbprint of the SSL certificate."
|
||||
},
|
||||
"ipBasedSslState": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"Disabled",
|
||||
"IpBasedEnabled",
|
||||
"SniEnabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 2
|
||||
},
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression"
|
||||
}
|
||||
],
|
||||
"description": "Microsoft.Web/sites/hostNameSslStates: IP Based SSL state"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Microsoft.Web/sites: Container for SSL states."
|
||||
}
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{ "$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/resourceBase" },
|
||||
{
|
||||
"oneOf": [
|
||||
{"$ref": "#/definitions/config"},
|
||||
{"$ref": "#/definitions/extensions"}
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Microsoft.Web/sites: Child resources to define configuration and extensions."
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"apiVersion",
|
||||
"properties"
|
||||
]
|
||||
},
|
||||
"certificates": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"Microsoft.Web/certificates"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2014-06-01"
|
||||
]
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pfxBlob": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Web/certificates: A base64Binary value that contains the PfxBlob of the certificate."
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"description": "Microsoft.Web/certficates: A string that contains the password for the certificate."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
51
extensions/json/server/src/tests/fixtures/SuccessBricks.ClearDB.json
vendored
Normal file
51
extensions/json/server/src/tests/fixtures/SuccessBricks.ClearDB.json
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"id": "http://schema.management.azure.com/schemas/2014-04-01/SuccessBricks.ClearDB.json",
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "SuccessBricks.ClearDB",
|
||||
"description": "SuccessBricks ClearDB Resource Types",
|
||||
"definitions": {
|
||||
"databases": {
|
||||
"type":"object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"SuccessBricks.ClearDB/databases"
|
||||
]
|
||||
},
|
||||
"apiVersion": {
|
||||
"enum": [
|
||||
"2014-04-01"
|
||||
]
|
||||
},
|
||||
"plan": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/expression"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"Free",
|
||||
"Jupiter",
|
||||
"Saturn",
|
||||
"Venus"
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Name of the plan"
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"description": "ClearDB database plan"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"apiVersion",
|
||||
"plan"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
47
extensions/json/server/src/tests/fixtures/deploymentParameters.json
vendored
Normal file
47
extensions/json/server/src/tests/fixtures/deploymentParameters.json
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"id": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters#",
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "Parameters",
|
||||
"description": "An Azure deployment parameter file",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"contentVersion": {
|
||||
"type": "string",
|
||||
"pattern": "(^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)",
|
||||
"description": "A 4 number format for the version number of this parameter file. For example, 1.0.0.0"
|
||||
},
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/parameter"
|
||||
},
|
||||
"description": "Collection of parameters to pass into a template"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"$schema",
|
||||
"contentVersion",
|
||||
"parameters"
|
||||
],
|
||||
"definitions": {
|
||||
"parameter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#/definitions/parameterValueTypes",
|
||||
"description": "Input value to template"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"description": "Client specific metadata"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"value"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
382
extensions/json/server/src/tests/fixtures/deploymentTemplate.json
vendored
Normal file
382
extensions/json/server/src/tests/fixtures/deploymentTemplate.json
vendored
Normal file
@@ -0,0 +1,382 @@
|
||||
{
|
||||
"id": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "Template",
|
||||
"description": "An Azure deployment template",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string",
|
||||
"description": "JSON schema reference"
|
||||
},
|
||||
"contentVersion": {
|
||||
"type": "string",
|
||||
"pattern": "(^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$)",
|
||||
"description": "A 4 number format for the version number of this template file. For example, 1.0.0.0"
|
||||
},
|
||||
"variables": {
|
||||
"type": "object",
|
||||
"description": "Variable definitions"
|
||||
},
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"description": "Input parameter definitions",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/parameter"
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"type": "array",
|
||||
"description": "Collection of resources to be deployed",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/resourceBase"
|
||||
},
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2014-06-01/Microsoft.Web.json#/definitions/certificates"
|
||||
},
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2014-06-01/Microsoft.Web.json#/definitions/serverfarms"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/resourceBaseForParentResources"
|
||||
},
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2014-06-01/Microsoft.Web.json#/definitions/sites"
|
||||
},
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2014-04-01-preview/Microsoft.Sql.json#/definitions/servers"
|
||||
},
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-08-01/Microsoft.Compute.json#/resourceDefinitions/virtualMachines"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/resourceBaseExternal"
|
||||
},
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2014-04-01/SuccessBricks.ClearDB.json#/definitions/databases"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ARMResourceBase"
|
||||
},
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/Microsoft.Resources.json#/definitions/deployments"
|
||||
},
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/Microsoft.Resources.json#/definitions/links"
|
||||
},
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/Microsoft.Authorization.json#/definitions/locks"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"minItems": 1
|
||||
},
|
||||
"outputs": {
|
||||
"type": "object",
|
||||
"description": "Output parameter definitions",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/output"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"$schema",
|
||||
"contentVersion",
|
||||
"resources"
|
||||
],
|
||||
"definitions": {
|
||||
"resourceBase": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/resourceBaseForParentResources"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"resources": {
|
||||
"$ref": "#/definitions/childResources"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ARMResourceBase": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the resource"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Resource type"
|
||||
},
|
||||
"apiVersion": {
|
||||
"type": "string",
|
||||
"description": "API Version of the resource type"
|
||||
},
|
||||
"dependsOn": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Collection of resources this resource depends on"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"type",
|
||||
"apiVersion"
|
||||
]
|
||||
},
|
||||
"proxyResourceBase": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ARMResourceBase"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"resources": {
|
||||
"$ref": "#/definitions/childResources"
|
||||
},
|
||||
"location": {
|
||||
"$ref": "#/definitions/resourceLocations",
|
||||
"description": "Location to deploy resource to"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"resourceBaseForParentResources": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ARMResourceBase"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"maxLength": 64,
|
||||
"pattern": "(^[a-zA-Z0-9_.()-]+$)",
|
||||
"description": "Kind of resource"
|
||||
},
|
||||
"location": {
|
||||
"$ref": "#/definitions/resourceLocations",
|
||||
"description": "Location to deploy resource to"
|
||||
},
|
||||
"tags": {
|
||||
"type": "object",
|
||||
"description": "Name-value pairs to add to the resource"
|
||||
},
|
||||
"plan": {
|
||||
"$ref": "#/definitions/resourcePlan"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"location"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"resourceBaseExternal": {
|
||||
"$ref": "#/definitions/resourceBase",
|
||||
"required": [
|
||||
"plan"
|
||||
]
|
||||
},
|
||||
"childResources": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ARMChildResources"
|
||||
}
|
||||
},
|
||||
"ARMChildResources": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/Microsoft.Authorization.json#/definitions/locks"
|
||||
},
|
||||
{
|
||||
"$ref": "http://schema.management.azure.com/schemas/2015-01-01/Microsoft.Resources.json#/definitions/links"
|
||||
}
|
||||
]
|
||||
},
|
||||
"resourcePlan": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the plan"
|
||||
},
|
||||
"promotionCode": {
|
||||
"type": "string",
|
||||
"description": "Plan promotion code"
|
||||
},
|
||||
"publisher": {
|
||||
"type": "string",
|
||||
"description": "Name of the publisher"
|
||||
},
|
||||
"product": {
|
||||
"type": "string",
|
||||
"description": "Name of the product"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"description": "Plan of the resource"
|
||||
},
|
||||
"resourceLocations": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"East Asia",
|
||||
"Southeast Asia",
|
||||
"Central US",
|
||||
"East US",
|
||||
"East US 2",
|
||||
"West US",
|
||||
"North Central US",
|
||||
"South Central US",
|
||||
"North Europe",
|
||||
"West Europe",
|
||||
"Japan West",
|
||||
"Japan East",
|
||||
"Brazil South",
|
||||
"Australia East",
|
||||
"Australia Southeast",
|
||||
"Central India",
|
||||
"West India",
|
||||
"South India"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"parameter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"$ref": "#/definitions/parameterTypes",
|
||||
"description": "Type of input parameter"
|
||||
},
|
||||
"defaultValue": {
|
||||
"$ref": "#/definitions/parameterValueTypes",
|
||||
"description": "Default value to be used if one is not provided"
|
||||
},
|
||||
"allowedValues": {
|
||||
"type": "array",
|
||||
"description": "Value can only be one of these values"
|
||||
},
|
||||
"minLength": {
|
||||
"type": "integer",
|
||||
"description": "Minimum number of characters that must be used"
|
||||
},
|
||||
"maxLength": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of characters that can be used"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"description": "Metadata for the parameter"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"description": "Input parameter definitions"
|
||||
},
|
||||
"output": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"$ref": "#/definitions/parameterTypes",
|
||||
"description": "Type of output value"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/definitions/parameterValueTypes",
|
||||
"description": "Value assigned for output"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"description": "Set of output parameters"
|
||||
},
|
||||
"parameterTypes": {
|
||||
"enum": [
|
||||
"string",
|
||||
"securestring",
|
||||
"int",
|
||||
"bool",
|
||||
"object",
|
||||
"array"
|
||||
]
|
||||
},
|
||||
"parameterValueTypes": {
|
||||
"type": [
|
||||
"string",
|
||||
"boolean",
|
||||
"integer",
|
||||
"number",
|
||||
"object",
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"expression": {
|
||||
"type": "string",
|
||||
"pattern": "^\\[(concat|parameters|variables|reference|resourceId|resourceGroup|subscription|listKeys|listPackage|base64|providers|copyIndex|padLeft)\\(.*\\).*\\]$",
|
||||
"description": "Deployment template expression. Expressions are enclosed in [] and must start with a function of concat(), parameters(), variables(), reference(), resourceId(), resourceGroup(), subscription(), listKeys(), listPackage(), base64(), providers(), copyIndex(), padLeft()"
|
||||
},
|
||||
"Iso8601Duration": {
|
||||
"type": "string",
|
||||
"pattern": "^P(\\d+Y)?(\\d+M)?(\\d+D)?(T(((\\d+H)(\\d+M)?(\\d+(\\.\\d{1,2})?S)?)|((\\d+M)(\\d+(\\.\\d{1,2})?S)?)|((\\d+(\\.\\d{1,2})?S))))?$"
|
||||
},
|
||||
"UTC": {
|
||||
"type": "string",
|
||||
"pattern": "^\\d{4}(-(0[1-9]|1[0-2])(-([012]\\d|3[01])(T((([01]\\d|2[0123]):[0-5]\\d)|(24:00))(:(([0-5]\\d)|60)(\\.\\d{1,}){0,1}){0,1}){0,1}((Z)|([+-]((([01]\\d|2[0123]):[0-5]\\d)|(24:00)))){0,1}){0,1}){0,1}$"
|
||||
},
|
||||
"apiVersion": {
|
||||
"type": "string",
|
||||
"pattern": "(^((\\d\\d\\d\\d-\\d\\d-\\d\\d)|([0-9]+(\\.[0-9]+)?))(-[a-zA-Z][a-zA-Z0-9]*)?$)",
|
||||
"description": "API version of the resource type"
|
||||
}
|
||||
}
|
||||
}
|
||||
437
extensions/json/server/src/tests/formatter.test.ts
Normal file
437
extensions/json/server/src/tests/formatter.test.ts
Normal file
@@ -0,0 +1,437 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Json = require('../json-toolbox/json');
|
||||
import {
|
||||
ITextDocument, DocumentFormattingParams, Range, Position, FormattingOptions, TextEdit
|
||||
} from 'vscode-languageserver';
|
||||
import {LinesModel, create as createLinesModel} from '../utils/lines';
|
||||
import Formatter = require('../jsonFormatter');
|
||||
import assert = require('assert');
|
||||
|
||||
suite('JSON Formatter', () => {
|
||||
|
||||
function format(unformatted: string, expected: string, insertSpaces = true) {
|
||||
let range: Range = null;
|
||||
let uri = 'test://test.json';
|
||||
|
||||
|
||||
let lines = createLinesModel(unformatted);
|
||||
|
||||
let rangeStart = unformatted.indexOf('|');
|
||||
let rangeEnd = unformatted.lastIndexOf('|');
|
||||
if (rangeStart !== -1 && rangeEnd !== -1) {
|
||||
// remove '|'
|
||||
unformatted = unformatted.substring(0, rangeStart) + unformatted.substring(rangeStart + 1, rangeEnd) + unformatted.substring(rangeEnd + 1);
|
||||
let startPos = lines.positionAt(rangeStart);
|
||||
let endPos = lines.positionAt(rangeEnd);
|
||||
range = Range.create(startPos, endPos);
|
||||
|
||||
lines = createLinesModel(unformatted);
|
||||
}
|
||||
|
||||
let document = {
|
||||
getText: () => unformatted,
|
||||
uri: uri
|
||||
}
|
||||
let edits = Formatter.format(document, lines, range, { tabSize: 2, insertSpaces: insertSpaces });
|
||||
|
||||
let formatted = unformatted;
|
||||
let sortedEdits = edits.sort((a, b) => lines.offsetAt(b.range.start) - lines.offsetAt(a.range.start));
|
||||
let lastOffset = formatted.length;
|
||||
sortedEdits.forEach(e => {
|
||||
let startOffset = lines.offsetAt(e.range.start);
|
||||
let endOffset = lines.offsetAt(e.range.end);
|
||||
assert.ok(startOffset <= endOffset);
|
||||
assert.ok(endOffset <= lastOffset);
|
||||
formatted = formatted.substring(0, startOffset) + e.newText + formatted.substring(endOffset, formatted.length);
|
||||
lastOffset = startOffset;
|
||||
})
|
||||
assert.equal(formatted, expected);
|
||||
}
|
||||
|
||||
test('object - single property', () => {
|
||||
var content = [
|
||||
'{"x" : 1}'
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'{',
|
||||
' "x": 1',
|
||||
'}'
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
test('object - multiple properties', () => {
|
||||
var content = [
|
||||
'{"x" : 1, "y" : "foo", "z" : true}'
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'{',
|
||||
' "x": 1,',
|
||||
' "y": "foo",',
|
||||
' "z": true',
|
||||
'}'
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
test('object - no properties ', () => {
|
||||
var content = [
|
||||
'{"x" : { }, "y" : {}}'
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'{',
|
||||
' "x": {},',
|
||||
' "y": {}',
|
||||
'}'
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
test('object - nesting', () => {
|
||||
var content = [
|
||||
'{"x" : { "y" : { "z" : { }}, "a": true}}'
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'{',
|
||||
' "x": {',
|
||||
' "y": {',
|
||||
' "z": {}',
|
||||
' },',
|
||||
' "a": true',
|
||||
' }',
|
||||
'}'
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
|
||||
test('array - single items', () => {
|
||||
var content = [
|
||||
'["[]"]'
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'[',
|
||||
' "[]"',
|
||||
']'
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
|
||||
test('array - multiple items', () => {
|
||||
var content = [
|
||||
'[true,null,1.2]'
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'[',
|
||||
' true,',
|
||||
' null,',
|
||||
' 1.2',
|
||||
']'
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
|
||||
test('array - no items', () => {
|
||||
var content = [
|
||||
'[ ]'
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'[]'
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
|
||||
test('array - nesting', () => {
|
||||
var content = [
|
||||
'[ [], [ [ {} ], "a" ] ]'
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'[',
|
||||
' [],',
|
||||
' [',
|
||||
' [',
|
||||
' {}',
|
||||
' ],',
|
||||
' "a"',
|
||||
' ]',
|
||||
']',
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
|
||||
test('syntax errors', () => {
|
||||
var content = [
|
||||
'[ null 1.2 ]'
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'[',
|
||||
' null 1.2',
|
||||
']',
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
|
||||
test('empty lines', () => {
|
||||
var content = [
|
||||
'{',
|
||||
'"a": true,',
|
||||
'',
|
||||
'"b": true',
|
||||
'}',
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'{',
|
||||
'\t"a": true,',
|
||||
'\t"b": true',
|
||||
'}',
|
||||
].join('\n');
|
||||
|
||||
format(content, expected, false);
|
||||
});
|
||||
test('single line comment', () => {
|
||||
var content = [
|
||||
'[ ',
|
||||
'//comment',
|
||||
'"foo", "bar"',
|
||||
'] '
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'[',
|
||||
' //comment',
|
||||
' "foo",',
|
||||
' "bar"',
|
||||
']',
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
test('block line comment', () => {
|
||||
var content = [
|
||||
'[{',
|
||||
' /*comment*/ ',
|
||||
'"foo" : true',
|
||||
'}] '
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'[',
|
||||
' {',
|
||||
' /*comment*/',
|
||||
' "foo": true',
|
||||
' }',
|
||||
']',
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
test('single line comment on same line', () => {
|
||||
var content = [
|
||||
' { ',
|
||||
' "a": {}// comment ',
|
||||
' } '
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'{',
|
||||
' "a": {} // comment ',
|
||||
'}',
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
test('block comment on same line', () => {
|
||||
var content = [
|
||||
'{ "a": {}, /*comment*/ ',
|
||||
' /*comment*/ "b": {}, ',
|
||||
' "c": {/*comment*/} } ',
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'{',
|
||||
' "a": {}, /*comment*/',
|
||||
' /*comment*/ "b": {},',
|
||||
' "c": { /*comment*/}',
|
||||
'}',
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
|
||||
test('block comment on same line advanced', () => {
|
||||
var content = [
|
||||
' { "d": [',
|
||||
' null',
|
||||
' ] /*comment*/',
|
||||
' ,"e": /*comment*/ [null] }',
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'{',
|
||||
' "d": [',
|
||||
' null',
|
||||
' ] /*comment*/,',
|
||||
' "e": /*comment*/ [',
|
||||
' null',
|
||||
' ]',
|
||||
'}',
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
|
||||
test('multiple block comments on same line', () => {
|
||||
var content = [
|
||||
'{ "a": {} /*comment*/, /*comment*/ ',
|
||||
' /*comment*/ "b": {} /*comment*/ } '
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'{',
|
||||
' "a": {} /*comment*/, /*comment*/',
|
||||
' /*comment*/ "b": {} /*comment*/',
|
||||
'}',
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
|
||||
test('range', () => {
|
||||
var content = [
|
||||
'{ "a": {},',
|
||||
'|"b": [null, null]|',
|
||||
'} '
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'{ "a": {},',
|
||||
'"b": [',
|
||||
' null,',
|
||||
' null',
|
||||
']',
|
||||
'} ',
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
|
||||
test('range with existing indent', () => {
|
||||
var content = [
|
||||
'{ "a": {},',
|
||||
' |"b": [null],',
|
||||
'"c": {}',
|
||||
'} |'
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'{ "a": {},',
|
||||
' "b": [',
|
||||
' null',
|
||||
' ],',
|
||||
' "c": {}',
|
||||
'}',
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
|
||||
test('range with existing indent - tabs', () => {
|
||||
var content = [
|
||||
'{ "a": {},',
|
||||
'| "b": [null], ',
|
||||
'"c": {}',
|
||||
'} | '
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'{ "a": {},',
|
||||
'\t"b": [',
|
||||
'\t\tnull',
|
||||
'\t],',
|
||||
'\t"c": {}',
|
||||
'}',
|
||||
].join('\n');
|
||||
|
||||
format(content, expected, false);
|
||||
});
|
||||
|
||||
|
||||
test('block comment none-line breaking symbols', () => {
|
||||
var content = [
|
||||
'{ "a": [ 1',
|
||||
'/* comment */',
|
||||
', 2',
|
||||
'/* comment */',
|
||||
']',
|
||||
'/* comment */',
|
||||
',',
|
||||
' "b": true',
|
||||
'/* comment */',
|
||||
'}'
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'{',
|
||||
' "a": [',
|
||||
' 1',
|
||||
' /* comment */',
|
||||
' ,',
|
||||
' 2',
|
||||
' /* comment */',
|
||||
' ]',
|
||||
' /* comment */',
|
||||
' ,',
|
||||
' "b": true',
|
||||
' /* comment */',
|
||||
'}',
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
test('line comment after none-line breaking symbols', () => {
|
||||
var content = [
|
||||
'{ "a":',
|
||||
'// comment',
|
||||
'null,',
|
||||
' "b"',
|
||||
'// comment',
|
||||
': null',
|
||||
'// comment',
|
||||
'}'
|
||||
].join('\n');
|
||||
|
||||
var expected = [
|
||||
'{',
|
||||
' "a":',
|
||||
' // comment',
|
||||
' null,',
|
||||
' "b"',
|
||||
' // comment',
|
||||
' : null',
|
||||
' // comment',
|
||||
'}',
|
||||
].join('\n');
|
||||
|
||||
format(content, expected);
|
||||
});
|
||||
});
|
||||
109
extensions/json/server/src/tests/hover.test.ts
Normal file
109
extensions/json/server/src/tests/hover.test.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import assert = require('assert');
|
||||
import Parser = require('../jsonParser');
|
||||
import SchemaService = require('../jsonSchemaService');
|
||||
import JsonSchema = require('../json-toolbox/jsonSchema');
|
||||
import {JSONCompletion} from '../jsonCompletion';
|
||||
import {IXHROptions, IXHRResponse} from '../utils/httpRequest';
|
||||
import {create as createLinesModel} from '../utils/lines';
|
||||
import {JSONHover} from '../jsonHover';
|
||||
|
||||
import {Hover, ITextDocument, TextDocumentIdentifier, TextDocumentPosition, Range, Position, TextEdit} from 'vscode-languageserver';
|
||||
|
||||
suite('JSON Hover', () => {
|
||||
|
||||
function testComputeInfo(value: string, schema: JsonSchema.IJSONSchema, position: Position): Promise<Hover> {
|
||||
var uri = 'test://test.json';
|
||||
|
||||
var schemaService = new SchemaService.JSONSchemaService(requestService);
|
||||
var hoverProvider = new JSONHover(schemaService);
|
||||
var id = "http://myschemastore/test1";
|
||||
schemaService.registerExternalSchema(id, ["*.json"], schema);
|
||||
|
||||
var document = {
|
||||
getText: () => value,
|
||||
uri: uri
|
||||
}
|
||||
var textDocumentLocation = TextDocumentPosition.create(uri, position);
|
||||
var lines = createLinesModel(value);
|
||||
var jsonDoc = Parser.parse(value);
|
||||
return hoverProvider.doHover(document, textDocumentLocation, lines, jsonDoc);
|
||||
}
|
||||
|
||||
var requestService = function(options: IXHROptions): Promise<IXHRResponse> {
|
||||
return Promise.reject<IXHRResponse>({ responseText: '', status: 404 });
|
||||
}
|
||||
|
||||
test('Simple schema', function(testDone) {
|
||||
|
||||
var content = '{"a": 42, "b": "hello", "c": false}';
|
||||
var schema: JsonSchema.IJSONSchema = {
|
||||
type: 'object',
|
||||
description: 'a very special object',
|
||||
properties: {
|
||||
'a': {
|
||||
type: 'number',
|
||||
description: 'A'
|
||||
},
|
||||
'b': {
|
||||
type: 'string',
|
||||
description: 'B'
|
||||
},
|
||||
'c': {
|
||||
type: 'boolean',
|
||||
description: 'C'
|
||||
}
|
||||
}
|
||||
};
|
||||
Promise.all([
|
||||
testComputeInfo(content, schema, { line: 0, character: 0 }).then((result) => {
|
||||
assert.deepEqual(result.contents, ['a very special object']);
|
||||
}),
|
||||
testComputeInfo(content, schema, { line: 0, character: 1 }).then((result) => {
|
||||
assert.deepEqual(result.contents, ['A']);
|
||||
}),
|
||||
testComputeInfo(content, schema, { line: 0, character: 32 }).then((result) => {
|
||||
assert.deepEqual(result.contents, ['C']);
|
||||
}),
|
||||
testComputeInfo(content, schema, { line: 0, character: 7 }).then((result) => {
|
||||
assert.deepEqual(result.contents, ['A']);
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('Nested schema', function(testDone) {
|
||||
|
||||
var content = '{"a": 42, "b": "hello"}';
|
||||
var schema: JsonSchema.IJSONSchema = {
|
||||
oneOf: [{
|
||||
type: 'object',
|
||||
description: 'a very special object',
|
||||
properties: {
|
||||
'a': {
|
||||
type: 'number',
|
||||
description: 'A'
|
||||
},
|
||||
'b': {
|
||||
type: 'string',
|
||||
description: 'B'
|
||||
},
|
||||
}
|
||||
}, {
|
||||
type: 'array'
|
||||
}]
|
||||
};
|
||||
Promise.all([
|
||||
testComputeInfo(content, schema, { line: 0, character: 9 }).then((result) => {
|
||||
assert.deepEqual(result.contents, ['a very special object']);
|
||||
}),
|
||||
testComputeInfo(content, schema, { line: 0, character: 1 }).then((result) => {
|
||||
assert.deepEqual(result.contents, ['A']);
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
})
|
||||
81
extensions/json/server/src/tests/lines.test.ts
Normal file
81
extensions/json/server/src/tests/lines.test.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import assert = require('assert');
|
||||
import lines = require('../utils/lines');
|
||||
import {Position} from 'vscode-languageserver';
|
||||
|
||||
suite('Lines Model Validator', () => {
|
||||
|
||||
test('single line', () => {
|
||||
var str = "Hello World";
|
||||
var lm = lines.create(str);
|
||||
assert.equal(lm.lineCount, 1);
|
||||
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
assert.equal(lm.offsetAt(Position.create(0, i)), i);
|
||||
assert.deepEqual(lm.positionAt(i), Position.create(0, i));
|
||||
}
|
||||
});
|
||||
|
||||
test('Mutiple lines', () => {
|
||||
var str = "ABCDE\nFGHIJ\nKLMNO\n";
|
||||
var lm = lines.create(str);
|
||||
assert.equal(lm.lineCount, 4);
|
||||
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
var line = Math.floor(i / 6);
|
||||
var column = i % 6;
|
||||
|
||||
assert.equal(lm.offsetAt(Position.create(line, column)), i);
|
||||
assert.deepEqual(lm.positionAt(i), Position.create(line, column));
|
||||
}
|
||||
|
||||
assert.equal(lm.offsetAt(Position.create(3, 0)), 18);
|
||||
assert.equal(lm.offsetAt(Position.create(3, 1)), 18);
|
||||
assert.deepEqual(lm.positionAt(18), Position.create(3, 0));
|
||||
assert.deepEqual(lm.positionAt(19), Position.create(3, 0));
|
||||
});
|
||||
|
||||
test('New line characters', () => {
|
||||
var str = "ABCDE\rFGHIJ";
|
||||
assert.equal(lines.create(str).lineCount, 2);
|
||||
|
||||
var str = "ABCDE\nFGHIJ";
|
||||
assert.equal(lines.create(str).lineCount, 2);
|
||||
|
||||
var str = "ABCDE\r\nFGHIJ";
|
||||
assert.equal(lines.create(str).lineCount, 2);
|
||||
|
||||
str = "ABCDE\n\nFGHIJ";
|
||||
assert.equal(lines.create(str).lineCount, 3);
|
||||
|
||||
str = "ABCDE\r\rFGHIJ";
|
||||
assert.equal(lines.create(str).lineCount, 3);
|
||||
|
||||
str = "ABCDE\n\rFGHIJ";
|
||||
assert.equal(lines.create(str).lineCount, 3);
|
||||
})
|
||||
|
||||
|
||||
test('invalid inputs', () => {
|
||||
var str = "Hello World";
|
||||
var lm = lines.create(str);
|
||||
|
||||
// invalid position
|
||||
assert.equal(lm.offsetAt(Position.create(0, str.length)), str.length);
|
||||
assert.equal(lm.offsetAt(Position.create(0, str.length + 3)), str.length);
|
||||
assert.equal(lm.offsetAt(Position.create(2, 3)), str.length);
|
||||
assert.equal(lm.offsetAt(Position.create(-1, 3)), 0);
|
||||
assert.equal(lm.offsetAt(Position.create(0, -3)), 0);
|
||||
assert.equal(lm.offsetAt(Position.create(1, -3)), str.length);
|
||||
|
||||
// invalid offsets
|
||||
assert.deepEqual(lm.positionAt(-1), Position.create(0, 0));
|
||||
assert.deepEqual(lm.positionAt(str.length), Position.create(0, str.length));
|
||||
assert.deepEqual(lm.positionAt(str.length + 3), Position.create(0, str.length));
|
||||
});
|
||||
});
|
||||
1265
extensions/json/server/src/tests/parser.test.ts
Normal file
1265
extensions/json/server/src/tests/parser.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
532
extensions/json/server/src/tests/schema.test.ts
Normal file
532
extensions/json/server/src/tests/schema.test.ts
Normal file
@@ -0,0 +1,532 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import assert = require('assert');
|
||||
import SchemaService = require('../jsonSchemaService');
|
||||
import JsonSchema = require('../json-toolbox/jsonSchema');
|
||||
import Json = require('../json-toolbox/json');
|
||||
import Parser = require('../jsonParser');
|
||||
import fs = require('fs');
|
||||
import path = require('path');
|
||||
import {IXHROptions, IXHRResponse} from '../utils/httpRequest';
|
||||
|
||||
|
||||
suite('JSON Schema', () => {
|
||||
var fixureDocuments = {
|
||||
'http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json': 'deploymentTemplate.json',
|
||||
'http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json': 'deploymentParameters.json',
|
||||
'http://schema.management.azure.com/schemas/2015-01-01/Microsoft.Authorization.json': 'Microsoft.Authorization.json',
|
||||
'http://schema.management.azure.com/schemas/2015-01-01/Microsoft.Resources.json': 'Microsoft.Resources.json',
|
||||
'http://schema.management.azure.com/schemas/2014-04-01-preview/Microsoft.Sql.json': 'Microsoft.Sql.json',
|
||||
'http://schema.management.azure.com/schemas/2014-06-01/Microsoft.Web.json': 'Microsoft.Web.json',
|
||||
'http://schema.management.azure.com/schemas/2014-04-01/SuccessBricks.ClearDB.json': 'SuccessBricks.ClearDB.json',
|
||||
'http://schema.management.azure.com/schemas/2015-08-01/Microsoft.Compute.json': 'Microsoft.Compute.json'
|
||||
}
|
||||
|
||||
var requestServiceMock = function (options:IXHROptions) : Promise<IXHRResponse> {
|
||||
var uri = options.url;
|
||||
if (uri.length && uri[uri.length - 1] === '#') {
|
||||
uri = uri.substr(0, uri.length - 1);
|
||||
}
|
||||
var fileName = fixureDocuments[uri];
|
||||
if (fileName) {
|
||||
return new Promise<IXHRResponse>((c, e) => {
|
||||
var fixturePath = path.join(__dirname, '../../../server/src/tests/fixtures', fileName);
|
||||
fs.readFile(fixturePath, 'UTF-8', (err, result) => {
|
||||
err ? e({ responseText: '', status: 404 }) : c({ responseText: result.toString(), status: 200 })
|
||||
});
|
||||
});
|
||||
}
|
||||
return Promise.reject<IXHRResponse>({ responseText: '', status: 404 });
|
||||
}
|
||||
|
||||
test('Resolving $refs', function(testDone) {
|
||||
var service = new SchemaService.JSONSchemaService(requestServiceMock);
|
||||
service.setSchemaContributions({ schemas: {
|
||||
"https://myschemastore/main" : {
|
||||
id: 'https://myschemastore/main',
|
||||
type: 'object',
|
||||
properties: {
|
||||
child: {
|
||||
'$ref': 'https://myschemastore/child'
|
||||
}
|
||||
}
|
||||
},
|
||||
"https://myschemastore/child" :{
|
||||
id: 'https://myschemastore/child',
|
||||
type: 'bool',
|
||||
description: 'Test description'
|
||||
}
|
||||
}});
|
||||
|
||||
service.getResolvedSchema('https://myschemastore/main').then(fs => {
|
||||
assert.deepEqual(fs.schema.properties['child'], {
|
||||
id: 'https://myschemastore/child',
|
||||
type: 'bool',
|
||||
description: 'Test description'
|
||||
});
|
||||
}).then(() => testDone(), (error) => {
|
||||
testDone(error);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('FileSchema', function(testDone) {
|
||||
var service = new SchemaService.JSONSchemaService(requestServiceMock);
|
||||
|
||||
service.setSchemaContributions({ schemas: {
|
||||
"main" : {
|
||||
id: 'main',
|
||||
type: 'object',
|
||||
properties: {
|
||||
child: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'grandchild': {
|
||||
type: 'number',
|
||||
description: 'Meaning of Life'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}});
|
||||
|
||||
service.getResolvedSchema('main').then(fs => {
|
||||
var section = fs.getSection(['child', 'grandchild']);
|
||||
assert.equal(section.description, 'Meaning of Life');
|
||||
}).then(() => testDone(), (error) => {
|
||||
testDone(error);
|
||||
});
|
||||
});
|
||||
|
||||
test('Array FileSchema', function(testDone) {
|
||||
var service = new SchemaService.JSONSchemaService(requestServiceMock);
|
||||
|
||||
service.setSchemaContributions({ schemas: {
|
||||
"main" : {
|
||||
id: 'main',
|
||||
type: 'object',
|
||||
properties: {
|
||||
child: {
|
||||
type: 'array',
|
||||
items: {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'grandchild': {
|
||||
type: 'number',
|
||||
description: 'Meaning of Life'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}});
|
||||
|
||||
service.getResolvedSchema('main').then(fs => {
|
||||
var section = fs.getSection(['child','0', 'grandchild']);
|
||||
assert.equal(section.description, 'Meaning of Life');
|
||||
}).then(() => testDone(), (error) => {
|
||||
testDone(error);
|
||||
});
|
||||
});
|
||||
|
||||
test('Missing subschema', function(testDone) {
|
||||
var service = new SchemaService.JSONSchemaService(requestServiceMock);
|
||||
|
||||
service.setSchemaContributions({ schemas: {
|
||||
"main" : {
|
||||
id: 'main',
|
||||
type: 'object',
|
||||
properties: {
|
||||
child: {
|
||||
type: 'object'
|
||||
}
|
||||
}
|
||||
}
|
||||
}});
|
||||
|
||||
service.getResolvedSchema('main').then(fs => {
|
||||
var section = fs.getSection(['child','grandchild']);
|
||||
assert.strictEqual(section, null);
|
||||
}).then(() => testDone(), (error) => {
|
||||
testDone(error);
|
||||
});
|
||||
});
|
||||
|
||||
test('Preloaded Schema', function(testDone) {
|
||||
var service = new SchemaService.JSONSchemaService(requestServiceMock);
|
||||
var id = 'https://myschemastore/test1';
|
||||
var schema : JsonSchema.IJSONSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
child: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'grandchild': {
|
||||
type: 'number',
|
||||
description: 'Meaning of Life'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
service.registerExternalSchema(id, [ '*.json' ], schema);
|
||||
|
||||
service.getSchemaForResource('test.json', null).then((schema) => {
|
||||
var section = schema.getSection(['child','grandchild']);
|
||||
assert.equal(section.description, 'Meaning of Life');
|
||||
}).then(() => testDone(), (error) => {
|
||||
testDone(error);
|
||||
});
|
||||
});
|
||||
|
||||
test('External Schema', function(testDone) {
|
||||
var service = new SchemaService.JSONSchemaService(requestServiceMock);
|
||||
var id = 'https://myschemastore/test1';
|
||||
var schema : JsonSchema.IJSONSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
child: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'grandchild': {
|
||||
type: 'number',
|
||||
description: 'Meaning of Life'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
service.registerExternalSchema(id, [ '*.json' ], schema);
|
||||
|
||||
service.getSchemaForResource('test.json', null).then((schema) => {
|
||||
var section = schema.getSection(['child','grandchild']);
|
||||
assert.equal(section.description, 'Meaning of Life');
|
||||
}).then(() => testDone(), (error) => {
|
||||
testDone(error);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('Resolving in-line $refs', function (testDone) {
|
||||
var service = new SchemaService.JSONSchemaService(requestServiceMock);
|
||||
var id = 'https://myschemastore/test1';
|
||||
|
||||
var schema:JsonSchema.IJSONSchema = {
|
||||
id: 'main',
|
||||
type: 'object',
|
||||
definitions: {
|
||||
'grandchild': {
|
||||
type: 'number',
|
||||
description: 'Meaning of Life'
|
||||
}
|
||||
},
|
||||
properties: {
|
||||
child: {
|
||||
type: 'array',
|
||||
items: {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'grandchild': {
|
||||
$ref: '#/definitions/grandchild'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
service.registerExternalSchema(id, [ '*.json' ], schema);
|
||||
|
||||
service.getSchemaForResource('test.json', null).then((fs) => {
|
||||
var section = fs.getSection(['child', '0', 'grandchild']);
|
||||
assert.equal(section.description, 'Meaning of Life');
|
||||
}).then(() => testDone(), (error) => {
|
||||
testDone(error);
|
||||
});
|
||||
});
|
||||
|
||||
test('Resolving in-line $refs automatically for external schemas', function(testDone) {
|
||||
var service = new SchemaService.JSONSchemaService(requestServiceMock);
|
||||
var id = 'https://myschemastore/test1';
|
||||
var schema:JsonSchema.IJSONSchema = {
|
||||
id: 'main',
|
||||
type: 'object',
|
||||
definitions: {
|
||||
'grandchild': {
|
||||
type: 'number',
|
||||
description: 'Meaning of Life'
|
||||
}
|
||||
},
|
||||
properties: {
|
||||
child: {
|
||||
type: 'array',
|
||||
items: {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'grandchild': {
|
||||
$ref: '#/definitions/grandchild'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var fsm = service.registerExternalSchema(id, [ '*.json' ], schema);
|
||||
fsm.getResolvedSchema().then((fs) => {
|
||||
var section = fs.getSection(['child','0', 'grandchild']);
|
||||
assert.equal(section.description, 'Meaning of Life');
|
||||
}).then(() => testDone(), (error) => {
|
||||
testDone(error);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('Clearing External Schemas', function(testDone) {
|
||||
var service = new SchemaService.JSONSchemaService(requestServiceMock);
|
||||
var id1 = 'http://myschemastore/test1';
|
||||
var schema1:JsonSchema.IJSONSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
child: {
|
||||
type: 'number'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var id2 = 'http://myschemastore/test2';
|
||||
var schema2:JsonSchema.IJSONSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
child: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
service.registerExternalSchema(id1, [ 'test.json', 'bar.json' ], schema1);
|
||||
|
||||
service.getSchemaForResource('test.json', null).then((schema) => {
|
||||
var section = schema.getSection(['child']);
|
||||
assert.equal(section.type, 'number');
|
||||
|
||||
service.clearExternalSchemas();
|
||||
|
||||
service.registerExternalSchema(id2, [ '*.json' ], schema2);
|
||||
|
||||
return service.getSchemaForResource('test.json', null).then((schema) => {
|
||||
var section = schema.getSection(['child']);
|
||||
assert.equal(section.type, 'string');
|
||||
});
|
||||
}).then(() => testDone(), (error) => {
|
||||
testDone(error);
|
||||
});
|
||||
});
|
||||
|
||||
test('Schema contributions', function(testDone) {
|
||||
var service = new SchemaService.JSONSchemaService(requestServiceMock);
|
||||
|
||||
service.setSchemaContributions({ schemas: {
|
||||
"http://myschemastore/myschemabar" : {
|
||||
id: 'main',
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}, schemaAssociations: {
|
||||
'*.bar': ['http://myschemastore/myschemabar', 'http://myschemastore/myschemafoo']
|
||||
}});
|
||||
|
||||
var id2 = 'http://myschemastore/myschemafoo';
|
||||
var schema2:JsonSchema.IJSONSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
child: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
service.registerExternalSchema(id2, null, schema2);
|
||||
|
||||
service.getSchemaForResource('main.bar', null).then(resolvedSchema => {
|
||||
assert.deepEqual(resolvedSchema.errors, []);
|
||||
assert.equal(2, resolvedSchema.schema.allOf.length);
|
||||
|
||||
service.clearExternalSchemas();
|
||||
return service.getSchemaForResource('main.bar', null).then(resolvedSchema => {
|
||||
assert.equal(resolvedSchema.errors.length, 1);
|
||||
assert.ok(resolvedSchema.errors[0].indexOf("Problems loading reference 'http://myschemastore/myschemafoo'") === 0);
|
||||
|
||||
service.clearExternalSchemas();
|
||||
service.registerExternalSchema(id2, null, schema2);
|
||||
return service.getSchemaForResource('main.bar', null).then(resolvedSchema => {
|
||||
assert.equal(resolvedSchema.errors.length, 0);
|
||||
});
|
||||
});
|
||||
}).then(() => testDone(), (error) => {
|
||||
testDone(error);
|
||||
});
|
||||
});
|
||||
|
||||
test('Resolving circular $refs', function(testDone) {
|
||||
|
||||
var service : SchemaService.IJSONSchemaService = new SchemaService.JSONSchemaService(requestServiceMock);
|
||||
|
||||
var input = {
|
||||
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"resources": [
|
||||
{
|
||||
"name": "SQLServer",
|
||||
"type": "Microsoft.Sql/servers",
|
||||
"location": "West US",
|
||||
"apiVersion": "2014-04-01-preview",
|
||||
"dependsOn": [ ],
|
||||
"tags": {
|
||||
"displayName": "SQL Server"
|
||||
},
|
||||
"properties": {
|
||||
"administratorLogin": "asdfasd",
|
||||
"administratorLoginPassword": "asdfasdfasd"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
var document = Parser.parse(JSON.stringify(input));
|
||||
|
||||
service.getSchemaForResource('file://doc/mydoc.json', document).then(resolveSchema => {
|
||||
assert.deepEqual(resolveSchema.errors, []);
|
||||
|
||||
var content = JSON.stringify(resolveSchema.schema);
|
||||
assert.equal(content.indexOf('$ref'), -1); // no more $refs
|
||||
|
||||
var matchingSchemas = [];
|
||||
document.validate(resolveSchema.schema, matchingSchemas);
|
||||
assert.deepEqual(document.errors, []);
|
||||
assert.deepEqual(document.warnings, []);
|
||||
}).then(() => testDone(), (error) => {
|
||||
testDone(error);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('Resolving circular $refs, invalid document', function(testDone) {
|
||||
|
||||
var service : SchemaService.IJSONSchemaService = new SchemaService.JSONSchemaService(requestServiceMock);
|
||||
|
||||
var input = {
|
||||
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"resources": [
|
||||
{
|
||||
"name": "foo",
|
||||
"type": "Microsoft.Resources/deployments",
|
||||
"apiVersion": "2015-01-01",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
var document = Parser.parse(JSON.stringify(input));
|
||||
|
||||
service.getSchemaForResource('file://doc/mydoc.json', document).then(resolveSchema => {
|
||||
assert.deepEqual(resolveSchema.errors, []);
|
||||
|
||||
var content = JSON.stringify(resolveSchema.schema);
|
||||
assert.equal(content.indexOf('$ref'), -1); // no more $refs
|
||||
|
||||
var matchingSchemas = [];
|
||||
document.validate(resolveSchema.schema, matchingSchemas);
|
||||
assert.deepEqual(document.errors, []);
|
||||
assert.equal(document.warnings.length, 1);
|
||||
}).then(() => testDone(), (error) => {
|
||||
testDone(error);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('Validate Azure Resource Dfinition', function(testDone) {
|
||||
|
||||
|
||||
var service : SchemaService.IJSONSchemaService = new SchemaService.JSONSchemaService(requestServiceMock);
|
||||
|
||||
var input = {
|
||||
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "2015-06-15",
|
||||
"type": "Microsoft.Compute/virtualMachines",
|
||||
"name": "a",
|
||||
"location": "West US",
|
||||
"properties": {
|
||||
"hardwareProfile": {
|
||||
"vmSize": "Small"
|
||||
},
|
||||
"osProfile": {
|
||||
"computername": "a",
|
||||
"adminUsername": "a",
|
||||
"adminPassword": "a"
|
||||
},
|
||||
"storageProfile": {
|
||||
"imageReference": {
|
||||
"publisher": "a",
|
||||
"offer": "a",
|
||||
"sku": "a",
|
||||
"version": "latest"
|
||||
},
|
||||
"osDisk": {
|
||||
"name": "osdisk",
|
||||
"vhd": {
|
||||
"uri": "[concat('http://', 'b','.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('OSDiskName'),'.vhd')]"
|
||||
},
|
||||
"caching": "ReadWrite",
|
||||
"createOption": "FromImage"
|
||||
}
|
||||
},
|
||||
"networkProfile": {
|
||||
"networkInterfaces": [
|
||||
{
|
||||
"id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"diagnosticsProfile": {
|
||||
"bootDiagnostics": {
|
||||
"enabled": "true",
|
||||
"storageUri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net')]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
var document = Parser.parse(JSON.stringify(input));
|
||||
|
||||
service.getSchemaForResource('file://doc/mydoc.json', document).then(resolvedSchema => {
|
||||
assert.deepEqual(resolvedSchema.errors, []);
|
||||
|
||||
document.validate(resolvedSchema.schema);
|
||||
|
||||
assert.equal(document.warnings.length, 1);
|
||||
assert.equal(document.warnings[0].message, 'Missing property "computerName"');
|
||||
}).then(() => testDone(), (error) => {
|
||||
testDone(error);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
165
extensions/json/server/src/utils/httpRequest.ts
Normal file
165
extensions/json/server/src/utils/httpRequest.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Url, parse as parseUrl } from 'url';
|
||||
import { getProxyAgent } from './proxy';
|
||||
import https = require('https');
|
||||
import http = require('http');
|
||||
import nls = require('./nls');
|
||||
|
||||
export interface IXHROptions {
|
||||
type?: string;
|
||||
url?: string;
|
||||
user?: string;
|
||||
password?: string;
|
||||
headers?: any;
|
||||
timeout?: number;
|
||||
data?: any;
|
||||
agent?: any;
|
||||
strictSSL?: boolean;
|
||||
responseType?: string;
|
||||
followRedirects: number;
|
||||
}
|
||||
|
||||
export interface IXHRResponse {
|
||||
responseText: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
let proxyUrl: string = null;
|
||||
let strictSSL: boolean = true;
|
||||
|
||||
function assign(destination: any, ...sources: any[]): any {
|
||||
sources.forEach(source => Object.keys(source).forEach((key) => destination[key] = source[key]));
|
||||
return destination;
|
||||
}
|
||||
|
||||
export function configure(_proxyUrl: string, _strictSSL: boolean): void {
|
||||
proxyUrl = _proxyUrl;
|
||||
strictSSL = _strictSSL;
|
||||
}
|
||||
|
||||
export function xhr(options: IXHROptions): Promise<IXHRResponse> {
|
||||
const agent = getProxyAgent(options.url, { proxyUrl, strictSSL });
|
||||
options = assign({}, options);
|
||||
options = assign(options, { agent, strictSSL });
|
||||
|
||||
return request(options).then(result => new Promise<IXHRResponse>((c, e) => {
|
||||
let res = result.res;
|
||||
let data: string[] = [];
|
||||
res.on('data', c => data.push(c));
|
||||
res.on('end', () => {
|
||||
if (options.followRedirects > 0 && (res.statusCode >= 300 && res.statusCode <= 303 || res.statusCode === 307)) {
|
||||
let location = res.headers['location'];
|
||||
if (location) {
|
||||
let newOptions = {
|
||||
type: options.type, url: location, user: options.user, password: options.password, responseType: options.responseType, headers: options.headers,
|
||||
timeout: options.timeout, followRedirects: options.followRedirects - 1, data: options.data
|
||||
};
|
||||
xhr(newOptions).then(c, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let response: IXHRResponse = {
|
||||
responseText: data.join(''),
|
||||
status: res.statusCode
|
||||
};
|
||||
|
||||
if ((res.statusCode >= 200 && res.statusCode < 300) || res.statusCode === 1223) {
|
||||
c(response);
|
||||
} else {
|
||||
e(response);
|
||||
}
|
||||
});
|
||||
}), err => {
|
||||
let message: string;
|
||||
|
||||
if (agent) {
|
||||
message = 'Unable to to connect to ' + options.url + ' through a proxy . Error: ' + err.message;
|
||||
} else {
|
||||
message = 'Unable to to connect to ' + options.url + '. Error: ' + err.message;
|
||||
}
|
||||
|
||||
return Promise.reject<IXHRResponse>({
|
||||
responseText: message,
|
||||
status: 404
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
interface IRequestResult {
|
||||
req: http.ClientRequest;
|
||||
res: http.ClientResponse;
|
||||
}
|
||||
|
||||
function request(options: IXHROptions): Promise<IRequestResult> {
|
||||
let req: http.ClientRequest;
|
||||
|
||||
return new Promise<IRequestResult>((c, e) => {
|
||||
let endpoint = parseUrl(options.url);
|
||||
|
||||
let opts: https.RequestOptions = {
|
||||
hostname: endpoint.hostname,
|
||||
port: endpoint.port ? parseInt(endpoint.port) : (endpoint.protocol === 'https:' ? 443 : 80),
|
||||
path: endpoint.path,
|
||||
method: options.type || 'GET',
|
||||
headers: options.headers,
|
||||
agent: options.agent,
|
||||
rejectUnauthorized: (typeof options.strictSSL === 'boolean') ? options.strictSSL : true
|
||||
};
|
||||
|
||||
if (options.user && options.password) {
|
||||
opts.auth = options.user + ':' + options.password;
|
||||
}
|
||||
|
||||
let protocol = endpoint.protocol === 'https:' ? https : http;
|
||||
req = protocol.request(opts, (res: http.ClientResponse) => {
|
||||
if (res.statusCode >= 300 && res.statusCode < 400 && options.followRedirects && options.followRedirects > 0 && res.headers['location']) {
|
||||
c(<any> request(assign({}, options, {
|
||||
url: res.headers['location'],
|
||||
followRedirects: options.followRedirects - 1
|
||||
})));
|
||||
} else {
|
||||
c({ req, res });
|
||||
}
|
||||
});
|
||||
req.on('error', e);
|
||||
|
||||
options.timeout && req.setTimeout(options.timeout);
|
||||
options.data && req.write(options.data);
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
export function getErrorStatusDescription(status: number) : string {
|
||||
if (status < 400) {
|
||||
return void 0;
|
||||
}
|
||||
switch (status) {
|
||||
case 400: return nls.localize('status.400', 'Bad request. The request cannot be fulfilled due to bad syntax.');
|
||||
case 401: return nls.localize('status.401', 'Unauthorized. The server is refusing to respond.');
|
||||
case 403: return nls.localize('status.403', 'Forbidden. The server is refusing to respond.');
|
||||
case 404: return nls.localize('status.404', 'Not Found. The requested location could not be found.');
|
||||
case 405: return nls.localize('status.405', 'Method not allowed. A request was made using a request method not supported by that location.');
|
||||
case 406: return nls.localize('status.406', 'Not Acceptable. The server can only generate a response that is not accepted by the client.');
|
||||
case 407: return nls.localize('status.407', 'Proxy Authentication Required. The client must first authenticate itself with the proxy.');
|
||||
case 408: return nls.localize('status.408', 'Request Timeout. The server timed out waiting for the request.');
|
||||
case 409: return nls.localize('status.409', 'Conflict. The request could not be completed because of a conflict in the request.');
|
||||
case 410: return nls.localize('status.410', 'Gone. The requested page is no longer available.');
|
||||
case 411: return nls.localize('status.411', 'Length Required. The "Content-Length" is not defined.');
|
||||
case 412: return nls.localize('status.412', 'Precondition Failed. The precondition given in the request evaluated to false by the server.');
|
||||
case 413: return nls.localize('status.413', 'Request Entity Too Large. The server will not accept the request, because the request entity is too large.');
|
||||
case 414: return nls.localize('status.414', 'Request-URI Too Long. The server will not accept the request, because the URL is too long.');
|
||||
case 415: return nls.localize('status.415', 'Unsupported Media Type. The server will not accept the request, because the media type is not supported.');
|
||||
case 500: return nls.localize('status.500', 'Internal Server Error.');
|
||||
case 501: return nls.localize('status.501', 'Not Implemented. The server either does not recognize the request method, or it lacks the ability to fulfill the request.');
|
||||
case 503: return nls.localize('status.503', 'Service Unavailable. The server is currently unavailable (overloaded or down).');
|
||||
default: return nls.localize('status.416', 'HTTP status code {0}', status);
|
||||
}
|
||||
}
|
||||
88
extensions/json/server/src/utils/lines.ts
Normal file
88
extensions/json/server/src/utils/lines.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {
|
||||
Position
|
||||
} from 'vscode-languageserver';
|
||||
|
||||
export interface LinesModel {
|
||||
/**
|
||||
* Converts a zero-based offset to a position.
|
||||
*
|
||||
* @param offset A zero-based offset.
|
||||
* @return A valid [position](#Position).
|
||||
*/
|
||||
positionAt(offset: number): Position;
|
||||
|
||||
/**
|
||||
* Converts the position to a zero-based offset.
|
||||
*
|
||||
* The position will be [adjusted](#TextDocument.validatePosition).
|
||||
*
|
||||
* @param position A position.
|
||||
* @return A valid zero-based offset.
|
||||
*/
|
||||
offsetAt(position: Position): number;
|
||||
|
||||
/**
|
||||
* The number of lines in this document.
|
||||
*
|
||||
* @readonly
|
||||
*/
|
||||
lineCount: number;
|
||||
}
|
||||
|
||||
export function create(text:string) : LinesModel {
|
||||
const lineStarts: number[] = [];
|
||||
var isLineStart = true;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
if (isLineStart) {
|
||||
lineStarts.push(i);
|
||||
isLineStart = false;
|
||||
}
|
||||
var ch = text.charAt(i);
|
||||
isLineStart = (ch === '\r' || ch === '\n');
|
||||
if (ch === '\r' && i + 1 < text.length && text.charAt(i+1) === '\n') {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (isLineStart && text.length > 0) {
|
||||
lineStarts.push(text.length);
|
||||
}
|
||||
return {
|
||||
positionAt: (offset:number) => {
|
||||
offset = Math.max(Math.min(offset, text.length), 0);
|
||||
let low = 0, high = lineStarts.length;
|
||||
if (high === 0) {
|
||||
return Position.create(0, offset);
|
||||
}
|
||||
while (low < high) {
|
||||
let mid = Math.floor((low + high) / 2);
|
||||
if (lineStarts[mid] > offset) {
|
||||
high = mid;
|
||||
} else {
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
// low is the least x for which the line offset is larger than the offset
|
||||
// or array.length if no element fullfills the given function.
|
||||
var line = low - 1;
|
||||
return Position.create(line, offset - lineStarts[line]);
|
||||
},
|
||||
offsetAt: (position: Position) => {
|
||||
if (position.line >= lineStarts.length) {
|
||||
return text.length;
|
||||
} else if (position.line < 0) {
|
||||
return 0;
|
||||
}
|
||||
var lineStart = lineStarts[position.line];
|
||||
var nextLineStart = (position.line + 1 < lineStarts.length) ? lineStarts[position.line + 1] : text.length;
|
||||
return Math.max(Math.min(lineStart + position.character, nextLineStart), lineStart);
|
||||
},
|
||||
lineCount: lineStarts.length
|
||||
}
|
||||
|
||||
}
|
||||
15
extensions/json/server/src/utils/nls.ts
Normal file
15
extensions/json/server/src/utils/nls.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export function localize(key: string, message: string, ...formatArgs: any[]) {
|
||||
if (formatArgs.length > 0) {
|
||||
return message.replace(/\{(\d+)\}/g, function(match, rest) {
|
||||
var index = rest[0];
|
||||
return typeof formatArgs[index] !== 'undefined' ? formatArgs[index] : match;
|
||||
});
|
||||
}
|
||||
return message;
|
||||
}
|
||||
49
extensions/json/server/src/utils/proxy.ts
Normal file
49
extensions/json/server/src/utils/proxy.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Url, parse as parseUrl } from 'url';
|
||||
import HttpProxyAgent = require('http-proxy-agent');
|
||||
import HttpsProxyAgent = require('https-proxy-agent');
|
||||
|
||||
function getSystemProxyURI(requestURL: Url): string {
|
||||
if (requestURL.protocol === 'http:') {
|
||||
return process.env.HTTP_PROXY || process.env.http_proxy || null;
|
||||
} else if (requestURL.protocol === 'https:') {
|
||||
return process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface IOptions {
|
||||
proxyUrl?: string;
|
||||
strictSSL?: boolean;
|
||||
}
|
||||
|
||||
export function getProxyAgent(rawRequestURL: string, options: IOptions = {}): any {
|
||||
const requestURL = parseUrl(rawRequestURL);
|
||||
const proxyURL = options.proxyUrl || getSystemProxyURI(requestURL);
|
||||
|
||||
if (!proxyURL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const proxyEndpoint = parseUrl(proxyURL);
|
||||
|
||||
if (!/^https?:$/.test(proxyEndpoint.protocol)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const opts = {
|
||||
host: proxyEndpoint.hostname,
|
||||
port: Number(proxyEndpoint.port),
|
||||
auth: proxyEndpoint.auth,
|
||||
rejectUnauthorized: (typeof options.strictSSL === 'boolean') ? options.strictSSL : true
|
||||
};
|
||||
|
||||
return requestURL.protocol === 'http:' ? new HttpProxyAgent(opts) : new HttpsProxyAgent(opts);
|
||||
}
|
||||
37
extensions/json/server/src/utils/strings.ts
Normal file
37
extensions/json/server/src/utils/strings.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export function startsWith(haystack: string, needle: string): boolean {
|
||||
if (haystack.length < needle.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < needle.length; i++) {
|
||||
if (haystack[i] !== needle[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if haystack ends with needle.
|
||||
*/
|
||||
export function endsWith(haystack: string, needle: string): boolean {
|
||||
let diff = haystack.length - needle.length;
|
||||
if (diff > 0) {
|
||||
return haystack.lastIndexOf(needle) === haystack.length - needle.length;
|
||||
} else if (diff === 0) {
|
||||
return haystack === needle;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function convertSimple2RegExpPattern(pattern: string): string {
|
||||
return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*');
|
||||
}
|
||||
334
extensions/json/server/src/utils/uri.ts
Normal file
334
extensions/json/server/src/utils/uri.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
|
||||
function fixedEncodeURIComponent(str: string): string {
|
||||
return encodeURIComponent(str).replace(/[!'()*]/g, c => '%' + c.charCodeAt(0).toString(16).toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986.
|
||||
* This class is a simple parser which creates the basic component paths
|
||||
* (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation
|
||||
* and encoding.
|
||||
*
|
||||
* foo://example.com:8042/over/there?name=ferret#nose
|
||||
* \_/ \______________/\_________/ \_________/ \__/
|
||||
* | | | | |
|
||||
* scheme authority path query fragment
|
||||
* | _____________________|__
|
||||
* / \ / \
|
||||
* urn:example:animal:ferret:nose
|
||||
*
|
||||
*
|
||||
*/
|
||||
export default class URI {
|
||||
|
||||
private static _empty = '';
|
||||
private static _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
|
||||
private static _driveLetterPath = /^\/[a-zA-z]:/;
|
||||
private static _driveLetter = /^[a-zA-z]:/;
|
||||
|
||||
private _scheme: string;
|
||||
private _authority: string;
|
||||
private _path: string;
|
||||
private _query: string;
|
||||
private _fragment: string;
|
||||
|
||||
constructor() {
|
||||
this._scheme = URI._empty;
|
||||
this._authority = URI._empty;
|
||||
this._path = URI._empty;
|
||||
this._query = URI._empty;
|
||||
this._fragment = URI._empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* scheme is the 'http' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
* The part before the first colon.
|
||||
*/
|
||||
get scheme() {
|
||||
return this._scheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* authority is the 'www.msft.com' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
* The part between the first double slashes and the next slash.
|
||||
*/
|
||||
get authority() {
|
||||
return this._authority;
|
||||
}
|
||||
|
||||
/**
|
||||
* path is the '/some/path' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
*/
|
||||
get path() {
|
||||
return this._path;
|
||||
}
|
||||
|
||||
/**
|
||||
* query is the 'query' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
*/
|
||||
get query() {
|
||||
return this._query;
|
||||
}
|
||||
|
||||
/**
|
||||
* fragment is the 'fragment' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
*/
|
||||
get fragment() {
|
||||
return this._fragment;
|
||||
}
|
||||
|
||||
// ---- filesystem path -----------------------
|
||||
|
||||
private _fsPath: string;
|
||||
|
||||
/**
|
||||
* Returns a string representing the corresponding file system path of this URI.
|
||||
* Will handle UNC paths and normalize windows drive letters to lower-case. Also
|
||||
* uses the platform specific path separator. Will *not* validate the path for
|
||||
* invalid characters and semantics. Will *not* look at the scheme of this URI.
|
||||
*/
|
||||
get fsPath() {
|
||||
if (!this._fsPath) {
|
||||
var value: string;
|
||||
if (this._authority && this.scheme === 'file') {
|
||||
// unc path: file://shares/c$/far/boo
|
||||
value = `//${this._authority}${this._path}`;
|
||||
} else if (URI._driveLetterPath.test(this._path)) {
|
||||
// windows drive letter: file:///c:/far/boo
|
||||
value = this._path[1].toLowerCase() + this._path.substr(2);
|
||||
} else {
|
||||
// other path
|
||||
value = this._path;
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
value = value.replace(/\//g, '\\');
|
||||
}
|
||||
this._fsPath = value;
|
||||
}
|
||||
return this._fsPath;
|
||||
}
|
||||
|
||||
// ---- modify to new -------------------------
|
||||
|
||||
public with(scheme: string, authority: string, path: string, query: string, fragment: string): URI {
|
||||
var ret = new URI();
|
||||
ret._scheme = scheme || this.scheme;
|
||||
ret._authority = authority || this.authority;
|
||||
ret._path = path || this.path;
|
||||
ret._query = query || this.query;
|
||||
ret._fragment = fragment || this.fragment;
|
||||
URI._validate(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public withScheme(value: string): URI {
|
||||
return this.with(value, undefined, undefined, undefined, undefined);
|
||||
}
|
||||
|
||||
public withAuthority(value: string): URI {
|
||||
return this.with(undefined, value, undefined, undefined, undefined);
|
||||
}
|
||||
|
||||
public withPath(value: string): URI {
|
||||
return this.with(undefined, undefined, value, undefined, undefined);
|
||||
}
|
||||
|
||||
public withQuery(value: string): URI {
|
||||
return this.with(undefined, undefined, undefined, value, undefined);
|
||||
}
|
||||
|
||||
public withFragment(value: string): URI {
|
||||
return this.with(undefined, undefined, undefined, undefined, value);
|
||||
}
|
||||
|
||||
// ---- parse & validate ------------------------
|
||||
|
||||
public static parse(value: string): URI {
|
||||
var ret = URI._parse(value);
|
||||
ret = ret.with(undefined,
|
||||
decodeURIComponent(ret.authority),
|
||||
decodeURIComponent(ret.path),
|
||||
decodeURIComponent(ret.query),
|
||||
decodeURIComponent(ret.fragment));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static file(path: string): URI {
|
||||
path = path.replace(/\\/g, '/');
|
||||
path = path.replace(/%/g, '%25');
|
||||
path = path.replace(/#/g, '%23');
|
||||
path = path.replace(/\?/g, '%3F');
|
||||
path = URI._driveLetter.test(path)
|
||||
? '/' + path
|
||||
: path;
|
||||
|
||||
var ret = URI._parse(path);
|
||||
if (ret.scheme || ret.fragment || ret.query) {
|
||||
throw new Error('Path contains a scheme, fragment or a query. Can not convert it to a file uri.');
|
||||
}
|
||||
|
||||
ret = ret.with('file', undefined,
|
||||
decodeURIComponent(ret.path),
|
||||
undefined, undefined);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static _parse(value: string): URI {
|
||||
var ret = new URI();
|
||||
var match = URI._regexp.exec(value);
|
||||
if (match) {
|
||||
ret._scheme = match[2] || ret._scheme;
|
||||
ret._authority = match[4] || ret._authority;
|
||||
ret._path = match[5] || ret._path;
|
||||
ret._query = match[7] || ret._query;
|
||||
ret._fragment = match[9] || ret._fragment;
|
||||
};
|
||||
URI._validate(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static create(scheme?: string, authority?: string, path?: string, query?: string, fragment?: string): URI {
|
||||
return new URI().with(scheme, authority, path, query, fragment);
|
||||
}
|
||||
|
||||
private static _validate(ret: URI): void {
|
||||
|
||||
// validation
|
||||
// path, http://tools.ietf.org/html/rfc3986#section-3.3
|
||||
// If a URI contains an authority component, then the path component
|
||||
// must either be empty or begin with a slash ("/") character. If a URI
|
||||
// does not contain an authority component, then the path cannot begin
|
||||
// with two slash characters ("//").
|
||||
if (ret.authority && ret.path && ret.path[0] !== '/') {
|
||||
throw new Error('[UriError]: If a URI contains an authority component, then the path component must either be empty or begin with a slash ("/") character');
|
||||
}
|
||||
if (!ret.authority && ret.path.indexOf('//') === 0) {
|
||||
throw new Error('[UriError]: If a URI does not contain an authority component, then the path cannot begin with two slash characters ("//")');
|
||||
}
|
||||
}
|
||||
|
||||
// ---- printing/externalize ---------------------------
|
||||
|
||||
private _formatted: string;
|
||||
|
||||
public toString(): string {
|
||||
if (!this._formatted) {
|
||||
var parts: string[] = [];
|
||||
|
||||
if (this._scheme) {
|
||||
parts.push(this._scheme);
|
||||
parts.push(':');
|
||||
}
|
||||
if (this._authority || this._scheme === 'file') {
|
||||
parts.push('//');
|
||||
}
|
||||
if (this._authority) {
|
||||
var authority = this._authority,
|
||||
idx: number;
|
||||
|
||||
authority = authority.toLowerCase();
|
||||
idx = authority.indexOf(':');
|
||||
if (idx === -1) {
|
||||
parts.push(fixedEncodeURIComponent(authority));
|
||||
} else {
|
||||
parts.push(fixedEncodeURIComponent(authority.substr(0, idx)));
|
||||
parts.push(authority.substr(idx));
|
||||
}
|
||||
}
|
||||
if (this._path) {
|
||||
// encode every segment of the path
|
||||
var path = this._path,
|
||||
segments: string[];
|
||||
|
||||
// lower-case win drive letters in /C:/fff
|
||||
if (URI._driveLetterPath.test(path)) {
|
||||
path = '/' + path[1].toLowerCase() + path.substr(2);
|
||||
} else if (URI._driveLetter.test(path)) {
|
||||
path = path[0].toLowerCase() + path.substr(1);
|
||||
}
|
||||
segments = path.split('/');
|
||||
for (var i = 0, len = segments.length; i < len; i++) {
|
||||
segments[i] = fixedEncodeURIComponent(segments[i]);
|
||||
}
|
||||
parts.push(segments.join('/'));
|
||||
}
|
||||
if (this._query) {
|
||||
// in http(s) querys often use 'key=value'-pairs and
|
||||
// ampersand characters for multiple pairs
|
||||
var encoder = /https?/i.test(this.scheme)
|
||||
? encodeURI
|
||||
: fixedEncodeURIComponent;
|
||||
|
||||
parts.push('?');
|
||||
parts.push(encoder(this._query));
|
||||
}
|
||||
if (this._fragment) {
|
||||
parts.push('#');
|
||||
parts.push(fixedEncodeURIComponent(this._fragment));
|
||||
}
|
||||
this._formatted = parts.join('');
|
||||
}
|
||||
return this._formatted;
|
||||
}
|
||||
|
||||
public toJSON(): any {
|
||||
return this.toString();
|
||||
}
|
||||
|
||||
public static isURI(thing: any): thing is URI {
|
||||
if (thing instanceof URI) {
|
||||
return true;
|
||||
}
|
||||
if(!thing) {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).scheme !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).authority !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).fsPath !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).query !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).fragment !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).with !== 'function') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).withScheme !== 'function') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).withAuthority !== 'function') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).withPath !== 'function') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).withQuery !== 'function') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).withFragment !== 'function') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).toString !== 'function') {
|
||||
return false;
|
||||
}
|
||||
if (typeof (<URI>thing).toJSON !== 'function') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user