Move bower/package.json dependency completions to javascript extension

This commit is contained in:
Martin Aeschlimann 2016-04-18 17:55:22 +02:00
parent 4fe233c752
commit 235cbcdf9d
33 changed files with 703 additions and 1844 deletions

View file

@ -0,0 +1,29 @@
{
"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"
}
]
}

View file

@ -0,0 +1,30 @@
// 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"
}

View file

@ -2,8 +2,19 @@
"name": "javascript",
"version": "0.1.0",
"publisher": "vscode",
"engines": {
"vscode": "*"
"engines": { "vscode": "0.10.x" },
"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": {
"languages": [

View file

@ -0,0 +1,177 @@
/*---------------------------------------------------------------------------------------------
* 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, DocumentSelector} from 'vscode';
import {IJSONContribution, ISuggestionsCollector} from './jsonContributions';
import {XHRRequest} from 'request-light';
import {Location} from 'jsonc-parser';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export class BowerJSONContribution implements IJSONContribution {
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(private xhr: XHRRequest) {
}
public getDocumentSelector(): DocumentSelector {
return [{ language: 'json', pattern: '**/bower.json' }, { language: 'json', pattern: '**/.bower.json' }];
}
public collectDefaultSuggestions(resource: string, collector: ISuggestionsCollector): Thenable<any> {
let defaultValue = {
'name': '{{name}}',
'description': '{{description}}',
'authors': [ '{{author}}' ],
'version': '{{1.0.0}}',
'main': '{{pathToMain}}',
'dependencies': {}
};
let proposal = new CompletionItem(localize('json.bower.default', 'Default bower.json'));
proposal.kind = CompletionItemKind.Class;
proposal.insertText = JSON.stringify(defaultValue, null, '\t');
collector.add(proposal);
return Promise.resolve(null);
}
public collectPropertySuggestions(resource: string, location: Location, currentWord: string, addValue: boolean, isLast:boolean, collector: ISuggestionsCollector) : Thenable<any> {
if ((location.matches(['dependencies']) || location.matches(['devDependencies']))) {
if (currentWord.length > 0) {
let queryUrl = 'https://bower.herokuapp.com/packages/search/' + encodeURIComponent(currentWord);
return this.xhr({
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 += ': "{{latest}}"';
if (!isLast) {
insertText += ',';
}
}
let proposal = new CompletionItem(name);
proposal.kind = CompletionItemKind.Property;
proposal.insertText = insertText;
proposal.documentation = description;
collector.add(proposal);
}
collector.setAsIncomplete();
}
} catch (e) {
// ignore
}
} else {
collector.error(localize('json.bower.error.repoaccess', 'Request to the bower repository failed: {0}', success.responseText));
return 0;
}
}, (error) => {
collector.error(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 += ': "{{latest}}"';
if (!isLast) {
insertText += ',';
}
}
let proposal = new CompletionItem(name);
proposal.kind = CompletionItemKind.Property;
proposal.insertText = insertText;
proposal.documentation = '';
collector.add(proposal);
});
collector.setAsIncomplete();
return Promise.resolve(null);
}
}
return null;
}
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
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 resolveSuggestion(item: CompletionItem) : Thenable<CompletionItem> {
if (item.kind === CompletionItemKind.Property && item.documentation === '') {
return this.getInfo(item.label).then(documentation => {
if (documentation) {
item.documentation = documentation;
return item;
}
return null;
});
};
return null;
}
private getInfo(pack: string): Thenable<string> {
let queryUrl = 'https://bower.herokuapp.com/packages/' + encodeURIComponent(pack);
return this.xhr({
url : queryUrl
}).then((success) => {
try {
let obj = JSON.parse(success.responseText);
if (obj && obj.url) {
let url : string = obj.url;
if (url.indexOf('git://') === 0) {
url = url.substring(6);
}
if (url.lastIndexOf('.git') === url.length - 4) {
url = url.substring(0, url.length - 4);
}
return url;
}
} 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;
});
}
return null;
}
}

View file

@ -0,0 +1,154 @@
/*---------------------------------------------------------------------------------------------
* 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);
}
}

View file

@ -0,0 +1,220 @@
/*---------------------------------------------------------------------------------------------
* 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, DocumentSelector} from 'vscode';
import {IJSONContribution, ISuggestionsCollector} from './jsonContributions';
import {XHRRequest} from 'request-light';
import {Location} from 'jsonc-parser';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
let LIMIT = 40;
export class PackageJSONContribution implements IJSONContribution {
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'];
public getDocumentSelector(): DocumentSelector {
return [{ language: 'json', pattern: '**/package.json' }];
}
public constructor(private xhr: XHRRequest) {
}
public collectDefaultSuggestions(fileName: string, result: ISuggestionsCollector): Thenable<any> {
let defaultValue = {
'name': '{{name}}',
'description': '{{description}}',
'author': '{{author}}',
'version': '{{1.0.0}}',
'main': '{{pathToMain}}',
'dependencies': {}
};
let proposal = new CompletionItem(localize('json.package.default', 'Default package.json'));
proposal.kind = CompletionItemKind.Module;
proposal.insertText = JSON.stringify(defaultValue, null, '\t');
result.add(proposal);
return Promise.resolve(null);
}
public collectPropertySuggestions(resource: string, location: Location, currentWord: string, addValue: boolean, isLast:boolean, collector: ISuggestionsCollector) : Thenable<any> {
if ((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.xhr({
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 += ',';
}
}
let proposal = new CompletionItem(name);
proposal.kind = CompletionItemKind.Property;
proposal.insertText = insertText;
proposal.documentation = '';
collector.add(proposal);
}
}
if (results.length === LIMIT) {
collector.setAsIncomplete();
}
}
} catch (e) {
// ignore
}
} else {
collector.error(localize('json.npm.error.repoaccess', 'Request to the NPM repository failed: {0}', success.responseText));
return 0;
}
}, (error) => {
collector.error(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 += ',';
}
}
let proposal = new CompletionItem(name);
proposal.kind = CompletionItemKind.Property;
proposal.insertText = insertText;
proposal.documentation = '';
collector.add(proposal);
});
collector.setAsIncomplete();
return Promise.resolve(null);
}
}
return null;
}
public collectValueSuggestions(fileName: string, location: Location, result: ISuggestionsCollector): Thenable<any> {
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';
return this.xhr({
url : queryUrl
}).then((success) => {
try {
let obj = JSON.parse(success.responseText);
if (obj && obj.version) {
let version = obj.version;
let name = JSON.stringify(version);
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);
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);
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) {
// ignore
}
return 0;
}, (error) => {
return 0;
});
}
return null;
}
public resolveSuggestion(item: CompletionItem) : Thenable<CompletionItem> {
if (item.kind === CompletionItemKind.Property && item.documentation === '') {
return this.getInfo(item.label).then(infos => {
if (infos.length > 0) {
item.documentation = infos[0];
if (infos.length > 1) {
item.detail = infos[1];
}
return item;
}
return null;
});
};
return null;
}
private getInfo(pack: string): Thenable<string[]> {
let queryUrl = 'http://registry.npmjs.org/' + encodeURIComponent(pack) + '/latest';
return this.xhr({
url : queryUrl
}).then((success) => {
try {
let obj = JSON.parse(success.responseText);
if (obj) {
let result = [];
if (obj.description) {
result.push(obj.description);
}
if (obj.version) {
result.push(localize('json.npm.version.hover', 'Latest version: {0}', obj.version));
}
return result;
}
} catch (e) {
// 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 null;
}
}

View file

@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* 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 {addJSONProviders} from './features/jsonContributions';
import * as httpRequest from 'request-light';
import {ExtensionContext, env, workspace} from 'vscode';
import * as nls from 'vscode-nls';
export function activate(context: ExtensionContext): any {
nls.config({locale: env.language});
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'));
}

View file

@ -3,18 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'http-proxy-agent' {
interface IHttpProxyAgentOptions {
host: string;
port: number;
auth?: string;
}
class HttpProxyAgent {
constructor(proxy: string);
constructor(opts: IHttpProxyAgentOptions);
}
export = HttpProxyAgent;
}
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../src/typings/mocha.d.ts'/>
/// <reference path='../../../../extensions/node.d.ts'/>
/// <reference path='../../../../extensions/lib.core.d.ts'/>
/// <reference path='../../../../extensions/declares.d.ts'/>

View file

@ -0,0 +1,11 @@
{
"compilerOptions": {
"noLib": true,
"target": "es5",
"module": "commonjs",
"outDir": "./out"
},
"exclude": [
"node_modules"
]
}

View file

@ -8,8 +8,8 @@
"node": "*"
},
"dependencies": {
"http-proxy-agent": "^0.2.6",
"https-proxy-agent": "^0.3.5",
"request-light": "^0.0.3",
"jsonc-parser": "^0.0.1",
"vscode-languageserver": "^1.3.0",
"vscode-nls": "^1.0.4"
},

View file

@ -1,980 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export enum ScanError {
None,
UnexpectedEndOfComment,
UnexpectedEndOfString,
UnexpectedEndOfNumber,
InvalidUnicode,
InvalidEscapeCharacter
}
export enum SyntaxKind {
Unknown = 0,
OpenBraceToken,
CloseBraceToken,
OpenBracketToken,
CloseBracketToken,
CommaToken,
ColonToken,
NullKeyword,
TrueKeyword,
FalseKeyword,
StringLiteral,
NumericLiteral,
LineCommentTrivia,
BlockCommentTrivia,
LineBreakTrivia,
Trivia,
EOF
}
export interface JSONScanner {
scan(): SyntaxKind;
getPosition(): number;
getToken(): SyntaxKind;
getTokenValue(): string;
getTokenOffset(): number;
getTokenLength(): number;
getTokenError(): ScanError;
}
export function createScanner(text:string, ignoreTrivia:boolean = false):JSONScanner {
var pos = 0,
len = text.length,
value:string = '',
tokenOffset = 0,
token:SyntaxKind = SyntaxKind.Unknown,
scanError:ScanError = ScanError.None;
function scanHexDigits(count: number, exact?: boolean): number {
var digits = 0;
var value = 0;
while (digits < count || !exact) {
var ch = text.charCodeAt(pos);
if (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) {
value = value * 16 + ch - CharacterCodes._0;
}
else if (ch >= CharacterCodes.A && ch <= CharacterCodes.F) {
value = value * 16 + ch - CharacterCodes.A + 10;
}
else if (ch >= CharacterCodes.a && ch <= CharacterCodes.f) {
value = value * 16 + ch - CharacterCodes.a + 10;
}
else {
break;
}
pos++;
digits++;
}
if (digits < count) {
value = -1;
}
return value;
}
function scanNumber(): string {
var start = pos;
if (text.charCodeAt(pos) === CharacterCodes._0) {
pos++;
} else {
pos++;
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
pos++;
}
}
if (pos < text.length && text.charCodeAt(pos) === CharacterCodes.dot) {
pos++;
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
pos++;
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
pos++;
}
} else {
scanError = ScanError.UnexpectedEndOfNumber;
return text.substring(start, end);
}
}
var end = pos;
if (pos < text.length && (text.charCodeAt(pos) === CharacterCodes.E || text.charCodeAt(pos) === CharacterCodes.e)) {
pos++;
if (pos < text.length && text.charCodeAt(pos) === CharacterCodes.plus || text.charCodeAt(pos) === CharacterCodes.minus) {
pos++;
}
if (pos < text.length && isDigit(text.charCodeAt(pos))) {
pos++;
while (pos < text.length && isDigit(text.charCodeAt(pos))) {
pos++;
}
end = pos;
} else {
scanError = ScanError.UnexpectedEndOfNumber;
}
}
return text.substring(start, end);
}
function scanString(): string {
var result = '',
start = pos;
while (true) {
if (pos >= len) {
result += text.substring(start, pos);
scanError = ScanError.UnexpectedEndOfString;
break;
}
var ch = text.charCodeAt(pos);
if (ch === CharacterCodes.doubleQuote) {
result += text.substring(start, pos);
pos++;
break;
}
if (ch === CharacterCodes.backslash) {
result += text.substring(start, pos);
pos++;
if (pos >= len) {
scanError = ScanError.UnexpectedEndOfString;
break;
}
ch = text.charCodeAt(pos++);
switch (ch) {
case CharacterCodes.doubleQuote:
result += '\"';
break;
case CharacterCodes.backslash:
result += '\\';
break;
case CharacterCodes.slash:
result += '/';
break;
case CharacterCodes.b:
result += '\b';
break;
case CharacterCodes.f:
result += '\f';
break;
case CharacterCodes.n:
result += '\n';
break;
case CharacterCodes.r:
result += '\r';
break;
case CharacterCodes.t:
result += '\t';
break;
case CharacterCodes.u:
var ch = scanHexDigits(4, true);
if (ch >= 0) {
result += String.fromCharCode(ch);
} else {
scanError = ScanError.InvalidUnicode;
}
break;
default:
scanError = ScanError.InvalidEscapeCharacter;
}
start = pos;
continue;
}
if (isLineBreak(ch)) {
result += text.substring(start, pos);
scanError = ScanError.UnexpectedEndOfString;
break;
}
pos++;
}
return result;
}
function scanNext():SyntaxKind {
value = '';
scanError = ScanError.None;
tokenOffset = pos;
if(pos >= len) {
// at the end
tokenOffset = len;
return token = SyntaxKind.EOF;
}
var code = text.charCodeAt(pos);
// trivia: whitespace
if (isWhiteSpace(code)) {
do {
pos++;
value += String.fromCharCode(code);
code = text.charCodeAt(pos);
} while (isWhiteSpace(code));
return token = SyntaxKind.Trivia;
}
// trivia: newlines
if (isLineBreak(code)) {
pos++;
value += String.fromCharCode(code);
if (code === CharacterCodes.carriageReturn && text.charCodeAt(pos) === CharacterCodes.lineFeed) {
pos++;
value += '\n';
}
return token = SyntaxKind.LineBreakTrivia;
}
switch(code) {
// tokens: []{}:,
case CharacterCodes.openBrace:
pos++;
return token = SyntaxKind.OpenBraceToken;
case CharacterCodes.closeBrace:
pos++;
return token = SyntaxKind.CloseBraceToken;
case CharacterCodes.openBracket:
pos++;
return token = SyntaxKind.OpenBracketToken;
case CharacterCodes.closeBracket:
pos++;
return token = SyntaxKind.CloseBracketToken;
case CharacterCodes.colon:
pos++;
return token = SyntaxKind.ColonToken;
case CharacterCodes.comma:
pos++;
return token = SyntaxKind.CommaToken;
// strings
case CharacterCodes.doubleQuote:
pos++;
value = scanString();
return token = SyntaxKind.StringLiteral;
// comments
case CharacterCodes.slash:
var start = pos - 1;
// Single-line comment
if (text.charCodeAt(pos + 1) === CharacterCodes.slash) {
pos += 2;
while (pos < len) {
if (isLineBreak(text.charCodeAt(pos))) {
break;
}
pos++;
}
value = text.substring(start, pos);
return token = SyntaxKind.LineCommentTrivia;
}
// Multi-line comment
if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) {
pos += 2;
var safeLength = len - 1; // For lookahead.
var commentClosed = false;
while (pos < safeLength) {
var ch = text.charCodeAt(pos);
if (ch === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) {
pos += 2;
commentClosed = true;
break;
}
pos++;
}
if (!commentClosed) {
pos++;
scanError = ScanError.UnexpectedEndOfComment;
}
value = text.substring(start, pos);
return token = SyntaxKind.BlockCommentTrivia;
}
// just a single slash
value += String.fromCharCode(code);
pos++;
return token = SyntaxKind.Unknown;
// numbers
case CharacterCodes.minus:
value += String.fromCharCode(code);
pos++;
if (pos === len || !isDigit(text.charCodeAt(pos))) {
return token = SyntaxKind.Unknown;
}
// found a minus, followed by a number so
// we fall through to proceed with scanning
// numbers
case CharacterCodes._0:
case CharacterCodes._1:
case CharacterCodes._2:
case CharacterCodes._3:
case CharacterCodes._4:
case CharacterCodes._5:
case CharacterCodes._6:
case CharacterCodes._7:
case CharacterCodes._8:
case CharacterCodes._9:
value += scanNumber();
return token = SyntaxKind.NumericLiteral;
// literals and unknown symbols
default:
// is a literal? Read the full word.
while (pos < len && isUnknownContentCharacter(code)) {
pos++;
code = text.charCodeAt(pos);
}
if (tokenOffset !== pos) {
value = text.substring(tokenOffset, pos);
// keywords: true, false, null
switch (value) {
case 'true': return token = SyntaxKind.TrueKeyword;
case 'false': return token = SyntaxKind.FalseKeyword;
case 'null': return token = SyntaxKind.NullKeyword;
}
return token = SyntaxKind.Unknown;
}
// some
value += String.fromCharCode(code);
pos++;
return token = SyntaxKind.Unknown;
}
}
function isUnknownContentCharacter(code: CharacterCodes) {
if (isWhiteSpace(code) || isLineBreak(code)) {
return false;
}
switch (code) {
case CharacterCodes.closeBrace:
case CharacterCodes.closeBracket:
case CharacterCodes.openBrace:
case CharacterCodes.openBracket:
case CharacterCodes.doubleQuote:
case CharacterCodes.colon:
case CharacterCodes.comma:
return false;
}
return true;
}
function scanNextNonTrivia():SyntaxKind {
var result : SyntaxKind;
do {
result = scanNext();
} while (result >= SyntaxKind.LineCommentTrivia && result <= SyntaxKind.Trivia);
return result;
}
return {
getPosition: () => pos,
scan: ignoreTrivia ? scanNextNonTrivia : scanNext,
getToken: () => token,
getTokenValue: () => value,
getTokenOffset: () => tokenOffset,
getTokenLength: () => pos - tokenOffset,
getTokenError: () => scanError
};
}
function isWhiteSpace(ch: number): boolean {
return ch === CharacterCodes.space || ch === CharacterCodes.tab || ch === CharacterCodes.verticalTab || ch === CharacterCodes.formFeed ||
ch === CharacterCodes.nonBreakingSpace || ch === CharacterCodes.ogham || ch >= CharacterCodes.enQuad && ch <= CharacterCodes.zeroWidthSpace ||
ch === CharacterCodes.narrowNoBreakSpace || ch === CharacterCodes.mathematicalSpace || ch === CharacterCodes.ideographicSpace || ch === CharacterCodes.byteOrderMark;
}
function isLineBreak(ch: number): boolean {
return ch === CharacterCodes.lineFeed || ch === CharacterCodes.carriageReturn || ch === CharacterCodes.lineSeparator || ch === CharacterCodes.paragraphSeparator;
}
function isDigit(ch: number): boolean {
return ch >= CharacterCodes._0 && ch <= CharacterCodes._9;
}
export function isLetter(ch: number): boolean {
return ch >= CharacterCodes.a && ch <= CharacterCodes.z || ch >= CharacterCodes.A && ch <= CharacterCodes.Z;
}
enum CharacterCodes {
nullCharacter = 0,
maxAsciiCharacter = 0x7F,
lineFeed = 0x0A, // \n
carriageReturn = 0x0D, // \r
lineSeparator = 0x2028,
paragraphSeparator = 0x2029,
// REVIEW: do we need to support this? The scanner doesn't, but our IText does. This seems
// like an odd disparity? (Or maybe it's completely fine for them to be different).
nextLine = 0x0085,
// Unicode 3.0 space characters
space = 0x0020, // " "
nonBreakingSpace = 0x00A0, //
enQuad = 0x2000,
emQuad = 0x2001,
enSpace = 0x2002,
emSpace = 0x2003,
threePerEmSpace = 0x2004,
fourPerEmSpace = 0x2005,
sixPerEmSpace = 0x2006,
figureSpace = 0x2007,
punctuationSpace = 0x2008,
thinSpace = 0x2009,
hairSpace = 0x200A,
zeroWidthSpace = 0x200B,
narrowNoBreakSpace = 0x202F,
ideographicSpace = 0x3000,
mathematicalSpace = 0x205F,
ogham = 0x1680,
_ = 0x5F,
$ = 0x24,
_0 = 0x30,
_1 = 0x31,
_2 = 0x32,
_3 = 0x33,
_4 = 0x34,
_5 = 0x35,
_6 = 0x36,
_7 = 0x37,
_8 = 0x38,
_9 = 0x39,
a = 0x61,
b = 0x62,
c = 0x63,
d = 0x64,
e = 0x65,
f = 0x66,
g = 0x67,
h = 0x68,
i = 0x69,
j = 0x6A,
k = 0x6B,
l = 0x6C,
m = 0x6D,
n = 0x6E,
o = 0x6F,
p = 0x70,
q = 0x71,
r = 0x72,
s = 0x73,
t = 0x74,
u = 0x75,
v = 0x76,
w = 0x77,
x = 0x78,
y = 0x79,
z = 0x7A,
A = 0x41,
B = 0x42,
C = 0x43,
D = 0x44,
E = 0x45,
F = 0x46,
G = 0x47,
H = 0x48,
I = 0x49,
J = 0x4A,
K = 0x4B,
L = 0x4C,
M = 0x4D,
N = 0x4E,
O = 0x4F,
P = 0x50,
Q = 0x51,
R = 0x52,
S = 0x53,
T = 0x54,
U = 0x55,
V = 0x56,
W = 0x57,
X = 0x58,
Y = 0x59,
Z = 0x5a,
ampersand = 0x26, // &
asterisk = 0x2A, // *
at = 0x40, // @
backslash = 0x5C, // \
bar = 0x7C, // |
caret = 0x5E, // ^
closeBrace = 0x7D, // }
closeBracket = 0x5D, // ]
closeParen = 0x29, // )
colon = 0x3A, // :
comma = 0x2C, // ,
dot = 0x2E, // .
doubleQuote = 0x22, // "
equals = 0x3D, // =
exclamation = 0x21, // !
greaterThan = 0x3E, // >
lessThan = 0x3C, // <
minus = 0x2D, // -
openBrace = 0x7B, // {
openBracket = 0x5B, // [
openParen = 0x28, // (
percent = 0x25, // %
plus = 0x2B, // +
question = 0x3F, // ?
semicolon = 0x3B, // ;
singleQuote = 0x27, // '
slash = 0x2F, // /
tilde = 0x7E, // ~
backspace = 0x08, // \b
formFeed = 0x0C, // \f
byteOrderMark = 0xFEFF,
tab = 0x09, // \t
verticalTab = 0x0B, // \v
}
/**
* Takes JSON with JavaScript-style comments and remove
* them. Optionally replaces every none-newline character
* of comments with a replaceCharacter
*/
export function stripComments(text:string, replaceCh?:string):string {
var _scanner = createScanner(text),
parts: string[] = [],
kind:SyntaxKind,
offset = 0,
pos:number;
do {
pos = _scanner.getPosition();
kind = _scanner.scan();
switch (kind) {
case SyntaxKind.LineCommentTrivia:
case SyntaxKind.BlockCommentTrivia:
case SyntaxKind.EOF:
if(offset !== pos) {
parts.push(text.substring(offset, pos));
}
if(replaceCh !== void 0) {
parts.push(_scanner.getTokenValue().replace(/[^\r\n]/g, replaceCh));
}
offset = _scanner.getPosition();
break;
}
} while(kind !== SyntaxKind.EOF);
return parts.join('');
}
export enum ParseErrorCode {
InvalidSymbol,
InvalidNumberFormat,
PropertyNameExpected,
ValueExpected,
ColonExpected,
CommaExpected,
CloseBraceExpected,
CloseBracketExpected,
EndOfFileExpected
}
export function getParseErrorMessage(errorCode: ParseErrorCode) : string {
switch (errorCode) {
case ParseErrorCode.InvalidSymbol: return 'Invalid symbol';
case ParseErrorCode.InvalidNumberFormat: return 'Invalid number format';
case ParseErrorCode.PropertyNameExpected: return 'Property name expected';
case ParseErrorCode.ValueExpected: return 'Value expected';
case ParseErrorCode.ColonExpected: return 'Colon expected';
case ParseErrorCode.CommaExpected: return 'Comma expected';
case ParseErrorCode.CloseBraceExpected: return 'Closing brace expected';
case ParseErrorCode.CloseBracketExpected: return 'Closing bracket expected';
case ParseErrorCode.EndOfFileExpected: return 'End of file expected';
default:
return '';
}
}
export type NodeType = "object" | "array" | "property" | "string" | "number" | "null";
export interface Node {
type: NodeType;
value: any;
offset: number;
length: number;
columnOffset?: number;
}
export interface Location {
previousNode: Node;
segments: string[];
completeProperty: boolean;
}
export function getLocation(text:string, position: number) : Location {
let segments: string[] = [];
let earlyReturnException = new Object();
let previousNode : Node = {
value: void 0,
offset: void 0,
length: void 0,
type: void 0
};
let completeProperty = false;
let hasComma = false;
try {
function setNode(value: string, offset: number, length: number, type: NodeType) {
previousNode.value = value;
previousNode.offset = offset;
previousNode.length = length;
previousNode.type = type;
previousNode.columnOffset = void 0;
}
visit(text, {
onObjectBegin: (offset: number, length: number) => {
if (position <= offset) {
throw earlyReturnException;
}
setNode(void 0, void 0, void 0, void 0);
completeProperty = position > offset;
hasComma = false;
},
onObjectProperty: (name: string, offset: number, length: number) => {
if (position < offset) {
throw earlyReturnException;
}
setNode(name, offset, length, 'property');
hasComma = false;
segments.push(name);
if (position <= offset + length) {
throw earlyReturnException;
}
},
onObjectEnd: (offset: number, length: number) => {
if (position <= offset) {
throw earlyReturnException;
}
setNode(void 0, void 0, void 0, void 0);
if (!hasComma) {
segments.pop();
}
hasComma = false;
},
onArrayBegin: (offset: number, length: number) => {
if (position <= offset) {
throw earlyReturnException;
}
setNode(void 0, void 0, void 0, void 0);
segments.push('[0]');
hasComma = false;
},
onArrayEnd: (offset: number, length: number) => {
if (position <= offset) {
throw earlyReturnException;
}
setNode(void 0, void 0, void 0, void 0);
if (!hasComma) {
segments.pop();
}
hasComma = false;
},
onLiteralValue: (value: any, offset: number, length: number) => {
if (position < offset) {
throw earlyReturnException;
}
setNode(value, offset, length, value === null ? 'null' : (typeof value === 'string' ? 'string' : 'number'));
if (position <= offset + length) {
throw earlyReturnException;
}
},
onSeparator: (sep: string, offset: number, length: number) => {
if (position <= offset) {
throw earlyReturnException;
}
if (sep === ':' && previousNode.type === 'property') {
previousNode.columnOffset = offset;
completeProperty = false;
setNode(void 0, void 0, void 0, void 0);
} else if (sep === ',') {
let last = segments.pop();
if (last[0] === '[' && last[last.length - 1] === ']') {
segments.push('[' + (parseInt(last.substr(1, last.length - 2)) + 1) + ']');
} else {
completeProperty = true;
}
setNode(void 0, void 0, void 0, void 0);
hasComma = true;
}
}
});
} catch (e) {
if (e !== earlyReturnException) {
throw e;
}
}
return {
segments,
previousNode,
completeProperty
};
}
export function parse(text:string, errors: { error:ParseErrorCode; }[] = []) : any {
let currentProperty : string = null;
let currentParent : any = [];
let previousParents : any[] = [];
function onValue(value: any) {
if (Array.isArray(currentParent)) {
(<any[]> currentParent).push(value);
} else if (currentProperty) {
currentParent[currentProperty] = value;
}
}
let visitor = {
onObjectBegin: () => {
let object = {};
onValue(object);
previousParents.push(currentParent);
currentParent = object;
currentProperty = null;
},
onObjectProperty: (name: string) => {
currentProperty = name;
},
onObjectEnd: () => {
currentParent = previousParents.pop();
},
onArrayBegin: () => {
let array = [];
onValue(array);
previousParents.push(currentParent);
currentParent = array;
currentProperty = null;
},
onArrayEnd: () => {
currentParent = previousParents.pop();
},
onLiteralValue: onValue,
onError:(error:ParseErrorCode) => {
errors.push({error: error});
}
};
visit(text, visitor);
return currentParent[0];
}
export function visit(text:string, visitor: JSONVisitor) : any {
var _scanner = createScanner(text, true);
function toNoArgVisit(visitFunction: (offset: number, length: number) => void) : () => void {
return visitFunction ? () => visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength()) : () => true;
}
function toOneArgVisit<T>(visitFunction: (arg: T, offset: number, length: number) => void) : (arg: T) => void {
return visitFunction ? (arg: T) => visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength()) : () => true;
}
let onObjectBegin = toNoArgVisit(visitor.onObjectBegin),
onObjectProperty = toOneArgVisit(visitor.onObjectProperty),
onObjectEnd = toNoArgVisit(visitor.onObjectEnd),
onArrayBegin = toNoArgVisit(visitor.onArrayBegin),
onArrayEnd = toNoArgVisit(visitor.onArrayEnd),
onLiteralValue = toOneArgVisit(visitor.onLiteralValue),
onSeparator = toOneArgVisit(visitor.onSeparator),
onError = toOneArgVisit(visitor.onError);
function scanNext() : SyntaxKind {
var token = _scanner.scan();
while (token === SyntaxKind.Unknown) {
handleError(ParseErrorCode.InvalidSymbol);
token = _scanner.scan();
}
return token;
}
function handleError(error:ParseErrorCode, skipUntilAfter: SyntaxKind[] = [], skipUntil: SyntaxKind[] = []) : void {
onError(error);
if (skipUntilAfter.length + skipUntil.length > 0) {
var token = _scanner.getToken();
while (token !== SyntaxKind.EOF) {
if (skipUntilAfter.indexOf(token) !== -1) {
scanNext();
break;
} else if (skipUntil.indexOf(token) !== -1) {
break;
}
token = scanNext();
}
}
}
function parseString(isValue: boolean) : boolean {
if (_scanner.getToken() !== SyntaxKind.StringLiteral) {
return false;
}
var value = _scanner.getTokenValue();
if (isValue) {
onLiteralValue(value);
} else {
onObjectProperty(value);
}
scanNext();
return true;
}
function parseLiteral() : boolean {
switch (_scanner.getToken()) {
case SyntaxKind.NumericLiteral:
let value = 0;
try {
value = JSON.parse(_scanner.getTokenValue());
if (typeof value !== 'number') {
handleError(ParseErrorCode.InvalidNumberFormat);
value = 0;
}
} catch (e) {
handleError(ParseErrorCode.InvalidNumberFormat);
}
onLiteralValue(value);
break;
case SyntaxKind.NullKeyword:
onLiteralValue(null);
break;
case SyntaxKind.TrueKeyword:
onLiteralValue(true);
break;
case SyntaxKind.FalseKeyword:
onLiteralValue(false);
break;
default:
return false;
}
scanNext();
return true;
}
function parseProperty() : boolean {
if (!parseString(false)) {
handleError(ParseErrorCode.PropertyNameExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken] );
return false;
}
if (_scanner.getToken() === SyntaxKind.ColonToken) {
onSeparator(':');
scanNext(); // consume colon
if (!parseValue()) {
handleError(ParseErrorCode.ValueExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken] );
}
} else {
handleError(ParseErrorCode.ColonExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken] );
}
return true;
}
function parseObject() : boolean {
if (_scanner.getToken() !== SyntaxKind.OpenBraceToken) {
return false;
}
onObjectBegin();
scanNext(); // consume open brace
var needsComma = false;
while (_scanner.getToken() !== SyntaxKind.CloseBraceToken && _scanner.getToken() !== SyntaxKind.EOF) {
if (_scanner.getToken() === SyntaxKind.CommaToken) {
if (!needsComma) {
handleError(ParseErrorCode.ValueExpected, [], [] );
}
onSeparator(',');
scanNext(); // consume comma
} else if (needsComma) {
handleError(ParseErrorCode.CommaExpected, [], [] );
}
if (!parseProperty()) {
handleError(ParseErrorCode.ValueExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken] );
}
needsComma = true;
}
onObjectEnd();
if (_scanner.getToken() !== SyntaxKind.CloseBraceToken) {
handleError(ParseErrorCode.CloseBraceExpected, [SyntaxKind.CloseBraceToken], []);
} else {
scanNext(); // consume close brace
}
return true;
}
function parseArray() : boolean {
if (_scanner.getToken() !== SyntaxKind.OpenBracketToken) {
return false;
}
onArrayBegin();
scanNext(); // consume open bracket
var needsComma = false;
while (_scanner.getToken() !== SyntaxKind.CloseBracketToken && _scanner.getToken() !== SyntaxKind.EOF) {
if (_scanner.getToken() === SyntaxKind.CommaToken) {
if (!needsComma) {
handleError(ParseErrorCode.ValueExpected, [], [] );
}
onSeparator(',');
scanNext(); // consume comma
} else if (needsComma) {
handleError(ParseErrorCode.CommaExpected, [], [] );
}
if (!parseValue()) {
handleError(ParseErrorCode.ValueExpected, [], [SyntaxKind.CloseBracketToken, SyntaxKind.CommaToken] );
}
needsComma = true;
}
onArrayEnd();
if (_scanner.getToken() !== SyntaxKind.CloseBracketToken) {
handleError(ParseErrorCode.CloseBracketExpected, [SyntaxKind.CloseBracketToken], []);
} else {
scanNext(); // consume close bracket
}
return true;
}
function parseValue() : boolean {
return parseArray() || parseObject() || parseString(true) || parseLiteral();
}
scanNext();
if (!parseValue()) {
handleError(ParseErrorCode.ValueExpected, [], []);
return false;
}
if (_scanner.getToken() !== SyntaxKind.EOF) {
handleError(ParseErrorCode.EndOfFileExpected, [], []);
}
return true;
}
export interface JSONVisitor {
onObjectBegin?: (offset:number, length:number) => void;
onObjectProperty?: (property: string, offset:number, length:number) => void;
onObjectEnd?: (offset:number, length:number) => void;
onArrayBegin?: (offset:number, length:number) => void;
onArrayEnd?: (offset:number, length:number) => void;
onLiteralValue?: (value: any, offset:number, length:number) => void;
onSeparator?: (charcter: string, offset:number, length:number) => void;
onError?: (error: ParseErrorCode, offset:number, length:number) => void;
}

View file

@ -7,7 +7,7 @@
import Parser = require('./jsonParser');
import SchemaService = require('./jsonSchemaService');
import JsonSchema = require('./json-toolbox/jsonSchema');
import JsonSchema = require('./jsonSchema');
import {IJSONWorkerContribution} from './jsonContributions';
import {CompletionItem, CompletionItemKind, CompletionList, ITextDocument, TextDocumentPosition, Range, TextEdit, RemoteConsole} from 'vscode-languageserver';

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import Json = require('./json-toolbox/json');
import Json = require('jsonc-parser');
import {ITextDocument, Range, Position, FormattingOptions, TextEdit} from 'vscode-languageserver';
export function format(document: ITextDocument, range: Range, options: FormattingOptions): TextEdit[] {

View file

@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import Json = require('./json-toolbox/json');
import JsonSchema = require('./json-toolbox/jsonSchema');
import Json = require('jsonc-parser');
import JsonSchema = require('./jsonSchema');
import {JSONLocation} from './jsonLocation';
import * as nls from 'vscode-nls';
@ -898,7 +898,7 @@ export function parse(text: string, config = new JSONDocumentConfig()): JSONDocu
if (_scanner.getToken() === Json.SyntaxKind.Unknown) {
// give a more helpful error message
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'));
}
}

View file

@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import Json = require('./json-toolbox/json');
import {IJSONSchema} from './json-toolbox/jsonSchema';
import {IXHROptions, IXHRResponse, getErrorStatusDescription} from './utils/httpRequest';
import Json = require('jsonc-parser');
import {IJSONSchema} from './jsonSchema';
import {XHROptions, XHRResponse, getErrorStatusDescription} from 'request-light';
import URI from './utils/uri';
import Strings = require('./utils/strings');
import Parser = require('./jsonParser');
@ -208,7 +208,7 @@ export interface IWorkspaceContextService {
}
export interface IRequestService {
(options: IXHROptions): Thenable<IXHRResponse>;
(options: XHROptions): Thenable<XHRResponse>;
}
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])] : [];
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());
return new UnresolvedSchema(<IJSONSchema>{}, [errorMessage]);
}

View file

@ -1,144 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 {IJSONWorkerContribution, ISuggestionsCollector} from '../jsonContributions';
import {IRequestService} from '../jsonSchemaService';
import {JSONLocation} from '../jsonLocation';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
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: 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(localize('json.bower.error.repoaccess', 'Request to the bower repository failed: {0}', success.responseText));
return 0;
}
}, (error) => {
result.error(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(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;
}
}

View file

@ -1,173 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 {IJSONWorkerContribution, ISuggestionsCollector} from '../jsonContributions';
import {IRequestService} from '../jsonSchemaService';
import {JSONLocation} from '../jsonLocation';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
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: 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(localize('json.npm.error.repoaccess', 'Request to the NPM repository failed: {0}', success.responseText));
return 0;
}
}, (error) => {
result.error(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: 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: 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: 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(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(localize('json.npm.version.hover', 'Latest version: {0}', obj.version));
}
}
} catch (e) {
// ignore
}
return htmlContent;
}, (error) => {
return htmlContent;
});
}
return null;
}
}

View file

@ -6,7 +6,7 @@
import {MarkedString, CompletionItemKind, CompletionItem} from 'vscode-languageserver';
import Strings = require('../utils/strings');
import {IXHRResponse, getErrorStatusDescription} from '../utils/httpRequest';
import {XHRResponse, getErrorStatusDescription} from 'request-light';
import {IJSONWorkerContribution, ISuggestionsCollector} from '../jsonContributions';
import {IRequestService} from '../jsonSchemaService';
import {JSONLocation} from '../jsonLocation';
@ -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));
}, (error: IXHRResponse) => {
}, (error: XHRResponse) => {
return Promise.reject<T>(localize('json.nugget.error.access', 'Request to {0} failed: {1}', url, getErrorStatusDescription(error.status)));
});
}

View file

@ -13,7 +13,7 @@ import {
DocumentRangeFormattingParams, NotificationType, RequestType
} 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 fs = require('fs');
import URI from './utils/uri';
@ -22,12 +22,10 @@ import {JSONSchemaService, ISchemaAssociations} from './jsonSchemaService';
import {parse as parseJSON, ObjectASTNode, JSONDocument} from './jsonParser';
import {JSONCompletion} from './jsonCompletion';
import {JSONHover} from './jsonHover';
import {IJSONSchema} from './json-toolbox/jsonSchema';
import {IJSONSchema} from './jsonSchema';
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';
import {FileAssociationContribution} from './jsoncontributions/fileAssociationContribution';
@ -94,10 +92,10 @@ let telemetry = {
}
};
let request = (options: IXHROptions): Thenable<IXHRResponse> => {
let request = (options: XHROptions): Thenable<XHRResponse> => {
if (Strings.startsWith(options.url, 'file://')) {
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) => {
err ? e({ responseText: '', status: 404 }) : c({ responseText: result.toString(), status: 200 });
});
@ -120,8 +118,6 @@ let request = (options: IXHROptions): Thenable<IXHRResponse> => {
let contributions = [
new ProjectJSONContribution(request),
new PackageJSONContribution(request),
new BowerJSONContribution(request),
new GlobPatternContribution(),
filesAssociationContribution
];

View file

@ -7,17 +7,17 @@
import assert = require('assert');
import Parser = require('../jsonParser');
import SchemaService = require('../jsonSchemaService');
import JsonSchema = require('../json-toolbox/jsonSchema');
import JsonSchema = require('../jsonSchema');
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 {applyEdits} from './textEditSupport';
suite('JSON Completion', () => {
var requestService = function(options: IXHROptions): Promise<IXHRResponse> {
return Promise.reject<IXHRResponse>({ responseText: '', status: 404 });
var requestService = function(options: XHROptions): Promise<XHRResponse> {
return Promise.reject<XHRResponse>({ responseText: '', status: 404 });
}
var assertSuggestion = function(completions: CompletionItem[], label: string, documentation?: string, document?: ITextDocument, resultText?: string) {

View file

@ -7,9 +7,8 @@
import assert = require('assert');
import Parser = require('../jsonParser');
import SchemaService = require('../jsonSchemaService');
import JsonSchema = require('../json-toolbox/jsonSchema');
import JsonSchema = require('../jsonSchema');
import {JSONCompletion} from '../jsonCompletion';
import {IXHROptions, IXHRResponse} from '../utils/httpRequest';
import {JSONDocumentSymbols} from '../jsonDocumentSymbols';
import {SymbolInformation, SymbolKind, TextDocumentIdentifier, ITextDocument, TextDocumentPosition, Range, Position, TextEdit} from 'vscode-languageserver';

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import Json = require('../json-toolbox/json');
import Json = require('jsonc-parser');
import {ITextDocument, DocumentFormattingParams, Range, Position, FormattingOptions, TextEdit} from 'vscode-languageserver';
import Formatter = require('../jsonFormatter');
import assert = require('assert');

View file

@ -7,9 +7,9 @@
import assert = require('assert');
import Parser = require('../jsonParser');
import SchemaService = require('../jsonSchemaService');
import JsonSchema = require('../json-toolbox/jsonSchema');
import JsonSchema = require('../jsonSchema');
import {JSONCompletion} from '../jsonCompletion';
import {IXHROptions, IXHRResponse} from '../utils/httpRequest';
import {XHROptions, XHRResponse} from 'request-light';
import {JSONHover} from '../jsonHover';
import {Hover, ITextDocument, TextDocumentIdentifier, TextDocumentPosition, Range, Position, TextEdit} from 'vscode-languageserver';
@ -30,8 +30,8 @@ suite('JSON Hover', () => {
return hoverProvider.doHover(document, textDocumentLocation, jsonDoc);
}
var requestService = function(options: IXHROptions): Promise<IXHRResponse> {
return Promise.reject<IXHRResponse>({ responseText: '', status: 404 });
var requestService = function(options: XHROptions): Promise<XHRResponse> {
return Promise.reject<XHRResponse>({ responseText: '', status: 404 });
}
test('Simple schema', function(testDone) {

View file

@ -1,226 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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);
});
});

View file

@ -7,7 +7,7 @@
import assert = require('assert');
import Parser = require('../jsonParser');
import SchemaService = require('../jsonSchemaService');
import JsonSchema = require('../json-toolbox/jsonSchema');
import JsonSchema = require('../jsonSchema');
suite('JSON Parser', () => {

View file

@ -6,12 +6,12 @@
import assert = require('assert');
import SchemaService = require('../jsonSchemaService');
import JsonSchema = require('../json-toolbox/jsonSchema');
import Json = require('../json-toolbox/json');
import JsonSchema = require('../jsonSchema');
import Json = require('jsonc-parser');
import Parser = require('../jsonParser');
import fs = require('fs');
import path = require('path');
import {IXHROptions, IXHRResponse} from '../utils/httpRequest';
import {XHROptions, XHRResponse} from 'request-light';
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'
}
var requestServiceMock = function (options:IXHROptions) : Promise<IXHRResponse> {
var requestServiceMock = function (options:XHROptions) : Promise<XHRResponse> {
var uri = options.url;
if (uri.length && uri[uri.length - 1] === '#') {
uri = uri.substr(0, uri.length - 1);
}
var fileName = fixureDocuments[uri];
if (fileName) {
return new Promise<IXHRResponse>((c, e) => {
return new Promise<XHRResponse>((c, e) => {
var fixturePath = path.join(__dirname, '../../src/test/fixtures', fileName);
fs.readFile(fixturePath, 'UTF-8', (err, result) => {
err ? e({ responseText: '', status: 404 }) : c({ responseText: result.toString(), status: 200 })
});
});
}
return Promise.reject<IXHRResponse>({ responseText: '', status: 404 });
return Promise.reject<XHRResponse>({ responseText: '', status: 404 });
}
test('Resolving $refs', function(testDone) {

View file

@ -1,24 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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;
}

View file

@ -1,174 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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);
}
}

View file

@ -1,15 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 localize2(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;
}

View file

@ -1,49 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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);
}

View file

@ -16,4 +16,4 @@
"path": "./syntaxes/R.plist"
}]
}
}
}

View file

@ -13,7 +13,7 @@
"scripts": {
"test": "mocha",
"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"
},
"dependencies": {