提交 1379c399 编写于 作者: M Martin Aeschlimann

Merge pull request #2245 from Microsoft/aeschli-json-language-server

Add json language server
......@@ -170,7 +170,12 @@ function packageTask(platform, arch, opts) {
'!extensions/*/src/**',
'!extensions/*/out/**/test/**',
'!extensions/typescript/bin/**',
'!extensions/vscode-api-tests/**'
'!extensions/vscode-api-tests/**',
'!extensions/json/server/.vscode/**',
'!extensions/json/server/src/**',
'!extensions/json/server/out/**/test/**',
'!extensions/json/server/test/**',
'!extensions/json/server/typings/**',
], { base: '.' });
var pluginHostSourceMap = gulp.src(out + '/vs/workbench/node/pluginHostProcess.js.map', { base: '.' })
......
{
"comments": {
"lineComment": "//",
"blockComment": [ "/*", "*/" ]
},
"brackets": [
["{", "}"],
["[", "]"]
]
}
\ No newline at end of file
......@@ -5,6 +5,14 @@
"engines": {
"vscode": "*"
},
"activationEvents": [
"onLanguage:json"
],
"main": "./out/jsonMain",
"scripts": {
"vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../gulpfile.plugins.js compile-plugin:json ./src/tsconfig.json",
"postinstall": "cd server && npm install && npm run compile && cd .."
},
"contributes": {
"languages": [
{
......@@ -22,7 +30,8 @@
],
"mimetypes": [
"application/json"
]
],
"configuration": "./json.configuration.json"
}
],
"grammars": [
......@@ -36,7 +45,83 @@
{
"fileMatch": "*.schema.json",
"url": "http://json-schema.org/draft-04/schema#"
},
{
"fileMatch": "package.json",
"url": "vscode://schemas/vscode-extensions"
},
{
"fileMatch": "vscode://defaultsettings/keybindings.json",
"url": "vscode://schemas/keybindings"
},
{
"fileMatch": "%APP_SETTINGS_HOME%/keybindings.json",
"url": "vscode://schemas/keybindings"
},
{
"fileMatch": "vscode://defaultsettings/settings.json",
"url": "vscode://schemas/settings"
},
{
"fileMatch": "%APP_SETTINGS_HOME%/settings.json",
"url": "vscode://schemas/settings"
},
{
"fileMatch": "/.vscode/settings.json",
"url": "vscode://schemas/settings"
},
{
"fileMatch": "/.vscode/launch.json",
"url": "vscode://schemas/launch"
},
{
"fileMatch": "/.vscode/tasks.json",
"url": "vscode://schemas/tasks"
},
{
"fileMatch": "%APP_SETTINGS_HOME%/snippets/*.json",
"url": "vscode://schemas/snippets"
}
]
],
"configuration": {
"id": "json",
"order": 20,
"type": "object",
"title": "JSON configuration",
"properties" : {
"json.schemas" : {
"type": "array",
"description": "Associate schemas to JSON files in the current project",
"items": {
"type": "object",
"default": { "fileMatch": [ "{{/myfile}}" ], "url": "{{schemaURL}}" },
"properties": {
"url": {
"type": "string",
"default": "/user.schema.json",
"description": "A URL to a schema or a relative path to a schema in the current directory"
},
"fileMatch": {
"type": "array",
"items": {
"type": "string",
"default": "MyFile.json",
"description": "A file pattern that can contain '*' to match against when resolving JSON files to schemas."
},
"minItems": 1,
"description": "An array of file patterns to match against when resolving JSON files to schemas."
},
"schema": {
"type": "object",
"description": "The schema definition for the given URL. The schema only needs to be provided to avoid accesses to the schema URL."
}
}
}
}
}
}
},
"dependencies": {
"vscode-languageclient": "next"
}
}
\ No newline at end of file
{
"version": "0.1.0",
// List of configurations. Add new configurations or edit existing ones.
"configurations": [
{
"name": "Attach",
"type": "node",
"request": "attach",
"port": 6004,
"sourceMaps": true,
"outDir": "${workspaceRoot}/out"
},
{
"name": "Unit Tests",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
"stopOnEntry": false,
"args": [
"--timeout",
"999999",
"--colors"
],
"cwd": "${workspaceRoot}",
"runtimeExecutable": null,
"runtimeArgs": [],
"env": {},
"sourceMaps": true,
"outDir": "${workspaceRoot}/out"
}
]
}
\ No newline at end of file
{
"version": "0.1.0",
"command": "npm",
"isShellCommand": true,
"showOutput": "silent",
"args": ["run", "watch"],
"isWatching": true,
"problemMatcher": "$tsc-watch"
}
\ No newline at end of file
{
"name": "vscode-json-server",
"description": "JSON language server",
"version": "0.0.1",
"author": "Microsoft Corporation",
"license": "MIT",
"engines": {
"node": "*"
},
"dependencies": {
"http-proxy-agent": "^0.2.6",
"https-proxy-agent": "^0.3.5",
"vscode-languageserver": "next"
},
"devDependencies": {
"mocha": "^2.2.5",
"typescript": "^1.6.2"
},
"scripts": {
"compile": "tsc -p .",
"watch": "tsc --watch -p .",
"postinstall": "tsc -p ."
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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': {}
}
}
}
\ No newline at end of file
此差异已折叠。
/*---------------------------------------------------------------------------------------------
* 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;
}
/*---------------------------------------------------------------------------------------------
* 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 {IJSONWorkerContribution} from './jsonContributions';
import {CompletionItem, CompletionItemKind, CompletionList, CompletionOptions, ITextDocument, TextDocumentIdentifier, TextDocumentPosition, Range, TextEdit} from 'vscode-languageserver';
export interface ISuggestionsCollector {
add(suggestion: CompletionItem): void;
error(message:string): void;
setAsIncomplete(): void;
}
export class JSONCompletion {
private schemaService: SchemaService.IJSONSchemaService;
private contributions: IJSONWorkerContribution[];
constructor(schemaService: SchemaService.IJSONSchemaService, contributions: IJSONWorkerContribution[] = []) {
this.schemaService = schemaService;
this.contributions = contributions;
}
public doSuggest(document: ITextDocument, textDocumentPosition: TextDocumentPosition, doc: Parser.JSONDocument): Thenable<CompletionList> {
let offset = document.offsetAt(textDocumentPosition.position);
let node = doc.getNodeFromOffsetEndInclusive(offset);
let overwriteRange = null;
let result: CompletionList = {
items: [],
isIncomplete: false
}
if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
overwriteRange = Range.create(document.positionAt(node.start), document.positionAt(node.end));
}
let proposed: { [key: string]: boolean } = {};
let collector: ISuggestionsCollector = {
add: (suggestion: CompletionItem) => {
if (!proposed[suggestion.label]) {
proposed[suggestion.label] = true;
if (overwriteRange) {
suggestion.textEdit = TextEdit.replace(overwriteRange, suggestion.insertText);
}
result.items.push(suggestion);
}
},
setAsIncomplete: () => {
result.isIncomplete = true;
},
error: (message: string) => {
console.log(message);
}
};
return this.schemaService.getSchemaForResource(textDocumentPosition.uri, doc).then((schema) => {
let collectionPromises: Thenable<any>[] = [];
let addValue = true;
let currentKey = '';
let currentWord = '';
let currentProperty: Parser.PropertyASTNode = null;
if (node) {
if (node.type === 'string') {
let 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().substring(node.start + 1, node.end - 1);
currentWord = document.getText().substring(node.start + 1, offset);
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
let properties = (<Parser.ObjectASTNode>node).properties;
properties.forEach(p => {
if (!currentProperty || currentProperty !== p) {
proposed[p.key.value] = true;
}
});
let isLast = properties.length === 0 || offset >= properties[properties.length - 1].start;
if (schema) {
// property proposals with schema
this.getPropertySuggestions(schema, doc, node, currentKey, addValue, isLast, collector);
} else if (node.parent) {
// property proposals without schema
this.getSchemaLessPropertySuggestions(doc, node, collector);
}
let location = node.getNodeLocation();
this.contributions.forEach((contribution) => {
let collectPromise = contribution.collectPropertySuggestions(textDocumentPosition.uri, location, this.getCurrentWord(document, offset), addValue, isLast, collector);
if (collectPromise) {
collectionPromises.push(collectPromise);
}
});
}
// 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);
}
if (!node) {
this.contributions.forEach((contribution) => {
let collectPromise = contribution.collectDefaultSuggestions(textDocumentPosition.uri, collector);
if (collectPromise) {
collectionPromises.push(collectPromise);
}
});
} else {
if ((node.type === 'property') && offset > (<Parser.PropertyASTNode> node).colonOffset) {
let parentKey = (<Parser.PropertyASTNode>node).key.value;
let valueNode = (<Parser.PropertyASTNode> node).value;
if (!valueNode || offset <= valueNode.end) {
let location = node.parent.getNodeLocation();
this.contributions.forEach((contribution) => {
let collectPromise = contribution.collectValueSuggestions(textDocumentPosition.uri, location, parentKey, collector);
if (collectPromise) {
collectionPromises.push(collectPromise);
}
});
}
}
}
return Promise.all(collectionPromises).then(() => result );
});
}
private getPropertySuggestions(schema: SchemaService.ResolvedSchema, doc: Parser.JSONDocument, node: Parser.ASTNode, currentWord: string, addValue: boolean, isLast: boolean, collector: ISuggestionsCollector): void {
let matchingSchemas: Parser.IApplicableSchema[] = [];
doc.validate(schema.schema, matchingSchemas, node.start);
matchingSchemas.forEach((s) => {
if (s.node === node && !s.inverted) {
let schemaProperties = s.schema.properties;
if (schemaProperties) {
Object.keys(schemaProperties).forEach((key: string) => {
let 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 {
let collectSuggestionsForSimilarObject = (obj: Parser.ObjectASTNode) => {
obj.properties.forEach((p) => {
let 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
let 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 {
let collectSuggestionsForValues = (value: Parser.ASTNode) => {
let 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) {
let valueNode = (<Parser.PropertyASTNode>node).value;
if (valueNode && offset > valueNode.end) {
return;
}
// suggest values at the same key
let 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
let 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 {
let parentKey: string = null;
if (node && (node.type === 'property') && offset > (<Parser.PropertyASTNode>node).colonOffset) {
let 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')) {
let 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) {
let 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 {
let 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 {
let 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)) {
let 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:
let content = document.getText().substr(node.start, node.end - node.start);
return content;
}
}
private getSnippetForProperty(key: string, propertySchema: JsonSchema.IJSONSchema, addValue: boolean, isLast: boolean): string {
let result = '"' + key + '"';
if (!addValue) {
return result;
}
result += ': ';
let 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 + '"';
}
private getCurrentWord(document: ITextDocument, offset: number) {
var i = offset - 1;
var text = document.getText();
while (i >= 0 && ' \t\n\r\v"'.indexOf(text.charAt(i)) === -1) {
i--;
}
return text.substring(i+1, offset);
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 {JSONLocation} from './jsonLocation';
import {ISuggestionsCollector} from './jsonCompletion';
import {MarkedString} from 'vscode-languageserver';
export {ISuggestionsCollector} from './jsonCompletion';
export interface IJSONWorkerContribution {
getInfoContribution(resource: string, location: JSONLocation) : Thenable<MarkedString[]>;
collectPropertySuggestions(resource: string, location: JSONLocation, currentWord: string, addValue: boolean, isLast:boolean, result: ISuggestionsCollector) : Thenable<any>;
collectValueSuggestions(resource: string, location: JSONLocation, propertyKey: string, result: ISuggestionsCollector): Thenable<any>;
collectDefaultSuggestions(resource: string, result: ISuggestionsCollector): Thenable<any>;
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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';
export class JSONDocumentSymbols {
constructor() {
}
public compute(document: ITextDocument, 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 === 'vscode://defaultsettings/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(document.positionAt(item.start), document.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(document.positionAt(property.start), document.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;
}
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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';
export function format(document: ITextDocument, 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 = document.offsetAt(startPosition);
let endOffset = document.offsetAt(Position.create(range.end.line + 1, 0));
let endLineStart = document.offsetAt(Position.create(range.end.line, 0));
while (endOffset > endLineStart && isEOL(documentText, endOffset - 1)) {
endOffset--;
}
range = Range.create(startPosition, document.positionAt(endOffset));
value = documentText.substring(rangeOffset, endOffset);
initialIndentLevel = computeIndentLevel(value, 0, options);
} else {
value = documentText;
range = Range.create(Position.create(0, 0), document.positionAt(value.length));
initialIndentLevel = 0;
rangeOffset = 0;
}
let eol = getEOL(document);
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(document.positionAt(startOffset), document.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): string {
let text = document.getText();
if (document.lineCount > 1) {
let to = document.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;
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 {IJSONWorkerContribution} from './jsonContributions';
import {Hover, ITextDocument, TextDocumentPosition, Range, MarkedString, RemoteConsole} from 'vscode-languageserver';
export class JSONHover {
private schemaService: SchemaService.IJSONSchemaService;
private contributions: IJSONWorkerContribution[];
constructor(schemaService: SchemaService.IJSONSchemaService, contributions: IJSONWorkerContribution[] = []) {
this.schemaService = schemaService;
this.contributions = contributions;
}
public doHover(document: ITextDocument, textDocumentPosition: TextDocumentPosition, doc: Parser.JSONDocument): Thenable<Hover> {
let offset = document.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;
});
var createHover = (contents: MarkedString[]) => {
let range = Range.create(document.positionAt(node.start), document.positionAt(node.end));
let result: Hover = {
contents: contents,
range: range
};
return result;
};
let location = node.getNodeLocation();
for (let i = this.contributions.length - 1; i >= 0; i--) {
let contribution = this.contributions[i];
let promise = contribution.getInfoContribution(textDocumentPosition.uri, location);
if (promise) {
return promise.then(htmlContent => createHover(htmlContent));
}
}
if (description) {
return createHover([description]);
}
}
return void 0;
});
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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('][') + ']';
}
}
\ No newline at end of file
此差异已折叠。
/*---------------------------------------------------------------------------------------------
* 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): Thenable<ResolvedSchema>;
}
export interface ISchemaAssociations {
[pattern: string]: string[];
}
export interface ISchemaContributions {
schemas?: { [id: string]: IJSONSchema };
schemaAssociations?: ISchemaAssociations;
}
export interface ISchemaHandle {
/**
* The schema id
*/
url: string;
/**
* The schema from the file, with potential $ref references
*/
getUnresolvedSchema(): Thenable<UnresolvedSchema>;
/**
* The schema from the file, with references resolved
*/
getResolvedSchema(): Thenable<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: Thenable<ResolvedSchema>;
private unresolvedSchema: Thenable<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(): Thenable<UnresolvedSchema> {
if (!this.unresolvedSchema) {
this.unresolvedSchema = this.service.loadSchema(this.url);
}
return this.unresolvedSchema;
}
public getResolvedSchema(): Thenable<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): Thenable<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): Thenable<ResolvedSchema> {
let id = this.normalizeId(schemaId);
let schemaHandle = this.schemasById[id];
if (schemaHandle) {
return schemaHandle.getResolvedSchema();
}
return Promise.resolve(null);
}
public loadSchema(url: string): Thenable<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): Thenable<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): Thenable<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): Thenable<any> => {
let toWalk = [node];
let seen: any[] = [];
let openPromises: Thenable<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): Thenable<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;
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 {MarkedString, CompletionItemKind} from 'vscode-languageserver';
import Strings = require('../utils/strings');
import nls = require('../utils/nls');
import {IJSONWorkerContribution, ISuggestionsCollector} from '../jsonContributions';
import {IRequestService} from '../jsonSchemaService';
import {JSONLocation} from '../jsonLocation';
export class BowerJSONContribution implements IJSONWorkerContribution {
private requestService : IRequestService;
private topRanked = ['twitter','bootstrap','angular-1.1.6','angular-latest','angulerjs','d3','myjquery','jq','abcdef1234567890','jQuery','jquery-1.11.1','jquery',
'sushi-vanilla-x-data','font-awsome','Font-Awesome','font-awesome','fontawesome','html5-boilerplate','impress.js','homebrew',
'backbone','moment1','momentjs','moment','linux','animate.css','animate-css','reveal.js','jquery-file-upload','blueimp-file-upload','threejs','express','chosen',
'normalize-css','normalize.css','semantic','semantic-ui','Semantic-UI','modernizr','underscore','underscore1',
'material-design-icons','ionic','chartjs','Chart.js','nnnick-chartjs','select2-ng','select2-dist','phantom','skrollr','scrollr','less.js','leancss','parser-lib',
'hui','bootstrap-languages','async','gulp','jquery-pjax','coffeescript','hammer.js','ace','leaflet','jquery-mobile','sweetalert','typeahead.js','soup','typehead.js',
'sails','codeigniter2'];
public constructor(requestService: IRequestService) {
this.requestService = requestService;
}
private isBowerFile(resource: string): boolean {
return Strings.endsWith(resource, '/bower.json') || Strings.endsWith(resource, '/.bower.json');
}
public collectDefaultSuggestions(resource: string, result: ISuggestionsCollector): Thenable<any> {
if (this.isBowerFile(resource)) {
let defaultValue = {
'name': '{{name}}',
'description': '{{description}}',
'authors': [ '{{author}}' ],
'version': '{{1.0.0}}',
'main': '{{pathToMain}}',
'dependencies': {}
};
result.add({ kind: CompletionItemKind.Class, label: nls.localize('json.bower.default', 'Default bower.json'), insertText: JSON.stringify(defaultValue, null, '\t'), documentation: '' });
}
return null;
}
public collectPropertySuggestions(resource: string, location: JSONLocation, currentWord: string, addValue: boolean, isLast:boolean, result: ISuggestionsCollector) : Thenable<any> {
if (this.isBowerFile(resource) && (location.matches(['dependencies']) || location.matches(['devDependencies']))) {
if (currentWord.length > 0) {
let queryUrl = 'https://bower.herokuapp.com/packages/search/' + encodeURIComponent(currentWord);
return this.requestService({
url : queryUrl
}).then((success) => {
if (success.status === 200) {
try {
let obj = JSON.parse(success.responseText);
if (Array.isArray(obj)) {
let results = <{name:string; description:string;}[]> obj;
for (let i = 0; i < results.length; i++) {
let name = results[i].name;
let description = results[i].description || '';
let insertText = JSON.stringify(name);
if (addValue) {
insertText += ': "{{*}}"';
if (!isLast) {
insertText += ',';
}
}
result.add({ kind: CompletionItemKind.Property, label: name, insertText: insertText, documentation: description });
}
result.setAsIncomplete();
}
} catch (e) {
// ignore
}
} else {
result.error(nls.localize('json.bower.error.repoaccess', 'Request to the bower repository failed: {0}', success.responseText));
return 0;
}
}, (error) => {
result.error(nls.localize('json.bower.error.repoaccess', 'Request to the bower repository failed: {0}', error.responseText));
return 0;
});
} else {
this.topRanked.forEach((name) => {
let insertText = JSON.stringify(name);
if (addValue) {
insertText += ': "{{*}}"';
if (!isLast) {
insertText += ',';
}
}
result.add({ kind: CompletionItemKind.Property, label: name, insertText: insertText, documentation: '' });
});
result.setAsIncomplete();
}
}
return null;
}
public collectValueSuggestions(resource: string, location: JSONLocation, currentKey: string, result: ISuggestionsCollector): Thenable<any> {
// not implemented. Could be do done calling the bower command. Waiting for web API: https://github.com/bower/registry/issues/26
return null;
}
public getInfoContribution(resource: string, location: JSONLocation): Thenable<MarkedString[]> {
if (this.isBowerFile(resource) && (location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']))) {
let pack = location.getSegments()[location.getSegments().length - 1];
let htmlContent : MarkedString[] = [];
htmlContent.push(nls.localize('json.bower.package.hover', '{0}', pack));
let queryUrl = 'https://bower.herokuapp.com/packages/' + encodeURIComponent(pack);
return this.requestService({
url : queryUrl
}).then((success) => {
try {
let obj = JSON.parse(success.responseText);
if (obj && obj.url) {
let url = obj.url;
if (Strings.startsWith(url, 'git://')) {
url = url.substring(6);
}
if (Strings.endsWith(url, '.git')) {
url = url.substring(0, url.length - 4);
}
htmlContent.push(url);
}
} catch (e) {
// ignore
}
return htmlContent;
}, (error) => {
return htmlContent;
});
}
return null;
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 {MarkedString, CompletionItemKind, CompletionItem} from 'vscode-languageserver';
import Strings = require('../utils/strings');
import nls = require('../utils/nls');
import {IJSONWorkerContribution, ISuggestionsCollector} from '../jsonContributions';
import {IRequestService} from '../jsonSchemaService';
import {JSONLocation} from '../jsonLocation';
let globProperties:CompletionItem[] = [
{ kind: CompletionItemKind.Value, label: nls.localize('fileLabel', "Files by Extension"), insertText: '"**/*.{{extension}}": true', documentation: nls.localize('fileDescription', "Match all files of a specific file extension.")},
{ kind: CompletionItemKind.Value, label: nls.localize('filesLabel', "Files with Multiple Extensions"), insertText: '"**/*.{ext1,ext2,ext3}": true', documentation: nls.localize('filesDescription', "Match all files with any of the file extensions.")},
{ kind: CompletionItemKind.Value, label: nls.localize('derivedLabel', "Files with Siblings by Name"), insertText: '"**/*.{{source-extension}}": { "when": "$(basename).{{target-extension}}" }', documentation: nls.localize('derivedDescription', "Match files that have siblings with the same name but a different extension.")},
{ kind: CompletionItemKind.Value, label: nls.localize('topFolderLabel', "Folder by Name (Top Level)"), insertText: '"{{name}}": true', documentation: nls.localize('topFolderDescription', "Match a top level folder with a specific name.")},
{ kind: CompletionItemKind.Value, label: nls.localize('topFoldersLabel', "Folders with Multiple Names (Top Level)"), insertText: '"{folder1,folder2,folder3}": true', documentation: nls.localize('topFoldersDescription', "Match multiple top level folders.")},
{ kind: CompletionItemKind.Value, label: nls.localize('folderLabel', "Folder by Name (Any Location)"), insertText: '"**/{{name}}": true', documentation: nls.localize('folderDescription', "Match a folder with a specific name in any location.")},
];
let globValues:CompletionItem[] = [
{ kind: CompletionItemKind.Value, label: nls.localize('trueLabel', "True"), insertText: 'true', documentation: nls.localize('trueDescription', "Enable the pattern.")},
{ kind: CompletionItemKind.Value, label: nls.localize('falseLabel', "False"), insertText: 'false', documentation: nls.localize('falseDescription', "Disable the pattern.")},
{ kind: CompletionItemKind.Value, label: nls.localize('derivedLabel', "Files with Siblings by Name"), insertText: '{ "when": "$(basename).{{extension}}" }', documentation: nls.localize('siblingsDescription', "Match files that have siblings with the same name but a different extension.")}
];
export class GlobPatternContribution implements IJSONWorkerContribution {
constructor() {
}
private isSettingsFile(resource: string): boolean {
return Strings.endsWith(resource, '/settings.json');
}
public collectDefaultSuggestions(resource: string, result: ISuggestionsCollector): Thenable<any> {
return null;
}
public collectPropertySuggestions(resource: string, location: JSONLocation, currentWord: string, addValue: boolean, isLast:boolean, result: ISuggestionsCollector) : Thenable<any> {
if (this.isSettingsFile(resource) && (location.matches(['files.exclude']) || location.matches(['search.exclude']))) {
globProperties.forEach((e) => result.add(e));
}
return null;
}
public collectValueSuggestions(resource: string, location: JSONLocation, currentKey: string, result: ISuggestionsCollector): Thenable<any> {
if (this.isSettingsFile(resource) && (location.matches(['files.exclude']) || location.matches(['search.exclude']))) {
globValues.forEach((e) => result.add(e));
}
return null;
}
public getInfoContribution(resource: string, location: JSONLocation): Thenable<MarkedString[]> {
return null;
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 {MarkedString, CompletionItemKind, CompletionItem} from 'vscode-languageserver';
import Strings = require('../utils/strings');
import nls = require('../utils/nls');
import {IJSONWorkerContribution, ISuggestionsCollector} from '../jsonContributions';
import {IRequestService} from '../jsonSchemaService';
import {JSONLocation} from '../jsonLocation';
let LIMIT = 40;
export class PackageJSONContribution implements IJSONWorkerContribution {
private mostDependedOn = [ 'lodash', 'async', 'underscore', 'request', 'commander', 'express', 'debug', 'chalk', 'colors', 'q', 'coffee-script',
'mkdirp', 'optimist', 'through2', 'yeoman-generator', 'moment', 'bluebird', 'glob', 'gulp-util', 'minimist', 'cheerio', 'jade', 'redis', 'node-uuid',
'socket', 'io', 'uglify-js', 'winston', 'through', 'fs-extra', 'handlebars', 'body-parser', 'rimraf', 'mime', 'semver', 'mongodb', 'jquery',
'grunt', 'connect', 'yosay', 'underscore', 'string', 'xml2js', 'ejs', 'mongoose', 'marked', 'extend', 'mocha', 'superagent', 'js-yaml', 'xtend',
'shelljs', 'gulp', 'yargs', 'browserify', 'minimatch', 'react', 'less', 'prompt', 'inquirer', 'ws', 'event-stream', 'inherits', 'mysql', 'esprima',
'jsdom', 'stylus', 'when', 'readable-stream', 'aws-sdk', 'concat-stream', 'chai', 'Thenable', 'wrench'];
private requestService : IRequestService;
private isPackageJSONFile(resource: string): boolean {
return Strings.endsWith(resource, '/package.json');
}
public constructor(requestService: IRequestService) {
this.requestService = requestService;
}
public collectDefaultSuggestions(resource: string, result: ISuggestionsCollector): Thenable<any> {
if (this.isPackageJSONFile(resource)) {
let defaultValue = {
'name': '{{name}}',
'description': '{{description}}',
'author': '{{author}}',
'version': '{{1.0.0}}',
'main': '{{pathToMain}}',
'dependencies': {}
};
result.add({ kind: CompletionItemKind.Module, label: nls.localize('json.package.default', 'Default package.json'), insertText: JSON.stringify(defaultValue, null, '\t'), documentation: '' });
}
return null;
}
public collectPropertySuggestions(resource: string, location: JSONLocation, currentWord: string, addValue: boolean, isLast:boolean, result: ISuggestionsCollector) : Thenable<any> {
if (this.isPackageJSONFile(resource) && (location.matches(['dependencies']) || location.matches(['devDependencies']) || location.matches(['optionalDependencies']) || location.matches(['peerDependencies']))) {
let queryUrl : string;
if (currentWord.length > 0) {
queryUrl = 'https://skimdb.npmjs.com/registry/_design/app/_view/browseAll?group_level=1&limit=' + LIMIT + '&start_key=%5B%22' + encodeURIComponent(currentWord) + '%22%5D&end_key=%5B%22'+ encodeURIComponent(currentWord + 'z') + '%22,%7B%7D%5D';
return this.requestService({
url : queryUrl
}).then((success) => {
if (success.status === 200) {
try {
let obj = JSON.parse(success.responseText);
if (obj && Array.isArray(obj.rows)) {
let results = <{ key: string[]; }[]> obj.rows;
for (let i = 0; i < results.length; i++) {
let keys = results[i].key;
if (Array.isArray(keys) && keys.length > 0) {
let name = keys[0];
let insertText = JSON.stringify(name);
if (addValue) {
insertText += ': "{{*}}"';
if (!isLast) {
insertText += ',';
}
}
result.add({ kind: CompletionItemKind.Property, label: name, insertText: insertText, documentation: '' });
}
}
if (results.length === LIMIT) {
result.setAsIncomplete();
}
}
} catch (e) {
// ignore
}
} else {
result.error(nls.localize('json.npm.error.repoaccess', 'Request to the NPM repository failed: {0}', success.responseText));
return 0;
}
}, (error) => {
result.error(nls.localize('json.npm.error.repoaccess', 'Request to the NPM repository failed: {0}', error.responseText));
return 0;
});
} else {
this.mostDependedOn.forEach((name) => {
let insertText = JSON.stringify(name);
if (addValue) {
insertText += ': "{{*}}"';
if (!isLast) {
insertText += ',';
}
}
result.add({ kind: CompletionItemKind.Property, label: name, insertText: insertText, documentation: '' });
});
result.setAsIncomplete();
}
}
return null;
}
public collectValueSuggestions(resource: string, location: JSONLocation, currentKey: string, result: ISuggestionsCollector): Thenable<any> {
if (this.isPackageJSONFile(resource) && (location.matches(['dependencies']) || location.matches(['devDependencies']) || location.matches(['optionalDependencies']) || location.matches(['peerDependencies']))) {
let queryUrl = 'http://registry.npmjs.org/' + encodeURIComponent(currentKey) + '/latest';
return this.requestService({
url : queryUrl
}).then((success) => {
try {
let obj = JSON.parse(success.responseText);
if (obj && obj.version) {
let version = obj.version;
let name = JSON.stringify(version);
result.add({ kind: CompletionItemKind.Class, label: name, insertText: name, documentation: nls.localize('json.npm.latestversion', 'The currently latest version of the package') });
name = JSON.stringify('^' + version);
result.add({ kind: CompletionItemKind.Class, label: name, insertText: name, documentation: nls.localize('json.npm.majorversion', 'Matches the most recent major version (1.x.x)') });
name = JSON.stringify('~' + version);
result.add({ kind: CompletionItemKind.Class, label: name, insertText: name, documentation: nls.localize('json.npm.minorversion', 'Matches the most recent minor version (1.2.x)') });
}
} catch (e) {
// ignore
}
return 0;
}, (error) => {
return 0;
});
}
return null;
}
public getInfoContribution(resource: string, location: JSONLocation): Thenable<MarkedString[]> {
if (this.isPackageJSONFile(resource) && (location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']) || location.matches(['optionalDependencies', '*']) || location.matches(['peerDependencies', '*']))) {
let pack = location.getSegments()[location.getSegments().length - 1];
let htmlContent : MarkedString[] = [];
htmlContent.push(nls.localize('json.npm.package.hover', '{0}', pack));
let queryUrl = 'http://registry.npmjs.org/' + encodeURIComponent(pack) + '/latest';
return this.requestService({
url : queryUrl
}).then((success) => {
try {
let obj = JSON.parse(success.responseText);
if (obj) {
if (obj.description) {
htmlContent.push(obj.description);
}
if (obj.version) {
htmlContent.push(nls.localize('json.npm.version.hover', 'Latest version: {0}', obj.version));
}
}
} catch (e) {
// ignore
}
return htmlContent;
}, (error) => {
return htmlContent;
});
}
return null;
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 {MarkedString, CompletionItemKind, CompletionItem} from 'vscode-languageserver';
import Strings = require('../utils/strings');
import nls = require('../utils/nls');
import {IJSONWorkerContribution, ISuggestionsCollector} from '../jsonContributions';
import {IRequestService} from '../jsonSchemaService';
import {JSONLocation} from '../jsonLocation';
let LIMIT = 40;
export class ProjectJSONContribution implements IJSONWorkerContribution {
private requestService : IRequestService;
public constructor(requestService: IRequestService) {
this.requestService = requestService;
}
private isProjectJSONFile(resource: string): boolean {
return Strings.endsWith(resource, '/project.json');
}
public collectDefaultSuggestions(resource: string, result: ISuggestionsCollector): Thenable<any> {
if (this.isProjectJSONFile(resource)) {
let defaultValue = {
'version': '{{1.0.0-*}}',
'dependencies': {},
'frameworks': {
'dnx451': {},
'dnxcore50': {}
}
};
result.add({ kind: CompletionItemKind.Class, label: nls.localize('json.project.default', 'Default project.json'), insertText: JSON.stringify(defaultValue, null, '\t'), documentation: '' });
}
return null;
}
public collectPropertySuggestions(resource: string, location: JSONLocation, currentWord: string, addValue: boolean, isLast:boolean, result: ISuggestionsCollector) : Thenable<any> {
if (this.isProjectJSONFile(resource) && (location.matches(['dependencies']) || location.matches(['frameworks', '*', 'dependencies']) || location.matches(['frameworks', '*', 'frameworkAssemblies']))) {
let queryUrl : string;
if (currentWord.length > 0) {
queryUrl = 'https://www.nuget.org/api/v2/Packages?'
+ '$filter=Id%20ge%20\''
+ encodeURIComponent(currentWord)
+ '\'%20and%20Id%20lt%20\''
+ encodeURIComponent(currentWord + 'z')
+ '\'%20and%20IsAbsoluteLatestVersion%20eq%20true'
+ '&$select=Id,Version,Description&$format=json&$top=' + LIMIT;
} else {
queryUrl = 'https://www.nuget.org/api/v2/Packages?'
+ '$filter=IsAbsoluteLatestVersion%20eq%20true'
+ '&$orderby=DownloadCount%20desc&$top=' + LIMIT
+ '&$select=Id,Version,DownloadCount,Description&$format=json';
}
return this.requestService({
url : queryUrl
}).then((success) => {
if (success.status === 200) {
try {
let obj = JSON.parse(success.responseText);
if (Array.isArray(obj.d)) {
let results = <any[]> obj.d;
for (let i = 0; i < results.length; i++) {
let curr = results[i];
let name = curr.Id;
let version = curr.Version;
if (name) {
let documentation = curr.Description;
let typeLabel = curr.Version;
let insertText = JSON.stringify(name);
if (addValue) {
insertText += ': "{{' + version + '}}"';
if (!isLast) {
insertText += ',';
}
}
result.add({ kind: CompletionItemKind.Property, label: name, insertText: insertText, detail: typeLabel, documentation: documentation });
}
}
if (results.length === LIMIT) {
result.setAsIncomplete();
}
}
} catch (e) {
// ignore
}
} else {
result.error(nls.localize('json.nugget.error.repoaccess', 'Request to the nuget repository failed: {0}', success.responseText));
return 0;
}
}, (error) => {
result.error(nls.localize('json.nugget.error.repoaccess', 'Request to the nuget repository failed: {0}', error.responseText));
return 0;
});
}
return null;
}
public collectValueSuggestions(resource: string, location: JSONLocation, currentKey: string, result: ISuggestionsCollector): Thenable<any> {
if (this.isProjectJSONFile(resource) && (location.matches(['dependencies']) || location.matches(['frameworks', '*', 'dependencies']) || location.matches(['frameworks', '*', 'frameworkAssemblies']))) {
let queryUrl = 'https://www.myget.org/F/aspnetrelease/api/v2/Packages?'
+ '$filter=Id%20eq%20\''
+ encodeURIComponent(currentKey)
+ '\'&$select=Version,IsAbsoluteLatestVersion&$format=json&$top=' + LIMIT;
return this.requestService({
url : queryUrl
}).then((success) => {
try {
let obj = JSON.parse(success.responseText);
if (Array.isArray(obj.d)) {
let results = <any[]> obj.d;
for (let i = 0; i < results.length; i++) {
let curr = results[i];
let version = curr.Version;
if (version) {
let name = JSON.stringify(version);
let isLatest = curr.IsAbsoluteLatestVersion === 'true';
let label = name;
let documentation = '';
if (isLatest) {
documentation = nls.localize('json.nugget.versiondescription.suggest', 'The currently latest version of the package');
}
result.add({ kind: CompletionItemKind.Class, label: label, insertText: name, documentation: documentation });
}
}
if (results.length === LIMIT) {
result.setAsIncomplete();
}
}
} catch (e) {
// ignore
}
return 0;
}, (error) => {
return 0;
});
}
return null;
}
public getInfoContribution(resource: string, location: JSONLocation): Thenable<MarkedString[]> {
if (this.isProjectJSONFile(resource) && (location.matches(['dependencies', '*']) || location.matches(['frameworks', '*', 'dependencies', '*']) || location.matches(['frameworks', '*', 'frameworkAssemblies', '*']))) {
let pack = location.getSegments()[location.getSegments().length - 1];
let htmlContent : MarkedString[] = [];
htmlContent.push(nls.localize('json.nugget.package.hover', '{0}', pack));
let queryUrl = 'https://www.myget.org/F/aspnetrelease/api/v2/Packages?'
+ '$filter=Id%20eq%20\''
+ encodeURIComponent(pack)
+ '\'%20and%20IsAbsoluteLatestVersion%20eq%20true'
+ '&$select=Version,Description&$format=json&$top=5';
return this.requestService({
url : queryUrl
}).then((success) => {
let content = success.responseText;
if (content) {
try {
let obj = JSON.parse(content);
if (obj.d && obj.d[0]) {
let res = obj.d[0];
if (res.Description) {
htmlContent.push(res.Description);
}
if (res.Version) {
htmlContent.push(nls.localize('json.nugget.version.hover', 'Latest version: {0}', res.Version));
}
}
} catch (e) {
// ignore
}
}
return htmlContent;
}, (error) => {
return htmlContent;
});
}
return null;
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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, CompletionList,
CompletionItem, CompletionItemKind, Files, Hover, SymbolInformation, TextEdit, DocumentFormattingParams,
DocumentRangeFormattingParams, NotificationType, RequestType
} from 'vscode-languageserver';
import {xhr, IXHROptions, IXHRResponse, configure as configureHttpRequests} from './utils/httpRequest';
import path = require('path');
import fs = require('fs');
import URI from './utils/uri';
import Strings = require('./utils/strings');
import {IWorkspaceContextService, ITelemetryService, JSONSchemaService, ISchemaContributions, ISchemaAssociations} 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';
import {BowerJSONContribution} from './jsoncontributions/bowerJSONContribution';
import {PackageJSONContribution} from './jsoncontributions/packageJSONContribution';
import {ProjectJSONContribution} from './jsoncontributions/projectJSONContribution';
import {GlobPatternContribution} from './jsoncontributions/globPatternContribution';
namespace TelemetryNotification {
export const type: NotificationType<{ key: string, data: any }> = { get method() { return 'telemetry'; } };
}
namespace SchemaAssociationNotification {
export const type: NotificationType<ISchemaAssociations> = { get method() { return 'json/schemaAssociations'; } };
}
namespace VSCodeContentRequest {
export const type: RequestType<string, string, any> = { get method() { return 'vscode/content'; } };
}
// 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: InitializeParams): InitializeResult => {
workspaceRoot = URI.parse(params.rootPath);
if (params.initializationOptions) {
let proxy = params.initializationOptions['proxy'];
let proxyStrictSSL = params.initializationOptions['proxyStrictSSL'];
configureHttpRequests(proxy, proxyStrictSSL);
}
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): Thenable<IXHRResponse> => {
if (Strings.startsWith(options.url, 'file://')) {
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 })
});
});
} else if (Strings.startsWith(options.url, 'vscode://')) {
return connection.sendRequest(VSCodeContentRequest.type, options.url).then(responseText => {
return {
responseText: responseText,
status: 200
};
}, error => {
return {
responseText: error.message,
status: 404
}
});
}
return xhr(options);
}
let contributions = [
new ProjectJSONContribution(request),
new PackageJSONContribution(request),
new BowerJSONContribution(request),
new GlobPatternContribution()
];
let jsonSchemaService = new JSONSchemaService(request, workspaceContext, telemetry);
jsonSchemaService.setSchemaContributions(schemaContributions);
let jsonCompletion = new JSONCompletion(jsonSchemaService, contributions);
let jsonHover = new JSONHover(jsonSchemaService, contributions);
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: {
schemas: JSONSchemaSettings[]
};
}
interface JSONSchemaSettings {
fileMatch: string[],
url: string,
schema?: any;
}
let jsonConfigurationSettings : JSONSchemaSettings[] = void 0;
let schemaAssociations : ISchemaAssociations = void 0;
// The settings have changed. Is send on server activation as well.
connection.onDidChangeConfiguration((change) => {
var jsonSettings = (<Settings>change.settings).json;
jsonConfigurationSettings = jsonSettings && jsonSettings.schemas;
updateConfiguration();
});
// The jsonValidation extension configuration has changed
connection.onNotification(SchemaAssociationNotification.type, (associations) => {
schemaAssociations = associations;
updateConfiguration();
});
function updateConfiguration() {
jsonSchemaService.clearExternalSchemas();
if (schemaAssociations) {
for (var pattern in schemaAssociations) {
let association = schemaAssociations[pattern];
if (Array.isArray(association)) {
association.forEach(url => {
jsonSchemaService.registerExternalSchema(url, [pattern]);
})
}
}
}
if (jsonConfigurationSettings) {
jsonConfigurationSettings.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 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: textDocument.positionAt(error.location.start),
end: textDocument.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 getJSONDocument(document: ITextDocument): JSONDocument {
return parseJSON(document.getText());
}
connection.onCompletion((textDocumentPosition: TextDocumentPosition): Thenable<CompletionList> => {
let document = documents.get(textDocumentPosition.uri);
let jsonDocument = getJSONDocument(document);
return jsonCompletion.doSuggest(document, textDocumentPosition, jsonDocument);
});
connection.onHover((textDocumentPosition: TextDocumentPosition): Thenable<Hover> => {
let document = documents.get(textDocumentPosition.uri);
let jsonDocument = getJSONDocument(document);
return jsonHover.doHover(document, textDocumentPosition, jsonDocument);
});
connection.onDocumentSymbol((textDocumentIdentifier: TextDocumentIdentifier): Thenable<SymbolInformation[]> => {
let document = documents.get(textDocumentIdentifier.uri);
let jsonDocument = getJSONDocument(document);
return jsonDocumentSymbols.compute(document, jsonDocument);
});
connection.onDocumentFormatting((formatParams: DocumentFormattingParams) => {
let document = documents.get(formatParams.textDocument.uri);
return formatJSON(document, null, formatParams.options);
});
connection.onDocumentRangeFormatting((formatParams: DocumentRangeFormattingParams) => {
let document = documents.get(formatParams.textDocument.uri);
return formatJSON(document, formatParams.range, formatParams.options);
});
// Listen on the connection
connection.listen();
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 {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): Thenable<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 = ITextDocument.create(uri, value);
var textDocumentLocation = TextDocumentPosition.create(uri, Position.create(0, idx));
var jsonDoc = Parser.parse(value);
return completionProvider.doSuggest(document, textDocumentLocation, jsonDoc).then(list => list.items);
};
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));
});
});
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
/*---------------------------------------------------------------------------------------------
* 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;
}
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
--ui tdd
--useColors true
./out/test
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册