提交 235cbcdf 编写于 作者: M Martin Aeschlimann

Move bower/package.json dependency completions to javascript extension

上级 4fe233c7
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceRoot}"
],
"stopOnEntry": false,
"sourceMaps": true,
"outDir": "${workspaceRoot}/out",
"preLaunchTask": "npm"
},
{
"name": "Launch Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ],
"stopOnEntry": false,
"sourceMaps": true,
"outDir": "${workspaceRoot}/out/test",
"preLaunchTask": "npm"
}
]
}
\ No newline at end of file
// Available variables which can be used inside of strings.
// ${workspaceRoot}: the root folder of the team
// ${file}: the current opened file
// ${fileBasename}: the current opened file's basename
// ${fileDirname}: the current opened file's dirname
// ${fileExtname}: the current opened file's extension
// ${cwd}: the current working directory of the spawned process
// A task runner that calls a custom npm script that compiles the extension.
{
"version": "0.1.0",
// we want to run npm
"command": "npm",
// the command is a shell script
"isShellCommand": true,
// show the output window only if unrecognized errors occur.
"showOutput": "silent",
// we run the custom script "compile" as defined in package.json
"args": ["run", "compile"],
// The tsc compiler is started in watching mode
"isWatching": true,
// use the standard tsc in watch mode problem matcher to find compile problems in the output.
"problemMatcher": "$tsc-watch"
}
\ No newline at end of file
...@@ -2,8 +2,19 @@ ...@@ -2,8 +2,19 @@
"name": "javascript", "name": "javascript",
"version": "0.1.0", "version": "0.1.0",
"publisher": "vscode", "publisher": "vscode",
"engines": { "engines": { "vscode": "0.10.x" },
"vscode": "*" "activationEvents": [
"onLanguage:javascript", "onLanguage:json"
],
"main": "./out/javascriptMain",
"dependencies": {
"vscode-nls": "^1.0.4",
"request-light": "^0.0.3",
"jsonc-parser": "^0.0.1"
},
"scripts": {
"compile": "gulp compile-extension:javascript",
"watch": "gulp watch-extension:javascript"
}, },
"contributes": { "contributes": {
"languages": [ "languages": [
......
...@@ -4,18 +4,15 @@ ...@@ -4,18 +4,15 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import {MarkedString, CompletionItemKind} from 'vscode-languageserver'; import {MarkedString, CompletionItemKind, CompletionItem, DocumentSelector} from 'vscode';
import Strings = require('../utils/strings'); import {IJSONContribution, ISuggestionsCollector} from './jsonContributions';
import {IJSONWorkerContribution, ISuggestionsCollector} from '../jsonContributions'; import {XHRRequest} from 'request-light';
import {IRequestService} from '../jsonSchemaService'; import {Location} from 'jsonc-parser';
import {JSONLocation} from '../jsonLocation';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
export class BowerJSONContribution implements IJSONWorkerContribution { export class BowerJSONContribution implements IJSONContribution {
private requestService : IRequestService;
private topRanked = ['twitter','bootstrap','angular-1.1.6','angular-latest','angulerjs','d3','myjquery','jq','abcdef1234567890','jQuery','jquery-1.11.1','jquery', 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', 'sushi-vanilla-x-data','font-awsome','Font-Awesome','font-awesome','fontawesome','html5-boilerplate','impress.js','homebrew',
...@@ -25,35 +22,35 @@ export class BowerJSONContribution implements IJSONWorkerContribution { ...@@ -25,35 +22,35 @@ export class BowerJSONContribution implements IJSONWorkerContribution {
'hui','bootstrap-languages','async','gulp','jquery-pjax','coffeescript','hammer.js','ace','leaflet','jquery-mobile','sweetalert','typeahead.js','soup','typehead.js', 'hui','bootstrap-languages','async','gulp','jquery-pjax','coffeescript','hammer.js','ace','leaflet','jquery-mobile','sweetalert','typeahead.js','soup','typehead.js',
'sails','codeigniter2']; 'sails','codeigniter2'];
public constructor(requestService: IRequestService) { public constructor(private xhr: XHRRequest) {
this.requestService = requestService;
} }
private isBowerFile(resource: string): boolean { public getDocumentSelector(): DocumentSelector {
return Strings.endsWith(resource, '/bower.json') || Strings.endsWith(resource, '/.bower.json'); return [{ language: 'json', pattern: '**/bower.json' }, { language: 'json', pattern: '**/.bower.json' }];
} }
public collectDefaultSuggestions(resource: string, result: ISuggestionsCollector): Thenable<any> { public collectDefaultSuggestions(resource: string, collector: ISuggestionsCollector): Thenable<any> {
if (this.isBowerFile(resource)) { let defaultValue = {
let defaultValue = { 'name': '{{name}}',
'name': '{{name}}', 'description': '{{description}}',
'description': '{{description}}', 'authors': [ '{{author}}' ],
'authors': [ '{{author}}' ], 'version': '{{1.0.0}}',
'version': '{{1.0.0}}', 'main': '{{pathToMain}}',
'main': '{{pathToMain}}', 'dependencies': {}
'dependencies': {} };
}; let proposal = new CompletionItem(localize('json.bower.default', 'Default bower.json'));
result.add({ kind: CompletionItemKind.Class, label: localize('json.bower.default', 'Default bower.json'), insertText: JSON.stringify(defaultValue, null, '\t'), documentation: '' }); proposal.kind = CompletionItemKind.Class;
} proposal.insertText = JSON.stringify(defaultValue, null, '\t');
return null; collector.add(proposal);
return Promise.resolve(null);
} }
public collectPropertySuggestions(resource: string, location: JSONLocation, currentWord: string, addValue: boolean, isLast:boolean, result: ISuggestionsCollector) : Thenable<any> { public collectPropertySuggestions(resource: string, location: Location, currentWord: string, addValue: boolean, isLast:boolean, collector: ISuggestionsCollector) : Thenable<any> {
if (this.isBowerFile(resource) && (location.matches(['dependencies']) || location.matches(['devDependencies']))) { if ((location.matches(['dependencies']) || location.matches(['devDependencies']))) {
if (currentWord.length > 0) { if (currentWord.length > 0) {
let queryUrl = 'https://bower.herokuapp.com/packages/search/' + encodeURIComponent(currentWord); let queryUrl = 'https://bower.herokuapp.com/packages/search/' + encodeURIComponent(currentWord);
return this.requestService({ return this.xhr({
url : queryUrl url : queryUrl
}).then((success) => { }).then((success) => {
if (success.status === 200) { if (success.status === 200) {
...@@ -66,76 +63,112 @@ export class BowerJSONContribution implements IJSONWorkerContribution { ...@@ -66,76 +63,112 @@ export class BowerJSONContribution implements IJSONWorkerContribution {
let description = results[i].description || ''; let description = results[i].description || '';
let insertText = JSON.stringify(name); let insertText = JSON.stringify(name);
if (addValue) { if (addValue) {
insertText += ': "{{*}}"'; insertText += ': "{{latest}}"';
if (!isLast) { if (!isLast) {
insertText += ','; insertText += ',';
} }
} }
result.add({ kind: CompletionItemKind.Property, label: name, insertText: insertText, documentation: description }); let proposal = new CompletionItem(name);
proposal.kind = CompletionItemKind.Property;
proposal.insertText = insertText;
proposal.documentation = description;
collector.add(proposal);
} }
result.setAsIncomplete(); collector.setAsIncomplete();
} }
} catch (e) { } catch (e) {
// ignore // ignore
} }
} else { } else {
result.error(localize('json.bower.error.repoaccess', 'Request to the bower repository failed: {0}', success.responseText)); collector.error(localize('json.bower.error.repoaccess', 'Request to the bower repository failed: {0}', success.responseText));
return 0; return 0;
} }
}, (error) => { }, (error) => {
result.error(localize('json.bower.error.repoaccess', 'Request to the bower repository failed: {0}', error.responseText)); collector.error(localize('json.bower.error.repoaccess', 'Request to the bower repository failed: {0}', error.responseText));
return 0; return 0;
}); });
} else { } else {
this.topRanked.forEach((name) => { this.topRanked.forEach((name) => {
let insertText = JSON.stringify(name); let insertText = JSON.stringify(name);
if (addValue) { if (addValue) {
insertText += ': "{{*}}"'; insertText += ': "{{latest}}"';
if (!isLast) { if (!isLast) {
insertText += ','; insertText += ',';
} }
} }
result.add({ kind: CompletionItemKind.Property, label: name, insertText: insertText, documentation: '' });
let proposal = new CompletionItem(name);
proposal.kind = CompletionItemKind.Property;
proposal.insertText = insertText;
proposal.documentation = '';
collector.add(proposal);
}); });
result.setAsIncomplete(); collector.setAsIncomplete();
return Promise.resolve(null);
} }
} }
return null; return null;
} }
public collectValueSuggestions(resource: string, location: JSONLocation, currentKey: string, result: ISuggestionsCollector): Thenable<any> { public collectValueSuggestions(resource: string, location: Location, collector: ISuggestionsCollector): Thenable<any> {
// not implemented. Could be do done calling the bower command. Waiting for web API: https://github.com/bower/registry/issues/26 // not implemented. Could be do done calling the bower command. Waiting for web API: https://github.com/bower/registry/issues/26
return null; let proposal = new CompletionItem(localize('json.bower.latest.version', 'latest'));
proposal.insertText = '"{{latest}}"';
proposal.kind = CompletionItemKind.Value;
proposal.documentation = 'The latest version of the package';
collector.add(proposal);
return Promise.resolve(null);
} }
public getInfoContribution(resource: string, location: JSONLocation): Thenable<MarkedString[]> { public resolveSuggestion(item: CompletionItem) : Thenable<CompletionItem> {
if (this.isBowerFile(resource) && (location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']))) { if (item.kind === CompletionItemKind.Property && item.documentation === '') {
let pack = location.getSegments()[location.getSegments().length - 1]; return this.getInfo(item.label).then(documentation => {
let htmlContent : MarkedString[] = []; if (documentation) {
htmlContent.push(localize('json.bower.package.hover', '{0}', pack)); item.documentation = documentation;
return item;
}
return null;
});
};
return null;
}
let queryUrl = 'https://bower.herokuapp.com/packages/' + encodeURIComponent(pack); private getInfo(pack: string): Thenable<string> {
let queryUrl = 'https://bower.herokuapp.com/packages/' + encodeURIComponent(pack);
return this.requestService({ return this.xhr({
url : queryUrl url : queryUrl
}).then((success) => { }).then((success) => {
try { try {
let obj = JSON.parse(success.responseText); let obj = JSON.parse(success.responseText);
if (obj && obj.url) { if (obj && obj.url) {
let url = obj.url; let url : string = obj.url;
if (Strings.startsWith(url, 'git://')) { if (url.indexOf('git://') === 0) {
url = url.substring(6); url = url.substring(6);
} }
if (Strings.endsWith(url, '.git')) { if (url.lastIndexOf('.git') === url.length - 4) {
url = url.substring(0, url.length - 4); url = url.substring(0, url.length - 4);
}
htmlContent.push(url);
} }
} catch (e) { return url;
// ignore }
} catch (e) {
// ignore
}
return void 0;
}, (error) => {
return void 0;
});
}
public getInfoContribution(resource: string, location: Location): Thenable<MarkedString[]> {
if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']))) {
let pack = location.segments[location.segments.length - 1];
let htmlContent : MarkedString[] = [];
htmlContent.push(localize('json.bower.package.hover', '{0}', pack));
return this.getInfo(pack).then(documentation => {
if (documentation) {
htmlContent.push(documentation);
} }
return htmlContent;
}, (error) => {
return htmlContent; return htmlContent;
}); });
} }
......
/*---------------------------------------------------------------------------------------------
* 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 {Location, getLocation, createScanner, SyntaxKind} from 'jsonc-parser';
import {basename} from 'path';
import {BowerJSONContribution} from './bowerJSONContribution';
import {PackageJSONContribution} from './packageJSONContribution';
import {XHRRequest} from 'request-light';
import {CompletionItem, CompletionItemProvider, CompletionList, TextDocument, Position, Hover, HoverProvider,
CancellationToken, Range, TextEdit, MarkedString, DocumentSelector, languages} from 'vscode';
export interface ISuggestionsCollector {
add(suggestion: CompletionItem): void;
error(message:string): void;
log(message:string): void;
setAsIncomplete(): void;
}
export interface IJSONContribution {
getDocumentSelector(): DocumentSelector;
getInfoContribution(fileName: string, location: Location) : Thenable<MarkedString[]>;
collectPropertySuggestions(fileName: string, location: Location, currentWord: string, addValue: boolean, isLast:boolean, result: ISuggestionsCollector) : Thenable<any>;
collectValueSuggestions(fileName: string, location: Location, result: ISuggestionsCollector): Thenable<any>;
collectDefaultSuggestions(fileName: string, result: ISuggestionsCollector): Thenable<any>;
resolveSuggestion?(item: CompletionItem): Thenable<CompletionItem>;
}
export function addJSONProviders(xhr: XHRRequest, subscriptions: { dispose(): any }[]) {
let contributions = [new PackageJSONContribution(xhr), new BowerJSONContribution(xhr)];
contributions.forEach(contribution => {
let selector = contribution.getDocumentSelector();
subscriptions.push(languages.registerCompletionItemProvider(selector, new JSONCompletionItemProvider(contribution), '.', '$'));
subscriptions.push(languages.registerHoverProvider(selector, new JSONHoverProvider(contribution)));
});
}
export class JSONHoverProvider implements HoverProvider {
constructor(private jsonContribution: IJSONContribution) {
}
public provideHover(document: TextDocument, position: Position, token: CancellationToken): Thenable<Hover> {
let fileName = basename(document.fileName);
let offset = document.offsetAt(position);
let location = getLocation(document.getText(), offset);
let node = location.previousNode;
if (node && node.offset <= offset && offset <= node.offset + node.length) {
let promise = this.jsonContribution.getInfoContribution(fileName, location);
if (promise) {
return promise.then(htmlContent => {
let range = new Range(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
let result: Hover = {
contents: htmlContent,
range: range
};
return result;
});
}
}
return null;
}
}
export class JSONCompletionItemProvider implements CompletionItemProvider {
constructor(private jsonContribution: IJSONContribution) {
}
public resolveCompletionItem(item: CompletionItem, token: CancellationToken) : Thenable<CompletionItem> {
if (this.jsonContribution.resolveSuggestion) {
let resolver = this.jsonContribution.resolveSuggestion(item);
if (resolver) {
return resolver;
}
}
return Promise.resolve(item);
}
public provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken): Thenable<CompletionList> {
let fileName = basename(document.fileName);
let currentWord = this.getCurrentWord(document, position);
let overwriteRange = null;
let items: CompletionItem[] = [];
let isIncomplete = false;
let offset = document.offsetAt(position);
let location = getLocation(document.getText(), offset);
let node = location.previousNode;
if (node && node.offset <= offset && offset <= node.offset + node.length && (node.type === 'property' || node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null')) {
overwriteRange = new Range(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
} else {
overwriteRange = new Range(document.positionAt(offset - currentWord.length), position);
}
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);
}
items.push(suggestion);
}
},
setAsIncomplete: () => isIncomplete = true,
error: (message: string) => console.error(message),
log: (message: string) => console.log(message)
};
let collectPromise : Thenable<any> = null;
if (location.completeProperty) {
let addValue = !location.previousNode || !location.previousNode.columnOffset;
let scanner = createScanner(document.getText(), true);
scanner.setPosition(offset);
scanner.scan();
let isLast = scanner.getToken() === SyntaxKind.CloseBraceToken || scanner.getToken() === SyntaxKind.EOF;
collectPromise = this.jsonContribution.collectPropertySuggestions(fileName, location, currentWord, addValue, isLast, collector);
} else {
if (location.segments.length === 0) {
collectPromise = this.jsonContribution.collectDefaultSuggestions(fileName, collector);
} else {
collectPromise = this.jsonContribution.collectValueSuggestions(fileName, location, collector);
}
}
if (collectPromise) {
return collectPromise.then(() => {
if (items.length > 0) {
return new CompletionList(items, isIncomplete);
}
return null;
});
}
return null;
}
private getCurrentWord(document: TextDocument, position: Position) {
var i = position.character - 1;
var text = document.lineAt(position.line).text;
while (i >= 0 && ' \t\n\r\v":{[,'.indexOf(text.charAt(i)) === -1) {
i--;
}
return text.substring(i+1, position.character);
}
}
\ No newline at end of file
...@@ -4,18 +4,17 @@ ...@@ -4,18 +4,17 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import {MarkedString, CompletionItemKind} from 'vscode-languageserver'; import {MarkedString, CompletionItemKind, CompletionItem, DocumentSelector} from 'vscode';
import Strings = require('../utils/strings'); import {IJSONContribution, ISuggestionsCollector} from './jsonContributions';
import {IJSONWorkerContribution, ISuggestionsCollector} from '../jsonContributions'; import {XHRRequest} from 'request-light';
import {IRequestService} from '../jsonSchemaService'; import {Location} from 'jsonc-parser';
import {JSONLocation} from '../jsonLocation';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
let LIMIT = 40; let LIMIT = 40;
export class PackageJSONContribution implements IJSONWorkerContribution { export class PackageJSONContribution implements IJSONContribution {
private mostDependedOn = [ 'lodash', 'async', 'underscore', 'request', 'commander', 'express', 'debug', 'chalk', 'colors', 'q', 'coffee-script', 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', 'mkdirp', 'optimist', 'through2', 'yeoman-generator', 'moment', 'bluebird', 'glob', 'gulp-util', 'minimist', 'cheerio', 'jade', 'redis', 'node-uuid',
...@@ -24,38 +23,36 @@ export class PackageJSONContribution implements IJSONWorkerContribution { ...@@ -24,38 +23,36 @@ export class PackageJSONContribution implements IJSONWorkerContribution {
'shelljs', 'gulp', 'yargs', 'browserify', 'minimatch', 'react', 'less', 'prompt', 'inquirer', 'ws', 'event-stream', 'inherits', 'mysql', 'esprima', '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']; 'jsdom', 'stylus', 'when', 'readable-stream', 'aws-sdk', 'concat-stream', 'chai', 'Thenable', 'wrench'];
private requestService : IRequestService; public getDocumentSelector(): DocumentSelector {
return [{ language: 'json', pattern: '**/package.json' }];
private isPackageJSONFile(resource: string): boolean {
return Strings.endsWith(resource, '/package.json');
} }
public constructor(requestService: IRequestService) { public constructor(private xhr: XHRRequest) {
this.requestService = requestService;
} }
public collectDefaultSuggestions(resource: string, result: ISuggestionsCollector): Thenable<any> { public collectDefaultSuggestions(fileName: string, result: ISuggestionsCollector): Thenable<any> {
if (this.isPackageJSONFile(resource)) { let defaultValue = {
let defaultValue = { 'name': '{{name}}',
'name': '{{name}}', 'description': '{{description}}',
'description': '{{description}}', 'author': '{{author}}',
'author': '{{author}}', 'version': '{{1.0.0}}',
'version': '{{1.0.0}}', 'main': '{{pathToMain}}',
'main': '{{pathToMain}}', 'dependencies': {}
'dependencies': {} };
}; let proposal = new CompletionItem(localize('json.package.default', 'Default package.json'));
result.add({ kind: CompletionItemKind.Module, label: localize('json.package.default', 'Default package.json'), insertText: JSON.stringify(defaultValue, null, '\t'), documentation: '' }); proposal.kind = CompletionItemKind.Module;
} proposal.insertText = JSON.stringify(defaultValue, null, '\t');
return null; result.add(proposal);
return Promise.resolve(null);
} }
public collectPropertySuggestions(resource: string, location: JSONLocation, currentWord: string, addValue: boolean, isLast:boolean, result: ISuggestionsCollector) : Thenable<any> { public collectPropertySuggestions(resource: string, location: Location, currentWord: string, addValue: boolean, isLast:boolean, collector: ISuggestionsCollector) : Thenable<any> {
if (this.isPackageJSONFile(resource) && (location.matches(['dependencies']) || location.matches(['devDependencies']) || location.matches(['optionalDependencies']) || location.matches(['peerDependencies']))) { if ((location.matches(['dependencies']) || location.matches(['devDependencies']) || location.matches(['optionalDependencies']) || location.matches(['peerDependencies']))) {
let queryUrl : string; let queryUrl : string;
if (currentWord.length > 0) { 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'; 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({ return this.xhr({
url : queryUrl url : queryUrl
}).then((success) => { }).then((success) => {
if (success.status === 200) { if (success.status === 200) {
...@@ -74,22 +71,26 @@ export class PackageJSONContribution implements IJSONWorkerContribution { ...@@ -74,22 +71,26 @@ export class PackageJSONContribution implements IJSONWorkerContribution {
insertText += ','; insertText += ',';
} }
} }
result.add({ kind: CompletionItemKind.Property, label: name, insertText: insertText, documentation: '' }); let proposal = new CompletionItem(name);
proposal.kind = CompletionItemKind.Property;
proposal.insertText = insertText;
proposal.documentation = '';
collector.add(proposal);
} }
} }
if (results.length === LIMIT) { if (results.length === LIMIT) {
result.setAsIncomplete(); collector.setAsIncomplete();
} }
} }
} catch (e) { } catch (e) {
// ignore // ignore
} }
} else { } else {
result.error(localize('json.npm.error.repoaccess', 'Request to the NPM repository failed: {0}', success.responseText)); collector.error(localize('json.npm.error.repoaccess', 'Request to the NPM repository failed: {0}', success.responseText));
return 0; return 0;
} }
}, (error) => { }, (error) => {
result.error(localize('json.npm.error.repoaccess', 'Request to the NPM repository failed: {0}', error.responseText)); collector.error(localize('json.npm.error.repoaccess', 'Request to the NPM repository failed: {0}', error.responseText));
return 0; return 0;
}); });
} else { } else {
...@@ -101,19 +102,25 @@ export class PackageJSONContribution implements IJSONWorkerContribution { ...@@ -101,19 +102,25 @@ export class PackageJSONContribution implements IJSONWorkerContribution {
insertText += ','; insertText += ',';
} }
} }
result.add({ kind: CompletionItemKind.Property, label: name, insertText: insertText, documentation: '' }); let proposal = new CompletionItem(name);
proposal.kind = CompletionItemKind.Property;
proposal.insertText = insertText;
proposal.documentation = '';
collector.add(proposal);
}); });
result.setAsIncomplete(); collector.setAsIncomplete();
return Promise.resolve(null);
} }
} }
return null; return null;
} }
public collectValueSuggestions(resource: string, location: JSONLocation, currentKey: string, result: ISuggestionsCollector): Thenable<any> { public collectValueSuggestions(fileName: string, location: Location, result: ISuggestionsCollector): Thenable<any> {
if (this.isPackageJSONFile(resource) && (location.matches(['dependencies']) || location.matches(['devDependencies']) || location.matches(['optionalDependencies']) || location.matches(['peerDependencies']))) { if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']) || location.matches(['optionalDependencies', '*']) || location.matches(['peerDependencies', '*']))) {
let currentKey = location.segments[location.segments.length - 1];
let queryUrl = 'http://registry.npmjs.org/' + encodeURIComponent(currentKey) + '/latest'; let queryUrl = 'http://registry.npmjs.org/' + encodeURIComponent(currentKey) + '/latest';
return this.requestService({ return this.xhr({
url : queryUrl url : queryUrl
}).then((success) => { }).then((success) => {
try { try {
...@@ -121,11 +128,25 @@ export class PackageJSONContribution implements IJSONWorkerContribution { ...@@ -121,11 +128,25 @@ export class PackageJSONContribution implements IJSONWorkerContribution {
if (obj && obj.version) { if (obj && obj.version) {
let version = obj.version; let version = obj.version;
let name = JSON.stringify(version); let name = JSON.stringify(version);
result.add({ kind: CompletionItemKind.Class, label: name, insertText: name, documentation: localize('json.npm.latestversion', 'The currently latest version of the package') }); let proposal = new CompletionItem(name);
proposal.kind = CompletionItemKind.Property;
proposal.insertText = name;
proposal.documentation = localize('json.npm.latestversion', 'The currently latest version of the package');
result.add(proposal);
name = JSON.stringify('^' + version); name = JSON.stringify('^' + version);
result.add({ kind: CompletionItemKind.Class, label: name, insertText: name, documentation: localize('json.npm.majorversion', 'Matches the most recent major version (1.x.x)') }); proposal = new CompletionItem(name);
proposal.kind = CompletionItemKind.Property;
proposal.insertText = name;
proposal.documentation = localize('json.npm.majorversion', 'Matches the most recent major version (1.x.x)');
result.add(proposal);
name = JSON.stringify('~' + version); name = JSON.stringify('~' + version);
result.add({ kind: CompletionItemKind.Class, label: name, insertText: name, documentation: localize('json.npm.minorversion', 'Matches the most recent minor version (1.2.x)') }); proposal = new CompletionItem(name);
proposal.kind = CompletionItemKind.Property;
proposal.insertText = name;
proposal.documentation = localize('json.npm.minorversion', 'Matches the most recent minor version (1.2.x)');
result.add(proposal);
} }
} catch (e) { } catch (e) {
// ignore // ignore
...@@ -138,33 +159,59 @@ export class PackageJSONContribution implements IJSONWorkerContribution { ...@@ -138,33 +159,59 @@ export class PackageJSONContribution implements IJSONWorkerContribution {
return null; return null;
} }
public getInfoContribution(resource: string, location: JSONLocation): Thenable<MarkedString[]> { public resolveSuggestion(item: CompletionItem) : Thenable<CompletionItem> {
if (this.isPackageJSONFile(resource) && (location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']) || location.matches(['optionalDependencies', '*']) || location.matches(['peerDependencies', '*']))) { if (item.kind === CompletionItemKind.Property && item.documentation === '') {
let pack = location.getSegments()[location.getSegments().length - 1]; return this.getInfo(item.label).then(infos => {
if (infos.length > 0) {
let htmlContent : MarkedString[] = []; item.documentation = infos[0];
htmlContent.push(localize('json.npm.package.hover', '{0}', pack)); if (infos.length > 1) {
item.detail = infos[1];
let queryUrl = 'http://registry.npmjs.org/' + encodeURIComponent(pack) + '/latest'; }
return item;
}
return null;
});
};
return null;
}
return this.requestService({ private getInfo(pack: string): Thenable<string[]> {
url : queryUrl let queryUrl = 'http://registry.npmjs.org/' + encodeURIComponent(pack) + '/latest';
}).then((success) => {
try { return this.xhr({
let obj = JSON.parse(success.responseText); url : queryUrl
if (obj) { }).then((success) => {
if (obj.description) { try {
htmlContent.push(obj.description); let obj = JSON.parse(success.responseText);
} if (obj) {
if (obj.version) { let result = [];
htmlContent.push(localize('json.npm.version.hover', 'Latest version: {0}', obj.version)); if (obj.description) {
} result.push(obj.description);
} }
} catch (e) { if (obj.version) {
// ignore result.push(localize('json.npm.version.hover', 'Latest version: {0}', obj.version));
}
return result;
} }
return htmlContent; } catch (e) {
}, (error) => { // ignore
}
return [];
}, (error) => {
return [];
});
}
public getInfoContribution(fileName: string, location: Location): Thenable<MarkedString[]> {
if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']) || location.matches(['optionalDependencies', '*']) || location.matches(['peerDependencies', '*']))) {
let pack = location.segments[location.segments.length - 1];
let htmlContent : MarkedString[] = [];
htmlContent.push(localize('json.npm.package.hover', '{0}', pack));
return this.getInfo(pack).then(infos => {
infos.forEach(info => {
htmlContent.push(info);
});
return htmlContent; return htmlContent;
}); });
} }
......
...@@ -2,14 +2,26 @@ ...@@ -2,14 +2,26 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
export function localize2(key: string, message: string, ...formatArgs: any[]) { import {addJSONProviders} from './features/jsonContributions';
if (formatArgs.length > 0) { import * as httpRequest from 'request-light';
return message.replace(/\{(\d+)\}/g, function(match, rest) {
var index = rest[0]; import {ExtensionContext, env, workspace} from 'vscode';
return typeof formatArgs[index] !== 'undefined' ? formatArgs[index] : match;
}); import * as nls from 'vscode-nls';
}
return message; export function activate(context: ExtensionContext): any {
} nls.config({locale: env.language});
\ No newline at end of file
configureHttpRequest();
workspace.onDidChangeConfiguration(e => configureHttpRequest());
addJSONProviders(httpRequest.xhr, context.subscriptions);
}
function configureHttpRequest() {
let httpSettings = workspace.getConfiguration('http');
httpRequest.configure(httpSettings.get<string>('proxy'), httpSettings.get<boolean>('proxyStrictSSL'));
}
...@@ -3,18 +3,8 @@ ...@@ -3,18 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
declare module 'http-proxy-agent' { /// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../src/typings/mocha.d.ts'/>
interface IHttpProxyAgentOptions { /// <reference path='../../../../extensions/node.d.ts'/>
host: string; /// <reference path='../../../../extensions/lib.core.d.ts'/>
port: number; /// <reference path='../../../../extensions/declares.d.ts'/>
auth?: string; \ No newline at end of file
}
class HttpProxyAgent {
constructor(proxy: string);
constructor(opts: IHttpProxyAgentOptions);
}
export = HttpProxyAgent;
}
\ No newline at end of file
{
"compilerOptions": {
"noLib": true,
"target": "es5",
"module": "commonjs",
"outDir": "./out"
},
"exclude": [
"node_modules"
]
}
\ No newline at end of file
...@@ -8,8 +8,8 @@ ...@@ -8,8 +8,8 @@
"node": "*" "node": "*"
}, },
"dependencies": { "dependencies": {
"http-proxy-agent": "^0.2.6", "request-light": "^0.0.3",
"https-proxy-agent": "^0.3.5", "jsonc-parser": "^0.0.1",
"vscode-languageserver": "^1.3.0", "vscode-languageserver": "^1.3.0",
"vscode-nls": "^1.0.4" "vscode-nls": "^1.0.4"
}, },
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
import Parser = require('./jsonParser'); import Parser = require('./jsonParser');
import SchemaService = require('./jsonSchemaService'); import SchemaService = require('./jsonSchemaService');
import JsonSchema = require('./json-toolbox/jsonSchema'); import JsonSchema = require('./jsonSchema');
import {IJSONWorkerContribution} from './jsonContributions'; import {IJSONWorkerContribution} from './jsonContributions';
import {CompletionItem, CompletionItemKind, CompletionList, ITextDocument, TextDocumentPosition, Range, TextEdit, RemoteConsole} from 'vscode-languageserver'; import {CompletionItem, CompletionItemKind, CompletionList, ITextDocument, TextDocumentPosition, Range, TextEdit, RemoteConsole} from 'vscode-languageserver';
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import Json = require('./json-toolbox/json'); import Json = require('jsonc-parser');
import {ITextDocument, Range, Position, FormattingOptions, TextEdit} from 'vscode-languageserver'; import {ITextDocument, Range, Position, FormattingOptions, TextEdit} from 'vscode-languageserver';
export function format(document: ITextDocument, range: Range, options: FormattingOptions): TextEdit[] { export function format(document: ITextDocument, range: Range, options: FormattingOptions): TextEdit[] {
......
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import Json = require('./json-toolbox/json'); import Json = require('jsonc-parser');
import JsonSchema = require('./json-toolbox/jsonSchema'); import JsonSchema = require('./jsonSchema');
import {JSONLocation} from './jsonLocation'; import {JSONLocation} from './jsonLocation';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
...@@ -898,7 +898,7 @@ export function parse(text: string, config = new JSONDocumentConfig()): JSONDocu ...@@ -898,7 +898,7 @@ export function parse(text: string, config = new JSONDocumentConfig()): JSONDocu
if (_scanner.getToken() === Json.SyntaxKind.Unknown) { if (_scanner.getToken() === Json.SyntaxKind.Unknown) {
// give a more helpful error message // give a more helpful error message
let value = _scanner.getTokenValue(); let value = _scanner.getTokenValue();
if (value.length > 0 && (value.charAt(0) === '\'' || Json.isLetter(value.charAt(0).charCodeAt(0)))) { if (value.match(/^['\w]/)) {
_error(localize('DoubleQuotesExpected', 'Property keys must be doublequoted')); _error(localize('DoubleQuotesExpected', 'Property keys must be doublequoted'));
} }
} }
......
...@@ -4,9 +4,9 @@ ...@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import Json = require('./json-toolbox/json'); import Json = require('jsonc-parser');
import {IJSONSchema} from './json-toolbox/jsonSchema'; import {IJSONSchema} from './jsonSchema';
import {IXHROptions, IXHRResponse, getErrorStatusDescription} from './utils/httpRequest'; import {XHROptions, XHRResponse, getErrorStatusDescription} from 'request-light';
import URI from './utils/uri'; import URI from './utils/uri';
import Strings = require('./utils/strings'); import Strings = require('./utils/strings');
import Parser = require('./jsonParser'); import Parser = require('./jsonParser');
...@@ -208,7 +208,7 @@ export interface IWorkspaceContextService { ...@@ -208,7 +208,7 @@ export interface IWorkspaceContextService {
} }
export interface IRequestService { export interface IRequestService {
(options: IXHROptions): Thenable<IXHRResponse>; (options: XHROptions): Thenable<XHRResponse>;
} }
export class JSONSchemaService implements IJSONSchemaService { export class JSONSchemaService implements IJSONSchemaService {
...@@ -362,7 +362,7 @@ export class JSONSchemaService implements IJSONSchemaService { ...@@ -362,7 +362,7 @@ export class JSONSchemaService implements IJSONSchemaService {
let errors = jsonErrors.length ? [localize('json.schema.invalidFormat', 'Unable to parse content from \'{0}\': {1}.', toDisplayString(url), jsonErrors[0])] : []; let errors = jsonErrors.length ? [localize('json.schema.invalidFormat', 'Unable to parse content from \'{0}\': {1}.', toDisplayString(url), jsonErrors[0])] : [];
return new UnresolvedSchema(schemaContent, errors); return new UnresolvedSchema(schemaContent, errors);
}, },
(error: IXHRResponse) => { (error: XHRResponse) => {
let errorMessage = localize('json.schema.unabletoload', 'Unable to load schema from \'{0}\': {1}', toDisplayString(url), error.responseText || getErrorStatusDescription(error.status) || error.toString()); let errorMessage = 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]); return new UnresolvedSchema(<IJSONSchema>{}, [errorMessage]);
} }
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
import {MarkedString, CompletionItemKind, CompletionItem} from 'vscode-languageserver'; import {MarkedString, CompletionItemKind, CompletionItem} from 'vscode-languageserver';
import Strings = require('../utils/strings'); import Strings = require('../utils/strings');
import {IXHRResponse, getErrorStatusDescription} from '../utils/httpRequest'; import {XHRResponse, getErrorStatusDescription} from 'request-light';
import {IJSONWorkerContribution, ISuggestionsCollector} from '../jsonContributions'; import {IJSONWorkerContribution, ISuggestionsCollector} from '../jsonContributions';
import {IRequestService} from '../jsonSchemaService'; import {IRequestService} from '../jsonSchemaService';
import {JSONLocation} from '../jsonLocation'; import {JSONLocation} from '../jsonLocation';
...@@ -131,7 +131,7 @@ export class ProjectJSONContribution implements IJSONWorkerContribution { ...@@ -131,7 +131,7 @@ export class ProjectJSONContribution implements IJSONWorkerContribution {
} }
} }
return Promise.reject<T>(localize('json.nugget.error.indexaccess', 'Request to {0} failed: {1}', url, success.responseText)); return Promise.reject<T>(localize('json.nugget.error.indexaccess', 'Request to {0} failed: {1}', url, success.responseText));
}, (error: IXHRResponse) => { }, (error: XHRResponse) => {
return Promise.reject<T>(localize('json.nugget.error.access', 'Request to {0} failed: {1}', url, getErrorStatusDescription(error.status))); return Promise.reject<T>(localize('json.nugget.error.access', 'Request to {0} failed: {1}', url, getErrorStatusDescription(error.status)));
}); });
} }
......
...@@ -13,7 +13,7 @@ import { ...@@ -13,7 +13,7 @@ import {
DocumentRangeFormattingParams, NotificationType, RequestType DocumentRangeFormattingParams, NotificationType, RequestType
} from 'vscode-languageserver'; } from 'vscode-languageserver';
import {xhr, IXHROptions, IXHRResponse, configure as configureHttpRequests} from './utils/httpRequest'; import {xhr, XHROptions, XHRResponse, configure as configureHttpRequests} from 'request-light';
import path = require('path'); import path = require('path');
import fs = require('fs'); import fs = require('fs');
import URI from './utils/uri'; import URI from './utils/uri';
...@@ -22,12 +22,10 @@ import {JSONSchemaService, ISchemaAssociations} from './jsonSchemaService'; ...@@ -22,12 +22,10 @@ import {JSONSchemaService, ISchemaAssociations} from './jsonSchemaService';
import {parse as parseJSON, ObjectASTNode, JSONDocument} from './jsonParser'; import {parse as parseJSON, ObjectASTNode, JSONDocument} from './jsonParser';
import {JSONCompletion} from './jsonCompletion'; import {JSONCompletion} from './jsonCompletion';
import {JSONHover} from './jsonHover'; import {JSONHover} from './jsonHover';
import {IJSONSchema} from './json-toolbox/jsonSchema'; import {IJSONSchema} from './jsonSchema';
import {JSONDocumentSymbols} from './jsonDocumentSymbols'; import {JSONDocumentSymbols} from './jsonDocumentSymbols';
import {format as formatJSON} from './jsonFormatter'; import {format as formatJSON} from './jsonFormatter';
import {schemaContributions} from './configuration'; import {schemaContributions} from './configuration';
import {BowerJSONContribution} from './jsoncontributions/bowerJSONContribution';
import {PackageJSONContribution} from './jsoncontributions/packageJSONContribution';
import {ProjectJSONContribution} from './jsoncontributions/projectJSONContribution'; import {ProjectJSONContribution} from './jsoncontributions/projectJSONContribution';
import {GlobPatternContribution} from './jsoncontributions/globPatternContribution'; import {GlobPatternContribution} from './jsoncontributions/globPatternContribution';
import {FileAssociationContribution} from './jsoncontributions/fileAssociationContribution'; import {FileAssociationContribution} from './jsoncontributions/fileAssociationContribution';
...@@ -94,10 +92,10 @@ let telemetry = { ...@@ -94,10 +92,10 @@ let telemetry = {
} }
}; };
let request = (options: IXHROptions): Thenable<IXHRResponse> => { let request = (options: XHROptions): Thenable<XHRResponse> => {
if (Strings.startsWith(options.url, 'file://')) { if (Strings.startsWith(options.url, 'file://')) {
let fsPath = URI.parse(options.url).fsPath; let fsPath = URI.parse(options.url).fsPath;
return new Promise<IXHRResponse>((c, e) => { return new Promise<XHRResponse>((c, e) => {
fs.readFile(fsPath, 'UTF-8', (err, result) => { fs.readFile(fsPath, 'UTF-8', (err, result) => {
err ? e({ responseText: '', status: 404 }) : c({ responseText: result.toString(), status: 200 }); err ? e({ responseText: '', status: 404 }) : c({ responseText: result.toString(), status: 200 });
}); });
...@@ -120,8 +118,6 @@ let request = (options: IXHROptions): Thenable<IXHRResponse> => { ...@@ -120,8 +118,6 @@ let request = (options: IXHROptions): Thenable<IXHRResponse> => {
let contributions = [ let contributions = [
new ProjectJSONContribution(request), new ProjectJSONContribution(request),
new PackageJSONContribution(request),
new BowerJSONContribution(request),
new GlobPatternContribution(), new GlobPatternContribution(),
filesAssociationContribution filesAssociationContribution
]; ];
......
...@@ -7,17 +7,17 @@ ...@@ -7,17 +7,17 @@
import assert = require('assert'); import assert = require('assert');
import Parser = require('../jsonParser'); import Parser = require('../jsonParser');
import SchemaService = require('../jsonSchemaService'); import SchemaService = require('../jsonSchemaService');
import JsonSchema = require('../json-toolbox/jsonSchema'); import JsonSchema = require('../jsonSchema');
import {JSONCompletion} from '../jsonCompletion'; import {JSONCompletion} from '../jsonCompletion';
import {IXHROptions, IXHRResponse} from '../utils/httpRequest'; import {XHROptions, XHRResponse} from 'request-light';
import {CompletionItem, CompletionItemKind, CompletionOptions, ITextDocument, TextDocumentIdentifier, TextDocumentPosition, Range, Position, TextEdit} from 'vscode-languageserver'; import {CompletionItem, CompletionItemKind, CompletionOptions, ITextDocument, TextDocumentIdentifier, TextDocumentPosition, Range, Position, TextEdit} from 'vscode-languageserver';
import {applyEdits} from './textEditSupport'; import {applyEdits} from './textEditSupport';
suite('JSON Completion', () => { suite('JSON Completion', () => {
var requestService = function(options: IXHROptions): Promise<IXHRResponse> { var requestService = function(options: XHROptions): Promise<XHRResponse> {
return Promise.reject<IXHRResponse>({ responseText: '', status: 404 }); return Promise.reject<XHRResponse>({ responseText: '', status: 404 });
} }
var assertSuggestion = function(completions: CompletionItem[], label: string, documentation?: string, document?: ITextDocument, resultText?: string) { var assertSuggestion = function(completions: CompletionItem[], label: string, documentation?: string, document?: ITextDocument, resultText?: string) {
......
...@@ -7,9 +7,8 @@ ...@@ -7,9 +7,8 @@
import assert = require('assert'); import assert = require('assert');
import Parser = require('../jsonParser'); import Parser = require('../jsonParser');
import SchemaService = require('../jsonSchemaService'); import SchemaService = require('../jsonSchemaService');
import JsonSchema = require('../json-toolbox/jsonSchema'); import JsonSchema = require('../jsonSchema');
import {JSONCompletion} from '../jsonCompletion'; import {JSONCompletion} from '../jsonCompletion';
import {IXHROptions, IXHRResponse} from '../utils/httpRequest';
import {JSONDocumentSymbols} from '../jsonDocumentSymbols'; import {JSONDocumentSymbols} from '../jsonDocumentSymbols';
import {SymbolInformation, SymbolKind, TextDocumentIdentifier, ITextDocument, TextDocumentPosition, Range, Position, TextEdit} from 'vscode-languageserver'; import {SymbolInformation, SymbolKind, TextDocumentIdentifier, ITextDocument, TextDocumentPosition, Range, Position, TextEdit} from 'vscode-languageserver';
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import Json = require('../json-toolbox/json'); import Json = require('jsonc-parser');
import {ITextDocument, DocumentFormattingParams, Range, Position, FormattingOptions, TextEdit} from 'vscode-languageserver'; import {ITextDocument, DocumentFormattingParams, Range, Position, FormattingOptions, TextEdit} from 'vscode-languageserver';
import Formatter = require('../jsonFormatter'); import Formatter = require('../jsonFormatter');
import assert = require('assert'); import assert = require('assert');
......
...@@ -7,9 +7,9 @@ ...@@ -7,9 +7,9 @@
import assert = require('assert'); import assert = require('assert');
import Parser = require('../jsonParser'); import Parser = require('../jsonParser');
import SchemaService = require('../jsonSchemaService'); import SchemaService = require('../jsonSchemaService');
import JsonSchema = require('../json-toolbox/jsonSchema'); import JsonSchema = require('../jsonSchema');
import {JSONCompletion} from '../jsonCompletion'; import {JSONCompletion} from '../jsonCompletion';
import {IXHROptions, IXHRResponse} from '../utils/httpRequest'; import {XHROptions, XHRResponse} from 'request-light';
import {JSONHover} from '../jsonHover'; import {JSONHover} from '../jsonHover';
import {Hover, ITextDocument, TextDocumentIdentifier, TextDocumentPosition, Range, Position, TextEdit} from 'vscode-languageserver'; import {Hover, ITextDocument, TextDocumentIdentifier, TextDocumentPosition, Range, Position, TextEdit} from 'vscode-languageserver';
...@@ -30,8 +30,8 @@ suite('JSON Hover', () => { ...@@ -30,8 +30,8 @@ suite('JSON Hover', () => {
return hoverProvider.doHover(document, textDocumentLocation, jsonDoc); return hoverProvider.doHover(document, textDocumentLocation, jsonDoc);
} }
var requestService = function(options: IXHROptions): Promise<IXHRResponse> { var requestService = function(options: XHROptions): Promise<XHRResponse> {
return Promise.reject<IXHRResponse>({ responseText: '', status: 404 }); return Promise.reject<XHRResponse>({ responseText: '', status: 404 });
} }
test('Simple schema', function(testDone) { test('Simple schema', function(testDone) {
......
/*---------------------------------------------------------------------------------------------
* 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 * as assert from 'assert';
import { SyntaxKind, createScanner, parse, getLocation, ParseErrorCode, getParseErrorMessage } from '../json-toolbox/json';
function assertKinds(text:string, ...kinds:SyntaxKind[]):void {
var _json = createScanner(text);
var kind: SyntaxKind;
while((kind = _json.scan()) !== SyntaxKind.EOF) {
assert.equal(kind, kinds.shift());
}
assert.equal(kinds.length, 0);
}
function assertValidParse(input:string, expected:any) : void {
var errors : {error: ParseErrorCode}[] = [];
var actual = parse(input, errors);
if (errors.length !== 0) {
assert(false, getParseErrorMessage(errors[0].error));
}
assert.deepEqual(actual, expected);
}
function assertInvalidParse(input:string, expected:any) : void {
var errors : {error: ParseErrorCode}[] = [];
var actual = parse(input, errors);
assert(errors.length > 0);
assert.deepEqual(actual, expected);
}
function assertLocation(input:string, expectedSegments: string[], expectedNodeType: string, expectedCompleteProperty: boolean) : void {
var errors : {error: ParseErrorCode}[] = [];
var offset = input.indexOf('|');
input = input.substring(0, offset) + input.substring(offset+1, input.length);
var actual = getLocation(input, offset);
assert(actual);
assert.deepEqual(actual.segments, expectedSegments, input);
assert.equal(actual.previousNode && actual.previousNode.type, expectedNodeType, input);
assert.equal(actual.completeProperty, expectedCompleteProperty, input);
}
suite('JSON', () => {
test('tokens', () => {
assertKinds('{', SyntaxKind.OpenBraceToken);
assertKinds('}', SyntaxKind.CloseBraceToken);
assertKinds('[', SyntaxKind.OpenBracketToken);
assertKinds(']', SyntaxKind.CloseBracketToken);
assertKinds(':', SyntaxKind.ColonToken);
assertKinds(',', SyntaxKind.CommaToken);
});
test('comments', () => {
assertKinds('// this is a comment', SyntaxKind.LineCommentTrivia);
assertKinds('// this is a comment\n', SyntaxKind.LineCommentTrivia, SyntaxKind.LineBreakTrivia);
assertKinds('/* this is a comment*/', SyntaxKind.BlockCommentTrivia);
assertKinds('/* this is a \r\ncomment*/', SyntaxKind.BlockCommentTrivia);
assertKinds('/* this is a \ncomment*/', SyntaxKind.BlockCommentTrivia);
// unexpected end
assertKinds('/* this is a', SyntaxKind.BlockCommentTrivia);
assertKinds('/* this is a \ncomment', SyntaxKind.BlockCommentTrivia);
// broken comment
assertKinds('/ ttt', SyntaxKind.Unknown, SyntaxKind.Trivia, SyntaxKind.Unknown);
});
test('strings', () => {
assertKinds('"test"', SyntaxKind.StringLiteral);
assertKinds('"\\""', SyntaxKind.StringLiteral);
assertKinds('"\\/"', SyntaxKind.StringLiteral);
assertKinds('"\\b"', SyntaxKind.StringLiteral);
assertKinds('"\\f"', SyntaxKind.StringLiteral);
assertKinds('"\\n"', SyntaxKind.StringLiteral);
assertKinds('"\\r"', SyntaxKind.StringLiteral);
assertKinds('"\\t"', SyntaxKind.StringLiteral);
assertKinds('"\\v"', SyntaxKind.StringLiteral);
assertKinds('"\u88ff"', SyntaxKind.StringLiteral);
// unexpected end
assertKinds('"test', SyntaxKind.StringLiteral);
assertKinds('"test\n"', SyntaxKind.StringLiteral, SyntaxKind.LineBreakTrivia, SyntaxKind.StringLiteral);
});
test('numbers', () => {
assertKinds('0', SyntaxKind.NumericLiteral);
assertKinds('0.1', SyntaxKind.NumericLiteral);
assertKinds('-0.1', SyntaxKind.NumericLiteral);
assertKinds('-1', SyntaxKind.NumericLiteral);
assertKinds('1', SyntaxKind.NumericLiteral);
assertKinds('123456789', SyntaxKind.NumericLiteral);
assertKinds('10', SyntaxKind.NumericLiteral);
assertKinds('90', SyntaxKind.NumericLiteral);
assertKinds('90E+123', SyntaxKind.NumericLiteral);
assertKinds('90e+123', SyntaxKind.NumericLiteral);
assertKinds('90e-123', SyntaxKind.NumericLiteral);
assertKinds('90E-123', SyntaxKind.NumericLiteral);
assertKinds('90E123', SyntaxKind.NumericLiteral);
assertKinds('90e123', SyntaxKind.NumericLiteral);
// zero handling
assertKinds('01', SyntaxKind.NumericLiteral, SyntaxKind.NumericLiteral);
assertKinds('-01', SyntaxKind.NumericLiteral, SyntaxKind.NumericLiteral);
// unexpected end
assertKinds('-', SyntaxKind.Unknown);
assertKinds('.0', SyntaxKind.Unknown);
});
test('keywords: true, false, null', () => {
assertKinds('true', SyntaxKind.TrueKeyword);
assertKinds('false', SyntaxKind.FalseKeyword);
assertKinds('null', SyntaxKind.NullKeyword);
assertKinds('true false null',
SyntaxKind.TrueKeyword,
SyntaxKind.Trivia,
SyntaxKind.FalseKeyword,
SyntaxKind.Trivia,
SyntaxKind.NullKeyword);
// invalid words
assertKinds('nulllll', SyntaxKind.Unknown);
assertKinds('True', SyntaxKind.Unknown);
assertKinds('foo-bar', SyntaxKind.Unknown);
assertKinds('foo bar', SyntaxKind.Unknown, SyntaxKind.Trivia, SyntaxKind.Unknown);
});
test('trivia', () => {
assertKinds(' ', SyntaxKind.Trivia);
assertKinds(' \t ', SyntaxKind.Trivia);
assertKinds(' \t \n \t ', SyntaxKind.Trivia, SyntaxKind.LineBreakTrivia, SyntaxKind.Trivia);
assertKinds('\r\n', SyntaxKind.LineBreakTrivia);
assertKinds('\r', SyntaxKind.LineBreakTrivia);
assertKinds('\n', SyntaxKind.LineBreakTrivia);
assertKinds('\n\r', SyntaxKind.LineBreakTrivia, SyntaxKind.LineBreakTrivia);
assertKinds('\n \n', SyntaxKind.LineBreakTrivia, SyntaxKind.Trivia, SyntaxKind.LineBreakTrivia);
});
test('parse: literals', () => {
assertValidParse('true', true);
assertValidParse('false', false);
assertValidParse('null', null);
assertValidParse('"foo"', 'foo');
assertValidParse('"\\"-\\\\-\\/-\\b-\\f-\\n-\\r-\\t"', '"-\\-/-\b-\f-\n-\r-\t');
assertValidParse('"\\u00DC"', 'Ü');
assertValidParse('9', 9);
assertValidParse('-9', -9);
assertValidParse('0.129', 0.129);
assertValidParse('23e3', 23e3);
assertValidParse('1.2E+3', 1.2E+3);
assertValidParse('1.2E-3', 1.2E-3);
});
test('parse: objects', () => {
assertValidParse('{}', {});
assertValidParse('{ "foo": true }', { foo: true });
assertValidParse('{ "bar": 8, "xoo": "foo" }', { bar: 8, xoo: 'foo' });
assertValidParse('{ "hello": [], "world": {} }', { hello: [], world: {} });
assertValidParse('{ "a": false, "b": true, "c": [ 7.4 ] }', { a: false, b: true, c: [ 7.4 ]});
assertValidParse('{ "lineComment": "//", "blockComment": ["/*", "*/"], "brackets": [ ["{", "}"], ["[", "]"], ["(", ")"] ] }', { lineComment: '//', blockComment: ["/*", "*/"], brackets: [ ["{", "}"], ["[", "]"], ["(", ")"] ] });
assertValidParse('{ "hello": [], "world": {} }', { hello: [], world: {} });
assertValidParse('{ "hello": { "again": { "inside": 5 }, "world": 1 }}', { hello: { again: { inside: 5 }, world: 1 }});
});
test('parse: arrays', () => {
assertValidParse('[]', []);
assertValidParse('[ [], [ [] ]]', [[], [[]]]);
assertValidParse('[ 1, 2, 3 ]', [ 1, 2, 3 ]);
assertValidParse('[ { "a": null } ]', [ { a: null } ]);
});
test('parse: objects with errors', () => {
assertInvalidParse('{,}', {});
assertInvalidParse('{ "foo": true, }', { foo: true });
assertInvalidParse('{ "bar": 8 "xoo": "foo" }', { bar: 8, xoo: 'foo' });
assertInvalidParse('{ ,"bar": 8 }', { bar: 8 });
assertInvalidParse('{ ,"bar": 8, "foo" }', { bar: 8 });
assertInvalidParse('{ "bar": 8, "foo": }', { bar: 8 });
assertInvalidParse('{ 8, "foo": 9 }', { foo: 9 });
});
test('parse: array with errors', () => {
assertInvalidParse('[,]', []);
assertInvalidParse('[ 1, 2, ]', [ 1, 2]);
assertInvalidParse('[ 1 2, 3 ]', [ 1, 2, 3 ]);
assertInvalidParse('[ ,1, 2, 3 ]', [ 1, 2, 3 ]);
assertInvalidParse('[ ,1, 2, 3, ]', [ 1, 2, 3 ]);
});
test('location: properties', () => {
assertLocation('|{ "foo": "bar" }', [], void 0, false);
assertLocation('{| "foo": "bar" }', [], void 0, true);
assertLocation('{ |"foo": "bar" }', ["foo" ], "property", true);
assertLocation('{ "foo|": "bar" }', [ "foo" ], "property", true);
assertLocation('{ "foo"|: "bar" }', ["foo" ], "property", true);
assertLocation('{ "foo": "bar"| }', ["foo" ], "string", false);
assertLocation('{ "foo":| "bar" }', ["foo" ], void 0, false);
assertLocation('{ "foo": {"bar|": 1, "car": 2 } }', ["foo", "bar" ], "property", true);
assertLocation('{ "foo": {"bar": 1|, "car": 3 } }', ["foo", "bar" ], "number", false);
assertLocation('{ "foo": {"bar": 1,| "car": 4 } }', ["foo"], void 0, true);
assertLocation('{ "foo": {"bar": 1, "ca|r": 5 } }', ["foo", "car" ], "property", true);
assertLocation('{ "foo": {"bar": 1, "car": 6| } }', ["foo", "car" ], "number", false);
assertLocation('{ "foo": {"bar": 1, "car": 7 }| }', ["foo"], void 0, false);
assertLocation('{ "foo": {"bar": 1, "car": 8 },| "goo": {} }', [], void 0, true);
assertLocation('{ "foo": {"bar": 1, "car": 9 }, "go|o": {} }', ["goo" ], "property", true);
});
test('location: arrays', () => {
assertLocation('|["foo", null ]', [], void 0, false);
assertLocation('[|"foo", null ]', ["[0]"], "string", false);
assertLocation('["foo"|, null ]', ["[0]"], "string", false);
assertLocation('["foo",| null ]', ["[1]"], void 0, false);
assertLocation('["foo", |null ]', ["[1]"], "null", false);
assertLocation('["foo", null,| ]', ["[2]"], void 0, false);
});
});
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
import assert = require('assert'); import assert = require('assert');
import Parser = require('../jsonParser'); import Parser = require('../jsonParser');
import SchemaService = require('../jsonSchemaService'); import SchemaService = require('../jsonSchemaService');
import JsonSchema = require('../json-toolbox/jsonSchema'); import JsonSchema = require('../jsonSchema');
suite('JSON Parser', () => { suite('JSON Parser', () => {
......
...@@ -6,12 +6,12 @@ ...@@ -6,12 +6,12 @@
import assert = require('assert'); import assert = require('assert');
import SchemaService = require('../jsonSchemaService'); import SchemaService = require('../jsonSchemaService');
import JsonSchema = require('../json-toolbox/jsonSchema'); import JsonSchema = require('../jsonSchema');
import Json = require('../json-toolbox/json'); import Json = require('jsonc-parser');
import Parser = require('../jsonParser'); import Parser = require('../jsonParser');
import fs = require('fs'); import fs = require('fs');
import path = require('path'); import path = require('path');
import {IXHROptions, IXHRResponse} from '../utils/httpRequest'; import {XHROptions, XHRResponse} from 'request-light';
suite('JSON Schema', () => { suite('JSON Schema', () => {
...@@ -26,21 +26,21 @@ suite('JSON Schema', () => { ...@@ -26,21 +26,21 @@ suite('JSON Schema', () => {
'http://schema.management.azure.com/schemas/2015-08-01/Microsoft.Compute.json': 'Microsoft.Compute.json' 'http://schema.management.azure.com/schemas/2015-08-01/Microsoft.Compute.json': 'Microsoft.Compute.json'
} }
var requestServiceMock = function (options:IXHROptions) : Promise<IXHRResponse> { var requestServiceMock = function (options:XHROptions) : Promise<XHRResponse> {
var uri = options.url; var uri = options.url;
if (uri.length && uri[uri.length - 1] === '#') { if (uri.length && uri[uri.length - 1] === '#') {
uri = uri.substr(0, uri.length - 1); uri = uri.substr(0, uri.length - 1);
} }
var fileName = fixureDocuments[uri]; var fileName = fixureDocuments[uri];
if (fileName) { if (fileName) {
return new Promise<IXHRResponse>((c, e) => { return new Promise<XHRResponse>((c, e) => {
var fixturePath = path.join(__dirname, '../../src/test/fixtures', fileName); var fixturePath = path.join(__dirname, '../../src/test/fixtures', fileName);
fs.readFile(fixturePath, 'UTF-8', (err, result) => { fs.readFile(fixturePath, 'UTF-8', (err, result) => {
err ? e({ responseText: '', status: 404 }) : c({ responseText: result.toString(), status: 200 }) err ? e({ responseText: '', status: 404 }) : c({ responseText: result.toString(), status: 200 })
}); });
}); });
} }
return Promise.reject<IXHRResponse>({ responseText: '', status: 404 }); return Promise.reject<XHRResponse>({ responseText: '', status: 404 });
} }
test('Resolving $refs', function(testDone) { test('Resolving $refs', function(testDone) {
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'https-proxy-agent' {
import * as tls from 'tls';
interface IHttpsProxyAgentOptions extends tls.ConnectionOptions {
host: string;
port: number;
auth?: string;
secureProxy?: boolean;
secureEndpoint?: boolean;
}
class HttpsProxyAgent {
constructor(proxy: string);
constructor(opts: IHttpsProxyAgentOptions);
}
export = HttpsProxyAgent;
}
\ 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 { parse as parseUrl } from 'url';
import { getProxyAgent } from './proxy';
import https = require('https');
import http = require('http');
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export interface IXHROptions {
type?: string;
url?: string;
user?: string;
password?: string;
headers?: any;
timeout?: number;
data?: any;
agent?: any;
strictSSL?: boolean;
responseType?: string;
followRedirects?: number;
}
export interface IXHRResponse {
responseText: string;
status: number;
}
let proxyUrl: string = null;
let strictSSL: boolean = true;
function assign(destination: any, ...sources: any[]): any {
sources.forEach(source => Object.keys(source).forEach((key) => destination[key] = source[key]));
return destination;
}
export function configure(_proxyUrl: string, _strictSSL: boolean): void {
proxyUrl = _proxyUrl;
strictSSL = _strictSSL;
}
export function xhr(options: IXHROptions): Promise<IXHRResponse> {
const agent = getProxyAgent(options.url, { proxyUrl, strictSSL });
options = assign({}, options);
options = assign(options, { agent, strictSSL });
if (typeof options.followRedirects !== 'number') {
options.followRedirects = 5;
}
return request(options).then(result => new Promise<IXHRResponse>((c, e) => {
let res = result.res;
let data: string[] = [];
res.on('data', c => data.push(c));
res.on('end', () => {
if (options.followRedirects > 0 && (res.statusCode >= 300 && res.statusCode <= 303 || res.statusCode === 307)) {
let location = res.headers['location'];
if (location) {
let newOptions = {
type: options.type, url: location, user: options.user, password: options.password, responseType: options.responseType, headers: options.headers,
timeout: options.timeout, followRedirects: options.followRedirects - 1, data: options.data
};
xhr(newOptions).then(c, e);
return;
}
}
let response: IXHRResponse = {
responseText: data.join(''),
status: res.statusCode
};
if ((res.statusCode >= 200 && res.statusCode < 300) || res.statusCode === 1223) {
c(response);
} else {
e(response);
}
});
}), err => {
let message: string;
if (agent) {
message = 'Unable to to connect to ' + options.url + ' through a proxy . Error: ' + err.message;
} else {
message = 'Unable to to connect to ' + options.url + '. Error: ' + err.message;
}
return Promise.reject<IXHRResponse>({
responseText: message,
status: 404
});
});
}
interface IRequestResult {
req: http.ClientRequest;
res: http.ClientResponse;
}
function request(options: IXHROptions): Promise<IRequestResult> {
let req: http.ClientRequest;
return new Promise<IRequestResult>((c, e) => {
let endpoint = parseUrl(options.url);
let opts: https.RequestOptions = {
hostname: endpoint.hostname,
port: endpoint.port ? parseInt(endpoint.port) : (endpoint.protocol === 'https:' ? 443 : 80),
path: endpoint.path,
method: options.type || 'GET',
headers: options.headers,
agent: options.agent,
rejectUnauthorized: (typeof options.strictSSL === 'boolean') ? options.strictSSL : true
};
if (options.user && options.password) {
opts.auth = options.user + ':' + options.password;
}
let protocol = endpoint.protocol === 'https:' ? https : http;
req = protocol.request(opts, (res: http.ClientResponse) => {
if (res.statusCode >= 300 && res.statusCode < 400 && options.followRedirects && options.followRedirects > 0 && res.headers['location']) {
c(<any> request(assign({}, options, {
url: res.headers['location'],
followRedirects: options.followRedirects - 1
})));
} else {
c({ req, res });
}
});
req.on('error', e);
if (options.timeout) {
req.setTimeout(options.timeout);
}
if (options.data) {
req.write(options.data);
}
req.end();
});
}
export function getErrorStatusDescription(status: number) : string {
if (status < 400) {
return void 0;
}
switch (status) {
case 400: return localize('status.400', 'Bad request. The request cannot be fulfilled due to bad syntax.');
case 401: return localize('status.401', 'Unauthorized. The server is refusing to respond.');
case 403: return localize('status.403', 'Forbidden. The server is refusing to respond.');
case 404: return localize('status.404', 'Not Found. The requested location could not be found.');
case 405: return localize('status.405', 'Method not allowed. A request was made using a request method not supported by that location.');
case 406: return localize('status.406', 'Not Acceptable. The server can only generate a response that is not accepted by the client.');
case 407: return localize('status.407', 'Proxy Authentication Required. The client must first authenticate itself with the proxy.');
case 408: return localize('status.408', 'Request Timeout. The server timed out waiting for the request.');
case 409: return localize('status.409', 'Conflict. The request could not be completed because of a conflict in the request.');
case 410: return localize('status.410', 'Gone. The requested page is no longer available.');
case 411: return localize('status.411', 'Length Required. The "Content-Length" is not defined.');
case 412: return localize('status.412', 'Precondition Failed. The precondition given in the request evaluated to false by the server.');
case 413: return localize('status.413', 'Request Entity Too Large. The server will not accept the request, because the request entity is too large.');
case 414: return localize('status.414', 'Request-URI Too Long. The server will not accept the request, because the URL is too long.');
case 415: return localize('status.415', 'Unsupported Media Type. The server will not accept the request, because the media type is not supported.');
case 500: return localize('status.500', 'Internal Server Error.');
case 501: return localize('status.501', 'Not Implemented. The server either does not recognize the request method, or it lacks the ability to fulfill the request.');
case 503: return localize('status.503', 'Service Unavailable. The server is currently unavailable (overloaded or down).');
default: return localize('status.416', 'HTTP status code {0}', status);
}
}
\ 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 { Url, parse as parseUrl } from 'url';
import HttpProxyAgent = require('http-proxy-agent');
import HttpsProxyAgent = require('https-proxy-agent');
function getSystemProxyURI(requestURL: Url): string {
if (requestURL.protocol === 'http:') {
return process.env.HTTP_PROXY || process.env.http_proxy || null;
} else if (requestURL.protocol === 'https:') {
return process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || null;
}
return null;
}
export interface IOptions {
proxyUrl?: string;
strictSSL?: boolean;
}
export function getProxyAgent(rawRequestURL: string, options: IOptions = {}): any {
const requestURL = parseUrl(rawRequestURL);
const proxyURL = options.proxyUrl || getSystemProxyURI(requestURL);
if (!proxyURL) {
return null;
}
const proxyEndpoint = parseUrl(proxyURL);
if (!/^https?:$/.test(proxyEndpoint.protocol)) {
return null;
}
const opts = {
host: proxyEndpoint.hostname,
port: Number(proxyEndpoint.port),
auth: proxyEndpoint.auth,
rejectUnauthorized: (typeof options.strictSSL === 'boolean') ? options.strictSSL : true
};
return requestURL.protocol === 'http:' ? new HttpProxyAgent(opts) : new HttpsProxyAgent(opts);
}
\ No newline at end of file
...@@ -16,4 +16,4 @@ ...@@ -16,4 +16,4 @@
"path": "./syntaxes/R.plist" "path": "./syntaxes/R.plist"
}] }]
} }
} }
\ No newline at end of file
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
"scripts": { "scripts": {
"test": "mocha", "test": "mocha",
"preinstall": "node build/npm/preinstall.js", "preinstall": "node build/npm/preinstall.js",
"postinstall": "npm --prefix extensions/vscode-api-tests/ install extensions/vscode-api-tests/ && npm --prefix extensions/vscode-colorize-tests/ install extensions/vscode-colorize-tests/ && npm --prefix extensions/json/ install extensions/json/ && npm --prefix extensions/typescript/ install extensions/typescript/ && npm --prefix extensions/php/ install extensions/php/", "postinstall": "npm --prefix extensions/vscode-api-tests/ install extensions/vscode-api-tests/ && npm --prefix extensions/vscode-colorize-tests/ install extensions/vscode-colorize-tests/ && npm --prefix extensions/json/ install extensions/json/ && npm --prefix extensions/typescript/ install extensions/typescript/ && npm --prefix extensions/php/ install extensions/php/ && npm --prefix extensions/javascript/ install extensions/javascript/",
"watch": "gulp watch" "watch": "gulp watch"
}, },
"dependencies": { "dependencies": {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册