From 767590151728baa77f2b3297a2b802c32121c8c2 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Fri, 1 Dec 2017 10:18:47 -0800 Subject: [PATCH 01/24] Basic tokenizer --- .vscode/launch.json | 11 +- .vscode/tasks.json | 6 +- gulpfile.js | 89 +++++++++++--- package.json | 9 +- src/client/language/characterStream.ts | 134 +++++++++++++++++++++ src/client/language/definitions.ts | 81 +++++++++++++ src/client/language/textIterator.ts | 53 ++++++++ src/client/language/textRangeCollection.ts | 103 ++++++++++++++++ src/client/language/tokenizer.ts | 119 ++++++++++++++++++ src/client/providers/completionProvider.ts | 11 +- 10 files changed, 584 insertions(+), 32 deletions(-) create mode 100644 src/client/language/characterStream.ts create mode 100644 src/client/language/definitions.ts create mode 100644 src/client/language/textIterator.ts create mode 100644 src/client/language/textRangeCollection.ts create mode 100644 src/client/language/tokenizer.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 3fbf982ae0b3..a69c3396ff4e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,7 @@ "outFiles": [ "${workspaceFolder}/out/**/*.js" ], - "preLaunchTask": "compile" + "preLaunchTask": "Compile" }, { "name": "Launch Extension as debugServer", // https://code.visualstudio.com/docs/extensions/example-debuggers @@ -30,7 +30,8 @@ "outFiles": [ "${workspaceFolder}/out/client/**/*.js" ], - "cwd": "${workspaceFolder}" + "cwd": "${workspaceFolder}", + "preLaunchTask": "Compile" }, { "name": "Launch Tests", @@ -47,7 +48,7 @@ "outFiles": [ "${workspaceFolder}/out/**/*.js" ], - "preLaunchTask": "compile" + "preLaunchTask": "Compile" }, { "name": "Launch Multiroot Tests", @@ -64,7 +65,7 @@ "outFiles": [ "${workspaceFolder}/out/**/*.js" ], - "preLaunchTask": "compile" + "preLaunchTask": "Compile" } ], "compounds": [ @@ -76,4 +77,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 155b94220ae6..ccf99a2c6f20 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,7 +13,7 @@ "script": "compile", "isBackground": true, "problemMatcher": [ - "$tsc", + "$tsc-watch", { "base": "$tslint5", "fileLocation": "relative" @@ -36,7 +36,7 @@ "panel": "shared" }, "problemMatcher": [ - "$tsc", + "$tsc-watch", { "base": "$tslint5", "fileLocation": "relative" @@ -72,4 +72,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 868a7281cc41..6c3f7819d003 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -22,7 +22,7 @@ const colors = require('colors/safe'); * named according to the checks performed on them. Each subset contains * the following one, as described in mathematical notation: * - * all ⊃ eol ⊇ indentation ⊃ copyright ⊃ typescript + * all ⊃ eol ⊇ indentation ⊃ typescript */ const all = [ @@ -115,12 +115,12 @@ const hygiene = (some, options) => { .toString('utf8') .split(/\r\n|\r|\n/) .forEach((line, i) => { - if (/^\s*$/.test(line)) { + if (/^\s*$/.test(line) || /^\S+.*$/.test(line)) { // Empty or whitespace lines are OK. } else if (/^(\s\s\s\s)+.*/.test(line)) { // Good indent. } else if (/^[\t]+.*/.test(line)) { - console.error(file.relative + '(' + (i + 1) + ',1): Bad whitespace indentation'); + console.error(file.relative + '(' + (i + 1) + ',1): Bad whitespace indentation (use 4 spaces instead of tabs or other)'); errorCount++; } }); @@ -137,7 +137,7 @@ const hygiene = (some, options) => { tsfmt: true }).then(result => { if (result.error) { - console.error(result.message); + console.error(result.message.trim()); errorCount++; } cb(null, file); @@ -147,14 +147,14 @@ const hygiene = (some, options) => { }); }); + const program = require('tslint').Linter.createProgram("./tsconfig.json"); + const linter = new tslint.Linter(options, program); const tsl = es.through(function (file) { const configuration = tslint.Configuration.findConfiguration(null, '.'); const options = { formatter: 'json' }; const contents = file.contents.toString('utf8'); - const program = require('tslint').Linter.createProgram("./tsconfig.json"); - const linter = new tslint.Linter(options, program); linter.lint(file.relative, contents, configuration.results); const result = linter.getResult(); if (result.failureCount > 0 || result.errorCount > 0) { @@ -206,22 +206,16 @@ const hygiene = (some, options) => { .pipe(filter(f => !f.stat.isDirectory())) .pipe(filter(eolFilter)) .pipe(options.skipEOL ? es.through() : eol) - .pipe(filter(indentationFilter)); - - if (!options.skipIndentationCheck) { - result = result - .pipe(indentation); - } + .pipe(filter(indentationFilter)) + .pipe(indentation); // Type script checks. let typescript = result - .pipe(filter(tslintFilter)); + .pipe(filter(tslintFilter)) + .pipe(formatting); - if (!options.skipFormatCheck) { - typescript = typescript - .pipe(formatting); - } - typescript = typescript.pipe(tsl) + typescript = typescript + .pipe(tsl) .pipe(tscFilesTracker) .pipe(tsc()); @@ -244,16 +238,32 @@ gulp.task('hygiene-staged', () => run({ mode: 'changes' })); gulp.task('hygiene-watch', ['hygiene-staged', 'hygiene-watch-runner']); gulp.task('hygiene-watch-runner', function () { + /** + * @type {Deferred} + */ + let runPromise; + return watch(all, { events: ['add', 'change'] }, function (event) { + // Damn bounce does not work, do our own checks. const start = new Date(); + if (runPromise && !runPromise.completed) { + console.log(`[${start.toLocaleTimeString()}] Already running`); + return; + } console.log(`[${start.toLocaleTimeString()}] Starting '${colors.cyan('hygiene-watch-runner')}'...`); + + runPromise = new Deferred(); // Skip indentation and formatting checks to speed up linting. - return run({ mode: 'watch', skipFormatCheck: true, skipIndentationCheck: true }) + run({ mode: 'watch', skipFormatCheck: true, skipIndentationCheck: true }) .then(() => { const end = new Date(); const time = (end.getTime() - start.getTime()) / 1000; console.log(`[${end.toLocaleTimeString()}] Finished '${colors.cyan('hygiene-watch-runner')}' after ${time} seconds`); - }); + runPromise.resolve(); + }) + .catch(runPromise.reject.bind); + + return runPromise.promise; }); }); @@ -402,7 +412,46 @@ function getModifiedFiles() { }); }); } + // this allows us to run hygiene as a git pre-commit hook. if (require.main === module) { run({ exitOnError: true, mode: 'staged' }); } + +class Deferred { + constructor(scope) { + this.scope = scope; + this._resolved = false; + this._rejected = false; + + this._promise = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + }); + } + resolve(value) { + this._resolve.apply(this.scope ? this.scope : this, arguments); + this._resolved = true; + } + /** + * Rejects the promise + * @param {any} reason + * @memberof Deferred + */ + reject(reason) { + this._reject.apply(this.scope ? this.scope : this, arguments); + this._rejected = true; + } + get promise() { + return this._promise; + } + get resolved() { + return this._resolved === true; + } + get rejected() { + return this._rejected === true; + } + get completed() { + return this._rejected || this._resolved; + } +} \ No newline at end of file diff --git a/package.json b/package.json index 0155540cb629..17145aa47ed6 100644 --- a/package.json +++ b/package.json @@ -1529,7 +1529,7 @@ "fuzzy": "^0.1.3", "get-port": "^3.2.0", "iconv-lite": "^0.4.19", - "inversify": "^4.5.2", + "inversify": "^4.5.1", "line-by-line": "^0.1.5", "lodash": "^4.17.4", "minimatch": "^3.0.3", @@ -1583,5 +1583,10 @@ "typescript": "^2.5.2", "typescript-formatter": "^6.0.0", "vscode": "^1.1.5" + }, + "__metadata": { + "id": "f1f59ae4-9318-4f3c-a9b5-81b2eaa5f8a5", + "publisherDisplayName": "Microsoft", + "publisherId": "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8" } -} +} \ No newline at end of file diff --git a/src/client/language/characterStream.ts b/src/client/language/characterStream.ts new file mode 100644 index 000000000000..fbb65f903644 --- /dev/null +++ b/src/client/language/characterStream.ts @@ -0,0 +1,134 @@ +'use strict'; + +// tslint:disable-next-line:import-name +import Char from 'typescript-char'; +import { ICharacterStream, ITextIterator } from './definitions'; +import { TextIterator } from './textIterator'; + +export class CharacterStream implements ICharacterStream { + private text: ITextIterator; + private pos: number; + private curChar: number; + private endOfStream: boolean; + + constructor(text: string | ITextIterator) { + const iter = text as ITextIterator; + const s = text as string; + + this.text = iter !== null ? iter : new TextIterator(s); + this.pos = 0; + this.curChar = text.length > 0 ? text.charCodeAt(0) : 0; + this.endOfStream = text.length === 0; + } + + public getText(): string { + return this.text.getText(); + } + + public get position(): number { + return this.pos; + } + + public set position(value: number) { + this.pos = value; + this.checkBounds(); + } + + public get currentChar(): number { + return this.curChar; + } + + public get nextChar(): number { + return this.position + 1 < this.text.length ? this.text.charCodeAt(this.position + 1) : 0; + } + + public get prevChar(): number { + return this.position - 1 >= 0 ? this.text.charCodeAt(this.position - 1) : 0; + } + + public isEndOfStream(): boolean { + return this.endOfStream; + } + + public lookAhead(offset: number): number { + const pos = this.position + offset; + return pos < 0 || pos >= this.text.length ? 0 : this.text.charCodeAt(pos); + } + + public advance(offset: number) { + this.position += offset; + } + + public moveNext(): boolean { + if (this.pos < this.text.length - 1) { + // Most common case, no need to check bounds extensively + this.pos += 1; + this.curChar = this.text.charCodeAt(this.pos); + return true; + } + this.advance(1); + return !this.endOfStream; + } + + public isAtWhiteSpace(): boolean { + return this.curChar <= Char.Space || this.curChar === 0x200B; + } + + public isAtLineBreak(): boolean { + return this.curChar === Char.CarriageReturn || this.curChar === Char.DataLineEscape; + } + + public skipLineBreak(): void { + if (this.curChar === Char.CarriageReturn) { + this.moveNext(); + if (this.currentChar === Char.LineFeed) { + this.moveNext(); + } + } else if (this.curChar === Char.LineFeed) { + this.moveNext(); + } + } + + public skipWhitespace(): void { + while (!this.endOfStream && this.isAtWhiteSpace()) { + this.moveNext(); + } + } + + public skipToEol(): void { + while (!this.endOfStream && !this.isAtLineBreak()) { + this.moveNext(); + } + } + + public skipToWhitespace(): void { + while (!this.endOfStream && !this.isAtWhiteSpace()) { + this.moveNext(); + } + } + + public isAtString(): boolean { + return this.curChar === 0x22 || this.curChar === 0x27; + } + + public charCodeAt(index: number): number { + return this.text.charCodeAt(index); + } + + public get length(): number { + return this.text.length; + } + + private checkBounds(): void { + if (this.pos < 0) { + this.pos = 0; + } + + this.endOfStream = this.pos >= this.text.length; + if (this.endOfStream) { + this.pos = this.text.length; + } + + this.curChar = this.endOfStream ? 0 : this.text.charCodeAt(this.pos); + } +} diff --git a/src/client/language/definitions.ts b/src/client/language/definitions.ts new file mode 100644 index 000000000000..a4f7a22b4da7 --- /dev/null +++ b/src/client/language/definitions.ts @@ -0,0 +1,81 @@ +'use strict'; + +export interface ITextRange { + readonly start: number; + readonly end: number; + readonly length: number; + contains(position: number): boolean; +} + +export class TextRange implements ITextRange { + public readonly start: number; + public readonly length: number; + + constructor(start: number, length: number) { + if (start < 0) { + throw new Error('start must be non-negative'); + } + if (length < 0) { + throw new Error('length must be non-negative'); + } + this.start = start; + this.length = length; + } + + public static fromBounds(start: number, end: number) { + return new TextRange(start, end - start); + } + + public get end(): number { + return this.start + this.length; + } + + public contains(position: number): boolean { + return position >= this.start && position < this.end; + } +} + +export interface ITextRangeCollection extends ITextRange { + count: number; + getItemAt(index: number): T; + getItemAtPosition(position: number): number; + getItemContaining(position: number): number; +} + +export interface ITextIterator { + readonly length: number; + charCodeAt(index: number): number; + getText(): string; +} + +export interface ICharacterStream extends ITextIterator { + position: number; + readonly currentChar: number; + readonly nextChar: number; + readonly prevChar: number; + getText(): string; + isEndOfStream(): boolean; + lookAhead(offset: number): number; + advance(offset: number); + moveNext(): boolean; + isAtWhiteSpace(): boolean; + isAtLineBreak(): boolean; + isAtString(): boolean; + skipLineBreak(): void; + skipWhitespace(): void; + skipToEol(): void; + skipToWhitespace(): void; +} + +export enum TokenType { + String +} + +export interface IToken extends ITextRange { + readonly type: TokenType; +} + +export interface ITokenizer { + Tokenize(text: string): ITextRangeCollection; + Tokenize(text: string, start: number, length: number): ITextRangeCollection; +} diff --git a/src/client/language/textIterator.ts b/src/client/language/textIterator.ts new file mode 100644 index 000000000000..8af0e1caefda --- /dev/null +++ b/src/client/language/textIterator.ts @@ -0,0 +1,53 @@ +'use strict'; + +import { Position, Range, TextDocument } from 'vscode'; +import { ITextIterator } from './definitions'; + +export class TextIterator implements ITextIterator { + private text: string; + + constructor(text: string) { + this.text = text; + } + + public charCodeAt(index: number): number { + if (index >= 0 && index < this.length) { + return this.text.charCodeAt[index]; + } + return 0; + } + + public get length(): number { + return this.text.length; + } + + public getText(): string { + return this.text; + } +} + +export class DocumentTextIterator implements ITextIterator { + public readonly length: number; + + private document: TextDocument; + + constructor(document: TextDocument) { + this.document = document; + + const lastIndex = this.document.lineCount - 1; + const lastLine = this.document.lineAt(lastIndex); + const end = new Position(lastIndex, lastLine.range.end.character); + this.length = this.document.offsetAt(end); + } + + public charCodeAt(index: number): number { + const position = this.document.positionAt(index); + return this.document + .getText(new Range(position, position.translate(0, 1))) + .charCodeAt(position.character); + } + + public getText(): string { + return this.document.getText(); + } +} diff --git a/src/client/language/textRangeCollection.ts b/src/client/language/textRangeCollection.ts new file mode 100644 index 000000000000..5448445c3092 --- /dev/null +++ b/src/client/language/textRangeCollection.ts @@ -0,0 +1,103 @@ +'use strict'; + +import { ITextRange, ITextRangeCollection } from './definitions'; + +export class TextRangeCollection implements ITextRangeCollection { + private items: T[]; + + constructor(items: T[]) { + this.items = items; + } + + public get start(): number { + return this.items.length > 0 ? this.items[0].start : 0; + } + + public get end(): number { + return this.items.length > 0 ? this.items[this.items.length - 1].end : 0; + } + + public get length(): number { + return this.end - this.start; + } + + public get count(): number { + return this.items.length; + } + + public contains(position: number) { + return position >= this.start && position < this.end; + } + + public getItemAt(index: number): T { + if (index < 0 || index >= this.items.length) { + throw new Error('index is out of range'); + } + return this.items[index] as T; + } + + public getItemAtPosition(position: number): number { + if (this.count === 0) { + return -1; + } + if (position < this.start) { + return -1; + } + if (position >= this.end) { + return -1; + } + + let min = 0; + let max = this.count - 1; + + while (min <= max) { + const mid = min + (max - min) / 2; + const item = this.items[mid]; + + if (item.start === position) { + return mid; + } + + if (position < item.start) { + max = mid - 1; + } else { + min = mid + 1; + } + } + return -1; + } + + public getItemContaining(position: number): number { + if (this.count === 0) { + return -1; + } + if (position < this.start) { + return -1; + } + if (position > this.end) { + return -1; + } + + let min = 0; + let max = this.count - 1; + + while (min <= max) { + const mid = min + (max - min) / 2; + const item = this[mid]; + + if (item.Contains(position)) { + return mid; + } + if (mid < this.count - 1 && item.end <= position && position < this.items[mid + 1].start) { + return -1; + } + + if (position < item.Start) { + max = mid - 1; + } else { + min = mid + 1; + } + } + return -1; + } +} diff --git a/src/client/language/tokenizer.ts b/src/client/language/tokenizer.ts new file mode 100644 index 000000000000..25af381dfc26 --- /dev/null +++ b/src/client/language/tokenizer.ts @@ -0,0 +1,119 @@ +'use strict'; + +import Char from 'typescript-char'; +import { CharacterStream } from './characterStream'; +import { ICharacterStream, ITextRangeCollection, IToken, ITokenizer, TextRange, TokenType } from './definitions'; +import { TextRangeCollection } from './textRangeCollection'; + +enum QuoteType { + None, + Single, + Double, + TripleSingle, + TripleDouble +} + +class Token extends TextRange implements IToken { + public readonly type: TokenType; + + constructor(type: TokenType, start: number, length: number) { + super(start, length); + this.type = type; + } +} + +export class Tokenizer implements ITokenizer { + private cs: ICharacterStream; + private tokens: IToken[] = []; + + public Tokenize(text: string): ITextRangeCollection; + public Tokenize(text: string, start: number, length: number): ITextRangeCollection; + + public Tokenize(text: string, start?: number, length?: number): ITextRangeCollection { + if (start === undefined) { + start = 0; + } else if (start < 0 || start >= text.length) { + throw new Error('Invalid range start'); + } + + if (length === undefined) { + length = text.length; + } else if (length < 0 || start + length >= text.length) { + throw new Error('Invalid range length'); + } + + this.cs = new CharacterStream(text); + this.cs.position = start; + + const end = start + length; + while (!this.cs.isEndOfStream()) { + this.AddNextToken(); + if (this.cs.position >= end) { + break; + } + } + return new TextRangeCollection(this.tokens); + } + + private AddNextToken(): void { + this.cs.skipWhitespace(); + if (this.cs.isEndOfStream()) { + return; + } + + if (!this.handleCharacter()) { + this.cs.moveNext(); + } + } + + private handleCharacter(): boolean { + const quoteType = this.getQuoteType(); + if (quoteType !== QuoteType.None) { + this.handleString(quoteType); + return true; + } + return false; + } + + private getQuoteType(): QuoteType { + if (this.cs.currentChar === Char.SingleQuote) { + return this.cs.nextChar === Char.SingleQuote && this.cs.lookAhead(2) === Char.SingleQuote + ? QuoteType.TripleSingle + : QuoteType.Single; + } + if (this.cs.currentChar === Char.DoubleQuote) { + return this.cs.nextChar === Char.DoubleQuote && this.cs.lookAhead(2) === Char.DoubleQuote + ? QuoteType.TripleDouble + : QuoteType.Double; + } + return QuoteType.None; + } + + private handleString(quoteType: QuoteType): void { + const start = this.cs.position; + if (quoteType === QuoteType.Single || quoteType === QuoteType.Double) { + this.cs.moveNext(); + this.skipToSingleEndQuote(quoteType === QuoteType.Single + ? Char.SingleQuote + : Char.DoubleQuote); + } else { + this.cs.advance(3); + this.skipToTripleEndQuote(quoteType === QuoteType.TripleSingle + ? Char.SingleQuote + : Char.DoubleQuote); + } + this.tokens.push(new Token(TokenType.String, start, this.cs.position - start)); + } + + private skipToSingleEndQuote(quote: number): void { + while (!this.cs.isEndOfStream() && this.cs.currentChar !== quote) { + this.cs.moveNext(); + } + } + + private skipToTripleEndQuote(quote: number): void { + while (!this.cs.isEndOfStream() && (this.cs.currentChar !== quote || this.cs.nextChar !== quote || this.cs.lookAhead(2) !== quote)) { + this.cs.moveNext(); + } + } +} diff --git a/src/client/providers/completionProvider.ts b/src/client/providers/completionProvider.ts index b453d19a7f19..020fb71daffc 100644 --- a/src/client/providers/completionProvider.ts +++ b/src/client/providers/completionProvider.ts @@ -1,8 +1,9 @@ 'use strict'; import * as vscode from 'vscode'; -import { ProviderResult, SnippetString, Uri } from 'vscode'; +import { Position, ProviderResult, SnippetString, Uri } from 'vscode'; import { PythonSettings } from '../common/configSettings'; +import { Tokenizer } from '../language/tokenizer'; import { JediFactory } from '../languageServices/jediProxyFactory'; import { captureTelemetry } from '../telemetry'; import { COMPLETION } from '../telemetry/constants'; @@ -47,7 +48,7 @@ export class PythonCompletionItemProvider implements vscode.CompletionItemProvid return Promise.resolve([]); } // If starts with a """ (possible doc string), then return - if (lineText.trim().startsWith('"""')) { + if (this.isPositionInsideString(document, position)) { return Promise.resolve([]); } const type = proxy.CommandType.Completions; @@ -66,4 +67,10 @@ export class PythonCompletionItemProvider implements vscode.CompletionItemProvid return PythonCompletionItemProvider.parseData(data, document.uri); }); } + + private isPositionInsideString(document: vscode.TextDocument, position: vscode.Position): boolean { + const text = document.getText(new vscode.Range(new Position(0, 0), position)); + const t = new Tokenizer(); + return t.Tokenize(text).getItemContaining(document.offsetAt(position)) >= 0; + } } From eb4266980d1308fbae7a0017a9da673b788a9e45 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Fri, 1 Dec 2017 12:19:00 -0800 Subject: [PATCH 02/24] Fixed property names --- src/client/language/textRangeCollection.ts | 6 +++--- src/client/language/tokenizer.ts | 3 +++ src/client/providers/completionProvider.ts | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/client/language/textRangeCollection.ts b/src/client/language/textRangeCollection.ts index 5448445c3092..0464dc945382 100644 --- a/src/client/language/textRangeCollection.ts +++ b/src/client/language/textRangeCollection.ts @@ -83,16 +83,16 @@ export class TextRangeCollection implements ITextRangeColl while (min <= max) { const mid = min + (max - min) / 2; - const item = this[mid]; + const item = this.items[mid]; - if (item.Contains(position)) { + if (item.contains(position)) { return mid; } if (mid < this.count - 1 && item.end <= position && position < this.items[mid + 1].start) { return -1; } - if (position < item.Start) { + if (position < item.start) { max = mid - 1; } else { min = mid + 1; diff --git a/src/client/language/tokenizer.ts b/src/client/language/tokenizer.ts index 25af381dfc26..5cb0d4e3e474 100644 --- a/src/client/language/tokenizer.ts +++ b/src/client/language/tokenizer.ts @@ -1,5 +1,6 @@ 'use strict'; +// tslint:disable-next-line:import-name import Char from 'typescript-char'; import { CharacterStream } from './characterStream'; import { ICharacterStream, ITextRangeCollection, IToken, ITokenizer, TextRange, TokenType } from './definitions'; @@ -109,11 +110,13 @@ export class Tokenizer implements ITokenizer { while (!this.cs.isEndOfStream() && this.cs.currentChar !== quote) { this.cs.moveNext(); } + this.cs.moveNext(); } private skipToTripleEndQuote(quote: number): void { while (!this.cs.isEndOfStream() && (this.cs.currentChar !== quote || this.cs.nextChar !== quote || this.cs.lookAhead(2) !== quote)) { this.cs.moveNext(); } + this.cs.advance(3); } } diff --git a/src/client/providers/completionProvider.ts b/src/client/providers/completionProvider.ts index 020fb71daffc..b097b1633a9e 100644 --- a/src/client/providers/completionProvider.ts +++ b/src/client/providers/completionProvider.ts @@ -69,7 +69,8 @@ export class PythonCompletionItemProvider implements vscode.CompletionItemProvid } private isPositionInsideString(document: vscode.TextDocument, position: vscode.Position): boolean { - const text = document.getText(new vscode.Range(new Position(0, 0), position)); + const tokenizeTo = position.translate(1, 0); + const text = document.getText(new vscode.Range(new Position(0, 0), tokenizeTo)); const t = new Tokenizer(); return t.Tokenize(text).getItemContaining(document.offsetAt(position)) >= 0; } From 275697428d0b725083b2054b24e9aaa08b0db7b1 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Fri, 1 Dec 2017 15:10:20 -0800 Subject: [PATCH 03/24] Tests, round I --- src/client/language/characterStream.ts | 63 ++++++------- src/client/language/definitions.ts | 2 + src/client/language/textIterator.ts | 4 +- src/test/.vscode/settings.json | 3 +- src/test/index.ts | 3 +- src/test/language/characterStream.test.ts | 108 ++++++++++++++++++++++ src/test/language/textIterator.test.ts | 24 +++++ src/test/language/textRange.test.ts | 52 +++++++++++ 8 files changed, 222 insertions(+), 37 deletions(-) create mode 100644 src/test/language/characterStream.test.ts create mode 100644 src/test/language/textIterator.test.ts create mode 100644 src/test/language/textRange.test.ts diff --git a/src/client/language/characterStream.ts b/src/client/language/characterStream.ts index fbb65f903644..e90ef7f2e6b7 100644 --- a/src/client/language/characterStream.ts +++ b/src/client/language/characterStream.ts @@ -7,18 +7,15 @@ import { TextIterator } from './textIterator'; export class CharacterStream implements ICharacterStream { private text: ITextIterator; - private pos: number; - private curChar: number; - private endOfStream: boolean; + private _position: number; + private _currentChar: number; + private _isEndOfStream: boolean; constructor(text: string | ITextIterator) { - const iter = text as ITextIterator; - const s = text as string; - - this.text = iter !== null ? iter : new TextIterator(s); - this.pos = 0; - this.curChar = text.length > 0 ? text.charCodeAt(0) : 0; - this.endOfStream = text.length === 0; + this.text = typeof text === 'string' ? new TextIterator(text) : text; + this._position = 0; + this._currentChar = text.length > 0 ? text.charCodeAt(0) : 0; + this._isEndOfStream = text.length === 0; } public getText(): string { @@ -26,16 +23,16 @@ export class CharacterStream implements ICharacterStream { } public get position(): number { - return this.pos; + return this._position; } public set position(value: number) { - this.pos = value; + this._position = value; this.checkBounds(); } public get currentChar(): number { - return this.curChar; + return this._currentChar; } public get nextChar(): number { @@ -47,11 +44,11 @@ export class CharacterStream implements ICharacterStream { } public isEndOfStream(): boolean { - return this.endOfStream; + return this._isEndOfStream; } public lookAhead(offset: number): number { - const pos = this.position + offset; + const pos = this._position + offset; return pos < 0 || pos >= this.text.length ? 0 : this.text.charCodeAt(pos); } @@ -60,55 +57,55 @@ export class CharacterStream implements ICharacterStream { } public moveNext(): boolean { - if (this.pos < this.text.length - 1) { + if (this._position < this.text.length - 1) { // Most common case, no need to check bounds extensively - this.pos += 1; - this.curChar = this.text.charCodeAt(this.pos); + this._position += 1; + this._currentChar = this.text.charCodeAt(this._position); return true; } this.advance(1); - return !this.endOfStream; + return !this.isEndOfStream(); } public isAtWhiteSpace(): boolean { - return this.curChar <= Char.Space || this.curChar === 0x200B; + return this.currentChar <= Char.Space || this.currentChar === 0x200B; // Unicode whitespace } public isAtLineBreak(): boolean { - return this.curChar === Char.CarriageReturn || this.curChar === Char.DataLineEscape; + return this.currentChar === Char.CarriageReturn || this.currentChar === Char.LineFeed; } public skipLineBreak(): void { - if (this.curChar === Char.CarriageReturn) { + if (this._currentChar === Char.CarriageReturn) { this.moveNext(); if (this.currentChar === Char.LineFeed) { this.moveNext(); } - } else if (this.curChar === Char.LineFeed) { + } else if (this._currentChar === Char.LineFeed) { this.moveNext(); } } public skipWhitespace(): void { - while (!this.endOfStream && this.isAtWhiteSpace()) { + while (!this.isEndOfStream() && this.isAtWhiteSpace()) { this.moveNext(); } } public skipToEol(): void { - while (!this.endOfStream && !this.isAtLineBreak()) { + while (!this.isEndOfStream() && !this.isAtLineBreak()) { this.moveNext(); } } public skipToWhitespace(): void { - while (!this.endOfStream && !this.isAtWhiteSpace()) { + while (!this.isEndOfStream() && !this.isAtWhiteSpace()) { this.moveNext(); } } public isAtString(): boolean { - return this.curChar === 0x22 || this.curChar === 0x27; + return this.currentChar === Char.SingleQuote || this.currentChar === Char.DoubleQuote; } public charCodeAt(index: number): number { @@ -120,15 +117,15 @@ export class CharacterStream implements ICharacterStream { } private checkBounds(): void { - if (this.pos < 0) { - this.pos = 0; + if (this._position < 0) { + this._position = 0; } - this.endOfStream = this.pos >= this.text.length; - if (this.endOfStream) { - this.pos = this.text.length; + this._isEndOfStream = this._position >= this.text.length; + if (this._isEndOfStream) { + this._position = this.text.length; } - this.curChar = this.endOfStream ? 0 : this.text.charCodeAt(this.pos); + this._currentChar = this._isEndOfStream ? 0 : this.text.charCodeAt(this._position); } } diff --git a/src/client/language/definitions.ts b/src/client/language/definitions.ts index a4f7a22b4da7..3e965b0b91bf 100644 --- a/src/client/language/definitions.ts +++ b/src/client/language/definitions.ts @@ -8,6 +8,8 @@ export interface ITextRange { } export class TextRange implements ITextRange { + public static readonly empty = TextRange.fromBounds(0, 0); + public readonly start: number; public readonly length: number; diff --git a/src/client/language/textIterator.ts b/src/client/language/textIterator.ts index 8af0e1caefda..927078da3939 100644 --- a/src/client/language/textIterator.ts +++ b/src/client/language/textIterator.ts @@ -11,8 +11,8 @@ export class TextIterator implements ITextIterator { } public charCodeAt(index: number): number { - if (index >= 0 && index < this.length) { - return this.text.charCodeAt[index]; + if (index >= 0 && index < this.text.length) { + return this.text.charCodeAt(index); } return 0; } diff --git a/src/test/.vscode/settings.json b/src/test/.vscode/settings.json index 2218e2cecd87..12cde5b9dc53 100644 --- a/src/test/.vscode/settings.json +++ b/src/test/.vscode/settings.json @@ -21,5 +21,6 @@ "python.linting.pydocstyleEnabled": false, "python.linting.pylamaEnabled": false, "python.linting.mypyEnabled": false, - "python.formatting.provider": "yapf" + "python.formatting.provider": "yapf", + "python.pythonPath": "python" } \ No newline at end of file diff --git a/src/test/index.ts b/src/test/index.ts index 0202b4e2dc43..acce5db2392a 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -8,6 +8,7 @@ testRunner.configure({ ui: 'tdd', useColors: true, timeout: 25000, - retries: 3 + retries: 3, + grep: 'Language.CharacterStream' } as {}); module.exports = testRunner; diff --git a/src/test/language/characterStream.test.ts b/src/test/language/characterStream.test.ts new file mode 100644 index 000000000000..d1c003d6b2d9 --- /dev/null +++ b/src/test/language/characterStream.test.ts @@ -0,0 +1,108 @@ +import * as assert from 'assert'; +// tslint:disable-next-line:import-name +import Char from 'typescript-char'; +import { CharacterStream } from '../../client/language/characterStream'; +import { ICharacterStream, TextRange } from '../../client/language/definitions'; +import { TextIterator } from '../../client/language/textIterator'; + +// tslint:disable-next-line:max-func-body-length +suite('Language.CharacterStream', () => { + test('Iteration (string)', async () => { + const content = 'some text'; + const cs = new CharacterStream(content); + testIteration(cs, content); + }); + test('Iteration (iterator)', async () => { + const content = 'some text'; + const cs = new CharacterStream(new TextIterator(content)); + testIteration(cs, content); + }); + test('Positioning', async () => { + const content = 'some text'; + const cs = new CharacterStream(content); + assert.equal(cs.position, 0); + cs.advance(1); + assert.equal(cs.position, 1); + cs.advance(1); + assert.equal(cs.position, 2); + cs.advance(2); + assert.equal(cs.position, 4); + cs.advance(-3); + assert.equal(cs.position, 1); + cs.advance(-3); + assert.equal(cs.position, 0); + cs.advance(100); + assert.equal(cs.position, content.length); + }); + test('Characters', async () => { + const content = 'some \ttext "" \' \' \n text \r\n more text'; + const cs = new CharacterStream(content); + for (let i = 0; i < content.length; i += 1) { + assert.equal(cs.currentChar, content.charCodeAt(i)); + + assert.equal(cs.nextChar, i < content.length - 1 ? content.charCodeAt(i + 1) : 0); + assert.equal(cs.prevChar, i > 0 ? content.charCodeAt(i - 1) : 0); + + assert.equal(cs.lookAhead(2), i < content.length - 2 ? content.charCodeAt(i + 2) : 0); + assert.equal(cs.lookAhead(-2), i > 1 ? content.charCodeAt(i - 2) : 0); + + const ch = content.charCodeAt(i); + const isLineBreak = ch === Char.LineFeed || ch === Char.CarriageReturn; + assert.equal(cs.isAtWhiteSpace(), ch === Char.Tab || ch === Char.Space || isLineBreak); + assert.equal(cs.isAtLineBreak(), isLineBreak); + assert.equal(cs.isAtString(), ch === Char.SingleQuote || ch === Char.DoubleQuote); + + cs.moveNext(); + } + }); + test('Skip', async () => { + const content = 'some \ttext "" \' \' \n text \r\n more text'; + const cs = new CharacterStream(content); + + cs.skipWhitespace(); + assert.equal(cs.position, 0); + + cs.skipToWhitespace(); + assert.equal(cs.position, 4); + + cs.skipToWhitespace(); + assert.equal(cs.position, 4); + + cs.skipWhitespace(); + assert.equal(cs.position, 6); + + cs.skipLineBreak(); + assert.equal(cs.position, 6); + + cs.skipToEol(); + assert.equal(cs.position, 18); + + cs.skipLineBreak(); + assert.equal(cs.position, 19); + }); +}); + +function testIteration(cs: ICharacterStream, content: string) { + assert.equal(cs.position, 0); + assert.equal(cs.length, content.length); + assert.equal(cs.isEndOfStream(), false); + + for (let i = -2; i < content.length + 2; i += 1) { + const ch = cs.charCodeAt(i); + if (i < 0 || i >= content.length) { + assert.equal(ch, 0); + } else { + assert.equal(ch, content.charCodeAt(i)); + } + } + + for (let i = 0; i < content.length; i += 1) { + assert.equal(cs.isEndOfStream(), false); + assert.equal(cs.position, i); + assert.equal(cs.currentChar, content.charCodeAt(i)); + cs.moveNext(); + } + + assert.equal(cs.isEndOfStream(), true); + assert.equal(cs.position, content.length); +} diff --git a/src/test/language/textIterator.test.ts b/src/test/language/textIterator.test.ts new file mode 100644 index 000000000000..dddfe3478ec5 --- /dev/null +++ b/src/test/language/textIterator.test.ts @@ -0,0 +1,24 @@ +import * as assert from 'assert'; +import { TextIterator } from '../../client/language/textIterator'; + +// tslint:disable-next-line:max-func-body-length +suite('Language.TextIterator', () => { + test('Construction', async () => { + const content = 'some text'; + const ti = new TextIterator(content); + assert.equal(ti.length, content.length); + assert.equal(ti.getText(), content); + }); + test('Iteration', async () => { + const content = 'some text'; + const ti = new TextIterator(content); + for (let i = -2; i < content.length + 2; i += 1) { + const ch = ti.charCodeAt(i); + if (i < 0 || i >= content.length) { + assert.equal(ch, 0); + } else { + assert.equal(ch, content.charCodeAt(i)); + } + } + }); +}); diff --git a/src/test/language/textRange.test.ts b/src/test/language/textRange.test.ts new file mode 100644 index 000000000000..4e0da8feb06c --- /dev/null +++ b/src/test/language/textRange.test.ts @@ -0,0 +1,52 @@ +import * as assert from 'assert'; +import { TextRange } from '../../client/language/definitions'; + +// tslint:disable-next-line:max-func-body-length +suite('Language.TextRange', () => { + test('Empty static', async () => { + const e = TextRange.empty; + assert.equal(e.start, 0); + assert.equal(e.end, 0); + assert.equal(e.length, 0); + }); + test('Construction', async () => { + let r = new TextRange(10, 20); + assert.equal(r.start, 10); + assert.equal(r.end, 30); + assert.equal(r.length, 20); + r = new TextRange(10, 0); + assert.equal(r.start, 10); + assert.equal(r.end, 10); + assert.equal(r.length, 0); + }); + test('From bounds', async () => { + let r = TextRange.fromBounds(7, 9); + assert.equal(r.start, 7); + assert.equal(r.end, 9); + assert.equal(r.length, 2); + + r = TextRange.fromBounds(5, 5); + assert.equal(r.start, 5); + assert.equal(r.end, 5); + assert.equal(r.length, 0); + }); + test('Contains', async () => { + const r = TextRange.fromBounds(7, 9); + assert.equal(r.contains(-1), false); + assert.equal(r.contains(6), false); + assert.equal(r.contains(7), true); + assert.equal(r.contains(8), true); + assert.equal(r.contains(9), false); + assert.equal(r.contains(10), false); + }); + test('Exceptions', async () => { + assert.throws( + () => { const e = new TextRange(0, -1); }, + Error + ); + assert.throws( + () => { const e = TextRange.fromBounds(3, 1); }, + Error + ); + }); +}); From c2c1ced6cf64be60ca808c269291af191df3b3b2 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Fri, 1 Dec 2017 16:07:28 -0800 Subject: [PATCH 04/24] Tests, round II --- src/client/language/definitions.ts | 3 +- src/client/language/tokenizer.ts | 13 +++++ src/client/providers/completionProvider.ts | 15 +++--- src/test/index.ts | 2 +- src/test/language/textRangeCollection.test.ts | 53 +++++++++++++++++++ src/test/language/tokenizer.test.ts | 39 ++++++++++++++ 6 files changed, 115 insertions(+), 10 deletions(-) create mode 100644 src/test/language/textRangeCollection.test.ts create mode 100644 src/test/language/tokenizer.test.ts diff --git a/src/client/language/definitions.ts b/src/client/language/definitions.ts index 3e965b0b91bf..d001adccbd88 100644 --- a/src/client/language/definitions.ts +++ b/src/client/language/definitions.ts @@ -70,7 +70,8 @@ export interface ICharacterStream extends ITextIterator { } export enum TokenType { - String + String, + Comment } export interface IToken extends ITextRange { diff --git a/src/client/language/tokenizer.ts b/src/client/language/tokenizer.ts index 5cb0d4e3e474..6d63f4168414 100644 --- a/src/client/language/tokenizer.ts +++ b/src/client/language/tokenizer.ts @@ -73,9 +73,22 @@ export class Tokenizer implements ITokenizer { this.handleString(quoteType); return true; } + switch (this.cs.currentChar) { + case Char.Hash: + this.handleComment(); + break; + default: + break; + } return false; } + private handleComment(): void { + const start = this.cs.position; + this.cs.skipToEol(); + this.tokens.push(new Token(TokenType.Comment, start, this.cs.position - start)); + } + private getQuoteType(): QuoteType { if (this.cs.currentChar === Char.SingleQuote) { return this.cs.nextChar === Char.SingleQuote && this.cs.lookAhead(2) === Char.SingleQuote diff --git a/src/client/providers/completionProvider.ts b/src/client/providers/completionProvider.ts index b097b1633a9e..10c930348e33 100644 --- a/src/client/providers/completionProvider.ts +++ b/src/client/providers/completionProvider.ts @@ -3,6 +3,7 @@ import * as vscode from 'vscode'; import { Position, ProviderResult, SnippetString, Uri } from 'vscode'; import { PythonSettings } from '../common/configSettings'; +import { TokenType } from '../language/definitions'; import { Tokenizer } from '../language/tokenizer'; import { JediFactory } from '../languageServices/jediProxyFactory'; import { captureTelemetry } from '../telemetry'; @@ -43,12 +44,8 @@ export class PythonCompletionItemProvider implements vscode.CompletionItemProvid if (lineText.match(/^\s*\/\//)) { return Promise.resolve([]); } - // If starts with a comment, then return - if (lineText.trim().startsWith('#')) { - return Promise.resolve([]); - } - // If starts with a """ (possible doc string), then return - if (this.isPositionInsideString(document, position)) { + // Suppress completion inside string and comments + if (this.isPositionInsideStringOrComment(document, position)) { return Promise.resolve([]); } const type = proxy.CommandType.Completions; @@ -68,10 +65,12 @@ export class PythonCompletionItemProvider implements vscode.CompletionItemProvid }); } - private isPositionInsideString(document: vscode.TextDocument, position: vscode.Position): boolean { + private isPositionInsideStringOrComment(document: vscode.TextDocument, position: vscode.Position): boolean { const tokenizeTo = position.translate(1, 0); const text = document.getText(new vscode.Range(new Position(0, 0), tokenizeTo)); const t = new Tokenizer(); - return t.Tokenize(text).getItemContaining(document.offsetAt(position)) >= 0; + const tokens = t.Tokenize(text); + const index = tokens.getItemContaining(document.offsetAt(position)); + return index >= 0 && (tokens[index].TokenType === TokenType.String || tokens[index].TokenType === TokenType.Comment); } } diff --git a/src/test/index.ts b/src/test/index.ts index acce5db2392a..6480c37439b6 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -9,6 +9,6 @@ testRunner.configure({ useColors: true, timeout: 25000, retries: 3, - grep: 'Language.CharacterStream' + grep: 'Language.Tokenizer' } as {}); module.exports = testRunner; diff --git a/src/test/language/textRangeCollection.test.ts b/src/test/language/textRangeCollection.test.ts new file mode 100644 index 000000000000..5b5dfad8c8b6 --- /dev/null +++ b/src/test/language/textRangeCollection.test.ts @@ -0,0 +1,53 @@ +import * as assert from 'assert'; +import { TextRange } from '../../client/language/definitions'; +import { TextRangeCollection } from '../../client/language/textRangeCollection'; + +// tslint:disable-next-line:max-func-body-length +suite('Language.TextRangeCollection', () => { + test('Empty', async () => { + const items: TextRange[] = []; + const c = new TextRangeCollection(items); + assert.equal(c.start, 0); + assert.equal(c.end, 0); + assert.equal(c.length, 0); + assert.equal(c.count, 0); + }); + test('Basic', async () => { + const items: TextRange[] = []; + items.push(new TextRange(2, 1)); + items.push(new TextRange(4, 2)); + const c = new TextRangeCollection(items); + assert.equal(c.start, 2); + assert.equal(c.end, 6); + assert.equal(c.length, 4); + assert.equal(c.count, 2); + + assert.equal(c.getItemAt(0).start, 2); + assert.equal(c.getItemAt(0).length, 1); + + assert.equal(c.getItemAt(1).start, 4); + assert.equal(c.getItemAt(1).length, 2); + }); + test('Contains position', async () => { + const items: TextRange[] = []; + items.push(new TextRange(2, 1)); + items.push(new TextRange(4, 2)); + const c = new TextRangeCollection(items); + const results = [-1, -1, 0, -1, 1, 1, -1]; + for (let i = 0; i < results.length; i += 1) { + const index = c.getItemContaining(i); + assert.equal(index, results[i]); + } + }); + test('Item at position', async () => { + const items: TextRange[] = []; + items.push(new TextRange(2, 1)); + items.push(new TextRange(4, 2)); + const c = new TextRangeCollection(items); + const results = [-1, -1, 0, -1, 1, 1, -1]; + for (let i = 0; i < results.length; i += 1) { + const index = c.getItemAtPosition(i); + assert.equal(index, results[i]); + } + }); +}); diff --git a/src/test/language/tokenizer.test.ts b/src/test/language/tokenizer.test.ts new file mode 100644 index 000000000000..29874f75c26a --- /dev/null +++ b/src/test/language/tokenizer.test.ts @@ -0,0 +1,39 @@ +import * as assert from 'assert'; +import { TextRange, TokenType } from '../../client/language/definitions'; +import { TextRangeCollection } from '../../client/language/textRangeCollection'; +import { Tokenizer } from '../../client/language/tokenizer'; + +// tslint:disable-next-line:max-func-body-length +suite('Language.Tokenizer', () => { + test('Empty', async () => { + const t = new Tokenizer(); + const tokens = t.Tokenize(''); + assert.equal(tokens instanceof TextRangeCollection, true); + assert.equal(tokens.count, 0); + assert.equal(tokens.length, 0); + }); + test('Strings', async () => { + const t = new Tokenizer(); + const tokens = t.Tokenize(' "string" """line1\n#line2"""\t\'un#closed'); + assert.equal(tokens.count, 3); + + const ranges = [1, 8, 10, 18, 29, 10]; + for (let i = 0; i < ranges.length / 2; i += 2) { + assert.equal(tokens.getItemAt(i).start, ranges[i]); + assert.equal(tokens.getItemAt(i).length, ranges[i + 1]); + assert.equal(tokens.getItemAt(i).type, TokenType.String); + } + }); + test('Comments', async () => { + const t = new Tokenizer(); + const tokens = t.Tokenize(' #co"""mment1\n\t\n#comm\'ent2 '); + assert.equal(tokens.count, 2); + + const ranges = [1, 12, 15, 11]; + for (let i = 0; i < ranges.length / 2; i += 2) { + assert.equal(tokens.getItemAt(i).start, ranges[i]); + assert.equal(tokens.getItemAt(i).length, ranges[i + 1]); + assert.equal(tokens.getItemAt(i).type, TokenType.Comment); + } + }); +}); From 14864a580d0affdc9f8fda616fe20e2dbdbb2df9 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Sun, 3 Dec 2017 16:42:46 -0800 Subject: [PATCH 05/24] tokenizer test --- src/test/language/tokenizer.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/language/tokenizer.test.ts b/src/test/language/tokenizer.test.ts index 29874f75c26a..1ce0165e4241 100644 --- a/src/test/language/tokenizer.test.ts +++ b/src/test/language/tokenizer.test.ts @@ -18,9 +18,9 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.count, 3); const ranges = [1, 8, 10, 18, 29, 10]; - for (let i = 0; i < ranges.length / 2; i += 2) { - assert.equal(tokens.getItemAt(i).start, ranges[i]); - assert.equal(tokens.getItemAt(i).length, ranges[i + 1]); + for (let i = 0; i < tokens.count; i += 1) { + assert.equal(tokens.getItemAt(i).start, ranges[2 * i]); + assert.equal(tokens.getItemAt(i).length, ranges[2 * i + 1]); assert.equal(tokens.getItemAt(i).type, TokenType.String); } }); From 0ed51d64ee215356c7e5665c3b642c386e997f2d Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Sun, 3 Dec 2017 16:46:19 -0800 Subject: [PATCH 06/24] Remove temorary change --- src/test/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/index.ts b/src/test/index.ts index 6480c37439b6..0202b4e2dc43 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -8,7 +8,6 @@ testRunner.configure({ ui: 'tdd', useColors: true, timeout: 25000, - retries: 3, - grep: 'Language.Tokenizer' + retries: 3 } as {}); module.exports = testRunner; From 51b544ca9df3515857f6d07411b7bf36bbb415d0 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Sun, 3 Dec 2017 17:08:35 -0800 Subject: [PATCH 07/24] Fix merge issue --- package.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/package.json b/package.json index 17145aa47ed6..e910867b6620 100644 --- a/package.json +++ b/package.json @@ -1529,7 +1529,7 @@ "fuzzy": "^0.1.3", "get-port": "^3.2.0", "iconv-lite": "^0.4.19", - "inversify": "^4.5.1", + "inversify": "^4.5.2", "line-by-line": "^0.1.5", "lodash": "^4.17.4", "minimatch": "^3.0.3", @@ -1583,10 +1583,5 @@ "typescript": "^2.5.2", "typescript-formatter": "^6.0.0", "vscode": "^1.1.5" - }, - "__metadata": { - "id": "f1f59ae4-9318-4f3c-a9b5-81b2eaa5f8a5", - "publisherDisplayName": "Microsoft", - "publisherId": "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8" } } \ No newline at end of file From 3cd11e649febd2cc750f9c8bd238a7f9e0a222d5 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Sun, 3 Dec 2017 17:18:09 -0800 Subject: [PATCH 08/24] Merge conflict --- .vscode/settings.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1884ba9a0255..de5f2d58fde1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,6 +17,5 @@ "python.linting.enabled": false, "python.formatting.formatOnSave": false, "python.unitTest.promptToConfigure": false, - "python.workspaceSymbols.enabled": false, - "python.formatting.provider": "yapf" + "python.workspaceSymbols.enabled": false } From 82e0ad16d200cfdb2ba4be1270305a882015ac9b Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Sun, 3 Dec 2017 17:25:50 -0800 Subject: [PATCH 09/24] Merge conflict --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index de5f2d58fde1..1884ba9a0255 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,5 +17,6 @@ "python.linting.enabled": false, "python.formatting.formatOnSave": false, "python.unitTest.promptToConfigure": false, - "python.workspaceSymbols.enabled": false + "python.workspaceSymbols.enabled": false, + "python.formatting.provider": "yapf" } From 9295c1ab90d1a328351b771d35e5c99103d32ecb Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 4 Dec 2017 11:23:18 -0800 Subject: [PATCH 10/24] Completion test --- package-lock.json | 5 +++ package.json | 3 +- src/test/autocomplete/base.test.ts | 53 +++++++++++++++++++---- src/test/pythonFiles/autocomp/suppress.py | 6 +++ 4 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 src/test/pythonFiles/autocomp/suppress.py diff --git a/package-lock.json b/package-lock.json index 10e608b1634d..50cf5f0c7820 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5457,6 +5457,11 @@ "integrity": "sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q=", "dev": true }, + "typescript-char": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/typescript-char/-/typescript-char-0.0.0.tgz", + "integrity": "sha1-VY/tpzfHZaYQtzfu+7F3Xum8jas=" + }, "typescript-formatter": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/typescript-formatter/-/typescript-formatter-6.1.0.tgz", diff --git a/package.json b/package.json index e910867b6620..23dc208e4ccf 100644 --- a/package.json +++ b/package.json @@ -1539,6 +1539,7 @@ "semver": "^5.4.1", "tmp": "0.0.29", "tree-kill": "^1.1.0", + "typescript-char": "^0.0.0", "uint64be": "^1.0.1", "untildify": "^3.0.2", "vscode-debugadapter": "^1.0.1", @@ -1584,4 +1585,4 @@ "typescript-formatter": "^6.0.0", "vscode": "^1.1.5" } -} \ No newline at end of file +} diff --git a/src/test/autocomplete/base.test.ts b/src/test/autocomplete/base.test.ts index a5398b7beb43..59a7e51c14f4 100644 --- a/src/test/autocomplete/base.test.ts +++ b/src/test/autocomplete/base.test.ts @@ -1,19 +1,18 @@ // Note: This example test is leveraging the Mocha test framework. // Please refer to their documentation on https://mochajs.org/ for help. - // The module 'assert' provides assertion methods from node import * as assert from 'assert'; import { EOL } from 'os'; +import * as path from 'path'; // You can import and use all API from the 'vscode' module // as well as import your extension to test it import * as vscode from 'vscode'; -import * as path from 'path'; import * as settings from '../../client/common/configSettings'; -import { initialize, closeActiveWindows, initializeTest } from '../initialize'; -import { execPythonFile } from '../../client/common/utils'; import { PythonSettings } from '../../client/common/configSettings'; +import { execPythonFile } from '../../client/common/utils'; import { rootWorkspaceUri } from '../common'; +import { closeActiveWindows, initialize, initializeTest } from '../initialize'; const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); const fileOne = path.join(autoCompPath, 'one.py'); @@ -23,7 +22,9 @@ const fileLambda = path.join(autoCompPath, 'lamb.py'); const fileDecorator = path.join(autoCompPath, 'deco.py'); const fileEncoding = path.join(autoCompPath, 'four.py'); const fileEncodingUsed = path.join(autoCompPath, 'five.py'); +const fileSuppress = path.join(autoCompPath, 'suppress.py'); +// tslint:disable-next-line:max-func-body-length suite('Autocomplete', () => { let isPython3: Promise; suiteSetup(async () => { @@ -31,9 +32,9 @@ suite('Autocomplete', () => { const version = await execPythonFile(rootWorkspaceUri, PythonSettings.getInstance(rootWorkspaceUri).pythonPath, ['--version'], __dirname, true); isPython3 = Promise.resolve(version.indexOf('3.') >= 0); }); - setup(() => initializeTest()); - suiteTeardown(() => closeActiveWindows()); - teardown(() => closeActiveWindows()); + setup(initializeTest); + suiteTeardown(closeActiveWindows); + teardown(closeActiveWindows); test('For "sys."', done => { let textEditor: vscode.TextEditor; @@ -115,7 +116,7 @@ suite('Autocomplete', () => { const position = new vscode.Position(10, 9); const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); assert.notEqual(list.items.filter(item => item.label === 'sleep').length, 0, 'sleep not found'); - assert.notEqual(list.items.filter(item => item.documentation.toString().startsWith("Delay execution for a given number of seconds. The argument may be")).length, 0, 'Documentation incorrect'); + assert.notEqual(list.items.filter(item => item.documentation.toString().startsWith('Delay execution for a given number of seconds. The argument may be')).length, 0, 'Documentation incorrect'); }); test('For custom class', done => { @@ -173,4 +174,40 @@ suite('Autocomplete', () => { assert.equal(list.items.filter(item => item.label === 'showMessage')[0].documentation, documentation, 'showMessage unicode documentation is incorrect'); }).then(done, done); }); + + // https://github.com/Microsoft/vscode-python/issues/110 + test('Suppress in strings/comments', done => { + let textEditor: vscode.TextEditor; + let textDocument: vscode.TextDocument; + const positions = [ + new vscode.Position(0, 1), // false + new vscode.Position(0, 9), // true + new vscode.Position(0, 12), // false + new vscode.Position(1, 1), // false + new vscode.Position(1, 3), // false + new vscode.Position(2, 7), // false + new vscode.Position(3, 0), // false + new vscode.Position(4, 2), // false + new vscode.Position(4, 8), // false + new vscode.Position(5, 4) // false + ]; + const expected = [ + false, true, false, false, false, false, false, false, false, false + ]; + vscode.workspace.openTextDocument(fileSuppress).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + textEditor = editor; + for (let i = 0; i < positions.length; i += 1) { + vscode.commands.executeCommand('vscode.executeCompletionItemProvider', + textDocument.uri, positions[i]).then(list => { + const result = list.items.filter(item => item.label === 'abs').length; + assert.equal(result > 0, expected[i], + `Expected ${expected[i]} at position ${positions[i].line}:${positions[i].character} but got ${result}`); + }); + } + }).then(done, done); + }); }); diff --git a/src/test/pythonFiles/autocomp/suppress.py b/src/test/pythonFiles/autocomp/suppress.py new file mode 100644 index 000000000000..9f74959ef14b --- /dev/null +++ b/src/test/pythonFiles/autocomp/suppress.py @@ -0,0 +1,6 @@ +"string" #comment +""" +content +""" +#comment +'un#closed From 06eb1a56049bdbcb9db71c550d9cbd869a4fce63 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 4 Dec 2017 11:26:07 -0800 Subject: [PATCH 11/24] Fix last line --- .vscode/launch.json | 2 +- .vscode/tasks.json | 2 +- gulpfile.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index a69c3396ff4e..51590d047be8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -77,4 +77,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ccf99a2c6f20..8a17b7da905f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -72,4 +72,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/gulpfile.js b/gulpfile.js index 6c3f7819d003..ecf4dd1d5bca 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -454,4 +454,4 @@ class Deferred { get completed() { return this._rejected || this._resolved; } -} \ No newline at end of file +} From e9db8e0de99936daef098000181c44db517c0d8c Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 4 Dec 2017 12:47:05 -0800 Subject: [PATCH 12/24] Fix javascript math --- src/client/language/textRangeCollection.ts | 4 ++-- src/test/index.ts | 3 ++- src/test/language/textRangeCollection.test.ts | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/client/language/textRangeCollection.ts b/src/client/language/textRangeCollection.ts index 0464dc945382..d09becea902f 100644 --- a/src/client/language/textRangeCollection.ts +++ b/src/client/language/textRangeCollection.ts @@ -51,7 +51,7 @@ export class TextRangeCollection implements ITextRangeColl let max = this.count - 1; while (min <= max) { - const mid = min + (max - min) / 2; + const mid = Math.floor(min + (max - min) / 2); const item = this.items[mid]; if (item.start === position) { @@ -82,7 +82,7 @@ export class TextRangeCollection implements ITextRangeColl let max = this.count - 1; while (min <= max) { - const mid = min + (max - min) / 2; + const mid = Math.floor(min + (max - min) / 2); const item = this.items[mid]; if (item.contains(position)) { diff --git a/src/test/index.ts b/src/test/index.ts index 0202b4e2dc43..9eb083463586 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -8,6 +8,7 @@ testRunner.configure({ ui: 'tdd', useColors: true, timeout: 25000, - retries: 3 + retries: 3, + grep: "Language.TextRangeCollection" } as {}); module.exports = testRunner; diff --git a/src/test/language/textRangeCollection.test.ts b/src/test/language/textRangeCollection.test.ts index 5b5dfad8c8b6..44666dd811ce 100644 --- a/src/test/language/textRangeCollection.test.ts +++ b/src/test/language/textRangeCollection.test.ts @@ -44,7 +44,7 @@ suite('Language.TextRangeCollection', () => { items.push(new TextRange(2, 1)); items.push(new TextRange(4, 2)); const c = new TextRangeCollection(items); - const results = [-1, -1, 0, -1, 1, 1, -1]; + const results = [-1, -1, 0, -1, 1, -1, -1]; for (let i = 0; i < results.length; i += 1) { const index = c.getItemAtPosition(i); assert.equal(index, results[i]); From d8ab041f3f8fb459a37bd3adc10de82fadfd3c7b Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 5 Dec 2017 10:07:55 -0800 Subject: [PATCH 13/24] Make test await for results --- src/test/autocomplete/base.test.ts | 27 +++++++++------------------ src/test/index.ts | 3 +-- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/test/autocomplete/base.test.ts b/src/test/autocomplete/base.test.ts index 59a7e51c14f4..e4b8b854570d 100644 --- a/src/test/autocomplete/base.test.ts +++ b/src/test/autocomplete/base.test.ts @@ -176,9 +176,7 @@ suite('Autocomplete', () => { }); // https://github.com/Microsoft/vscode-python/issues/110 - test('Suppress in strings/comments', done => { - let textEditor: vscode.TextEditor; - let textDocument: vscode.TextDocument; + test('Suppress in strings/comments', async () => { const positions = [ new vscode.Position(0, 1), // false new vscode.Position(0, 9), // true @@ -194,20 +192,13 @@ suite('Autocomplete', () => { const expected = [ false, true, false, false, false, false, false, false, false, false ]; - vscode.workspace.openTextDocument(fileSuppress).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - textEditor = editor; - for (let i = 0; i < positions.length; i += 1) { - vscode.commands.executeCommand('vscode.executeCompletionItemProvider', - textDocument.uri, positions[i]).then(list => { - const result = list.items.filter(item => item.label === 'abs').length; - assert.equal(result > 0, expected[i], - `Expected ${expected[i]} at position ${positions[i].line}:${positions[i].character} but got ${result}`); - }); - } - }).then(done, done); + const textDocument = await vscode.workspace.openTextDocument(fileSuppress); + await vscode.window.showTextDocument(textDocument); + for (let i = 0; i < positions.length; i += 1) { + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, positions[i]); + const result = list.items.filter(item => item.label === 'abs').length; + assert.equal(result > 0, expected[i], + `Expected ${expected[i]} at position ${positions[i].line}:${positions[i].character} but got ${result}`); + } }); }); diff --git a/src/test/index.ts b/src/test/index.ts index 9eb083463586..0202b4e2dc43 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -8,7 +8,6 @@ testRunner.configure({ ui: 'tdd', useColors: true, timeout: 25000, - retries: 3, - grep: "Language.TextRangeCollection" + retries: 3 } as {}); module.exports = testRunner; From db75cd00e892d8721c92df14b7b77ca92d5c2a0a Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 5 Dec 2017 10:22:37 -0800 Subject: [PATCH 14/24] Add license headers --- src/client/language/characterStream.ts | 2 ++ src/test/autocomplete/base.test.ts | 3 +++ src/test/language/characterStream.test.ts | 4 ++++ src/test/language/textIterator.test.ts | 4 ++++ src/test/language/textRange.test.ts | 4 ++++ src/test/language/textRangeCollection.test.ts | 4 ++++ src/test/language/tokenizer.test.ts | 4 ++++ 7 files changed, 25 insertions(+) diff --git a/src/client/language/characterStream.ts b/src/client/language/characterStream.ts index e90ef7f2e6b7..a4da08659a9d 100644 --- a/src/client/language/characterStream.ts +++ b/src/client/language/characterStream.ts @@ -1,3 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. 'use strict'; // tslint:disable-next-line:import-name diff --git a/src/test/autocomplete/base.test.ts b/src/test/autocomplete/base.test.ts index e4b8b854570d..1873f86f0776 100644 --- a/src/test/autocomplete/base.test.ts +++ b/src/test/autocomplete/base.test.ts @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + // Note: This example test is leveraging the Mocha test framework. // Please refer to their documentation on https://mochajs.org/ for help. diff --git a/src/test/language/characterStream.test.ts b/src/test/language/characterStream.test.ts index d1c003d6b2d9..165fdde51ae9 100644 --- a/src/test/language/characterStream.test.ts +++ b/src/test/language/characterStream.test.ts @@ -1,3 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; + import * as assert from 'assert'; // tslint:disable-next-line:import-name import Char from 'typescript-char'; diff --git a/src/test/language/textIterator.test.ts b/src/test/language/textIterator.test.ts index dddfe3478ec5..34daa81534cd 100644 --- a/src/test/language/textIterator.test.ts +++ b/src/test/language/textIterator.test.ts @@ -1,3 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; + import * as assert from 'assert'; import { TextIterator } from '../../client/language/textIterator'; diff --git a/src/test/language/textRange.test.ts b/src/test/language/textRange.test.ts index 4e0da8feb06c..fecf287032a0 100644 --- a/src/test/language/textRange.test.ts +++ b/src/test/language/textRange.test.ts @@ -1,3 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; + import * as assert from 'assert'; import { TextRange } from '../../client/language/definitions'; diff --git a/src/test/language/textRangeCollection.test.ts b/src/test/language/textRangeCollection.test.ts index 44666dd811ce..5c56c3f139c7 100644 --- a/src/test/language/textRangeCollection.test.ts +++ b/src/test/language/textRangeCollection.test.ts @@ -1,3 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; + import * as assert from 'assert'; import { TextRange } from '../../client/language/definitions'; import { TextRangeCollection } from '../../client/language/textRangeCollection'; diff --git a/src/test/language/tokenizer.test.ts b/src/test/language/tokenizer.test.ts index 1ce0165e4241..139441aeea81 100644 --- a/src/test/language/tokenizer.test.ts +++ b/src/test/language/tokenizer.test.ts @@ -1,3 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; + import * as assert from 'assert'; import { TextRange, TokenType } from '../../client/language/definitions'; import { TextRangeCollection } from '../../client/language/textRangeCollection'; From 9ab2c47a609629570fc387e3b3697cd60e8397e4 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 5 Dec 2017 10:35:58 -0800 Subject: [PATCH 15/24] Rename definitions to types --- src/client/language/characterStream.ts | 2 +- src/client/language/textIterator.ts | 2 +- src/client/language/textRangeCollection.ts | 2 +- src/client/language/tokenizer.ts | 2 +- src/client/language/{definitions.ts => types.ts} | 0 src/client/providers/completionProvider.ts | 2 +- src/test/language/characterStream.test.ts | 2 +- src/test/language/textRange.test.ts | 2 +- src/test/language/textRangeCollection.test.ts | 2 +- src/test/language/tokenizer.test.ts | 2 +- 10 files changed, 9 insertions(+), 9 deletions(-) rename src/client/language/{definitions.ts => types.ts} (100%) diff --git a/src/client/language/characterStream.ts b/src/client/language/characterStream.ts index a4da08659a9d..a95af7ede457 100644 --- a/src/client/language/characterStream.ts +++ b/src/client/language/characterStream.ts @@ -4,8 +4,8 @@ // tslint:disable-next-line:import-name import Char from 'typescript-char'; -import { ICharacterStream, ITextIterator } from './definitions'; import { TextIterator } from './textIterator'; +import { ICharacterStream, ITextIterator } from './types'; export class CharacterStream implements ICharacterStream { private text: ITextIterator; diff --git a/src/client/language/textIterator.ts b/src/client/language/textIterator.ts index 927078da3939..3984dfbe3458 100644 --- a/src/client/language/textIterator.ts +++ b/src/client/language/textIterator.ts @@ -1,7 +1,7 @@ 'use strict'; import { Position, Range, TextDocument } from 'vscode'; -import { ITextIterator } from './definitions'; +import { ITextIterator } from './types'; export class TextIterator implements ITextIterator { private text: string; diff --git a/src/client/language/textRangeCollection.ts b/src/client/language/textRangeCollection.ts index d09becea902f..47983f8fe0cb 100644 --- a/src/client/language/textRangeCollection.ts +++ b/src/client/language/textRangeCollection.ts @@ -1,6 +1,6 @@ 'use strict'; -import { ITextRange, ITextRangeCollection } from './definitions'; +import { ITextRange, ITextRangeCollection } from './types'; export class TextRangeCollection implements ITextRangeCollection { private items: T[]; diff --git a/src/client/language/tokenizer.ts b/src/client/language/tokenizer.ts index 6d63f4168414..8b122da7c346 100644 --- a/src/client/language/tokenizer.ts +++ b/src/client/language/tokenizer.ts @@ -3,8 +3,8 @@ // tslint:disable-next-line:import-name import Char from 'typescript-char'; import { CharacterStream } from './characterStream'; -import { ICharacterStream, ITextRangeCollection, IToken, ITokenizer, TextRange, TokenType } from './definitions'; import { TextRangeCollection } from './textRangeCollection'; +import { ICharacterStream, ITextRangeCollection, IToken, ITokenizer, TextRange, TokenType } from './types'; enum QuoteType { None, diff --git a/src/client/language/definitions.ts b/src/client/language/types.ts similarity index 100% rename from src/client/language/definitions.ts rename to src/client/language/types.ts diff --git a/src/client/providers/completionProvider.ts b/src/client/providers/completionProvider.ts index 10c930348e33..b6d62c340dd7 100644 --- a/src/client/providers/completionProvider.ts +++ b/src/client/providers/completionProvider.ts @@ -3,8 +3,8 @@ import * as vscode from 'vscode'; import { Position, ProviderResult, SnippetString, Uri } from 'vscode'; import { PythonSettings } from '../common/configSettings'; -import { TokenType } from '../language/definitions'; import { Tokenizer } from '../language/tokenizer'; +import { TokenType } from '../language/types'; import { JediFactory } from '../languageServices/jediProxyFactory'; import { captureTelemetry } from '../telemetry'; import { COMPLETION } from '../telemetry/constants'; diff --git a/src/test/language/characterStream.test.ts b/src/test/language/characterStream.test.ts index 165fdde51ae9..63ea71f01746 100644 --- a/src/test/language/characterStream.test.ts +++ b/src/test/language/characterStream.test.ts @@ -6,8 +6,8 @@ import * as assert from 'assert'; // tslint:disable-next-line:import-name import Char from 'typescript-char'; import { CharacterStream } from '../../client/language/characterStream'; -import { ICharacterStream, TextRange } from '../../client/language/definitions'; import { TextIterator } from '../../client/language/textIterator'; +import { ICharacterStream, TextRange } from '../../client/language/types'; // tslint:disable-next-line:max-func-body-length suite('Language.CharacterStream', () => { diff --git a/src/test/language/textRange.test.ts b/src/test/language/textRange.test.ts index fecf287032a0..02cad753c16f 100644 --- a/src/test/language/textRange.test.ts +++ b/src/test/language/textRange.test.ts @@ -3,7 +3,7 @@ 'use strict'; import * as assert from 'assert'; -import { TextRange } from '../../client/language/definitions'; +import { TextRange } from '../../client/language/types'; // tslint:disable-next-line:max-func-body-length suite('Language.TextRange', () => { diff --git a/src/test/language/textRangeCollection.test.ts b/src/test/language/textRangeCollection.test.ts index 5c56c3f139c7..32522e63c778 100644 --- a/src/test/language/textRangeCollection.test.ts +++ b/src/test/language/textRangeCollection.test.ts @@ -3,8 +3,8 @@ 'use strict'; import * as assert from 'assert'; -import { TextRange } from '../../client/language/definitions'; import { TextRangeCollection } from '../../client/language/textRangeCollection'; +import { TextRange } from '../../client/language/types'; // tslint:disable-next-line:max-func-body-length suite('Language.TextRangeCollection', () => { diff --git a/src/test/language/tokenizer.test.ts b/src/test/language/tokenizer.test.ts index 139441aeea81..7642b88acfaa 100644 --- a/src/test/language/tokenizer.test.ts +++ b/src/test/language/tokenizer.test.ts @@ -3,9 +3,9 @@ 'use strict'; import * as assert from 'assert'; -import { TextRange, TokenType } from '../../client/language/definitions'; import { TextRangeCollection } from '../../client/language/textRangeCollection'; import { Tokenizer } from '../../client/language/tokenizer'; +import { TextRange, TokenType } from '../../client/language/types'; // tslint:disable-next-line:max-func-body-length suite('Language.Tokenizer', () => { From d9c95d07b4904a901aafe3cda3b3ee8a3c30027e Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 5 Dec 2017 12:01:00 -0800 Subject: [PATCH 16/24] Round I --- src/client/providers/completionProvider.ts | 20 +++++++++++-- src/client/providers/jediHelpers.ts | 33 +++++++++++++--------- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/client/providers/completionProvider.ts b/src/client/providers/completionProvider.ts index b6d62c340dd7..7328bdaa22af 100644 --- a/src/client/providers/completionProvider.ts +++ b/src/client/providers/completionProvider.ts @@ -1,3 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. 'use strict'; import * as vscode from 'vscode'; @@ -17,11 +19,8 @@ export class PythonCompletionItemProvider implements vscode.CompletionItemProvid private static parseData(data: proxy.ICompletionResult, resource: Uri): vscode.CompletionItem[] { if (data && data.items.length > 0) { return data.items.map(item => { - const sigAndDocs = extractSignatureAndDocumentation(item); const completionItem = new vscode.CompletionItem(item.text); completionItem.kind = item.type; - completionItem.documentation = sigAndDocs[1].length === 0 ? item.description : sigAndDocs[1]; - completionItem.detail = sigAndDocs[0].split(/\r?\n/).join(''); if (PythonSettings.getInstance(resource).autoComplete.addBrackets === true && (item.kind === vscode.SymbolKind.Function || item.kind === vscode.SymbolKind.Method)) { completionItem.insertText = new SnippetString(item.text).appendText('(').appendTabstop().appendText(')'); @@ -34,6 +33,15 @@ export class PythonCompletionItemProvider implements vscode.CompletionItemProvid } return []; } + + private static parseDocumentation(item: proxy.IAutoCompleteItem, resource: Uri): vscode.CompletionItem { + const sigAndDocs = extractSignatureAndDocumentation(item); + const completionItem = new vscode.CompletionItem(item.text); + completionItem.documentation = sigAndDocs[1].length === 0 ? item.description : sigAndDocs[1]; + completionItem.detail = sigAndDocs[0].split(/\r?\n/).join(''); + return completionItem; + } + @captureTelemetry(COMPLETION) public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): ProviderResult { if (position.character <= 0) { @@ -65,6 +73,12 @@ export class PythonCompletionItemProvider implements vscode.CompletionItemProvid }); } + public resolveCompletionItem(item: vscode.CompletionItem): vscode.CompletionItem { + const docs = extractDocumentation(item); + item.documentation = docs[1].length === 0 ? item.description : sigAndDocs[1]; + return item; + } + private isPositionInsideStringOrComment(document: vscode.TextDocument, position: vscode.Position): boolean { const tokenizeTo = position.translate(1, 0); const text = document.getText(new vscode.Range(new Position(0, 0), tokenizeTo)); diff --git a/src/client/providers/jediHelpers.ts b/src/client/providers/jediHelpers.ts index 9ff87f966b84..6054cab301a9 100644 --- a/src/client/providers/jediHelpers.ts +++ b/src/client/providers/jediHelpers.ts @@ -1,6 +1,10 @@ -import * as proxy from './jediProxy'; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; + import { EOL } from 'os'; import * as vscode from 'vscode'; +import * as proxy from './jediProxy'; export function extractSignatureAndDocumentation(definition: proxy.IAutoCompleteItem, highlightCode: boolean = false): [string, string] { // Somtimes the signature of the function, class (whatever) is broken into multiple lines @@ -20,18 +24,19 @@ export function extractSignatureAndDocumentation(definition: proxy.IAutoComplete let lines = txt.split(/\r?\n/); const startIndexOfDocString = firstLineOfRawDocString === '' ? -1 : lines.findIndex(line => line.indexOf(firstLineOfRawDocString) === 0); - let signatureLines = startIndexOfDocString === -1 ? [lines.shift()] : lines.splice(0, startIndexOfDocString); + const signatureLines = startIndexOfDocString === -1 ? [lines.shift()] : lines.splice(0, startIndexOfDocString); let signature = signatureLines.filter(line => line.trim().length > 0).join(EOL); + // tslint:disable-next-line:switch-default switch (definition.type) { case vscode.CompletionItemKind.Constructor: case vscode.CompletionItemKind.Function: case vscode.CompletionItemKind.Method: { - signature = 'def ' + signature; + signature = `def ${signature}`; break; } case vscode.CompletionItemKind.Class: { - signature = 'class ' + signature; + signature = `class ${signature}`; break; } } @@ -50,37 +55,39 @@ export function extractSignatureAndDocumentation(definition: proxy.IAutoComplete export function highlightCode(docstring: string): string { /********** - * + * * Magic. Do not touch. [What is the best comment in source code](https://stackoverflow.com/a/185106) - * + * * This method uses several regexs to 'translate' reStructruedText syntax (Python doc syntax) to Markdown syntax. - * + * * Let's just keep it unchanged unless a better solution becomes possible. - * + * **********/ // Add 2 line break before and after docstring (used to match a blank line) docstring = EOL + EOL + docstring.trim() + EOL + EOL; // Section title -> heading level 2 - docstring = docstring.replace(/(.+\r?\n)[-=]+\r?\n/g, '## $1' + EOL); + docstring = docstring.replace(/(.+\r?\n)[-=]+\r?\n/g, `## $1${EOL}`); // Directives: '.. directive::' -> '**directive**' docstring = docstring.replace(/\.\. (.*)::/g, '**$1**'); // Pattern of 'var : description' - let paramLinePattern = '[\\*\\w_]+ ?:[^:\r\n]+'; + const paramLinePattern = '[\\*\\w_]+ ?:[^:\r\n]+'; // Add new line after and before param line docstring = docstring.replace(new RegExp(`(${EOL + paramLinePattern})`, 'g'), `$1${EOL}`); docstring = docstring.replace(new RegExp(`(${EOL + paramLinePattern + EOL})`, 'g'), `${EOL}$1`); // 'var : description' -> '`var` description' docstring = docstring.replace(/\r?\n([\*\w]+) ?: ?([^:\r\n]+\r?\n)/g, `${EOL}\`$1\` $2`); // Doctest blocks: begin with `>>>` and end with blank line + // tslint:disable-next-line:prefer-template docstring = docstring.replace(/(>>>[\w\W]+?\r?\n)\r?\n/g, `${'```python' + EOL}$1${'```' + EOL + EOL}`); // Literal blocks: begin with `::` (literal blocks are indented or quoted; for simplicity, we end literal blocks with blank line) + // tslint:disable-next-line:prefer-template docstring = docstring.replace(/(\r?\n[^\.]*)::\r?\n\r?\n([\w\W]+?\r?\n)\r?\n/g, `$1${EOL + '```' + EOL}$2${'```' + EOL + EOL}`); // Remove indentation in Field lists and Literal blocks let inCodeBlock = false; let codeIndentation = 0; - let lines = docstring.split(/\r?\n/); - for (let i = 0; i < lines.length; i++) { - let line = lines[i]; + const lines = docstring.split(/\r?\n/); + for (let i = 0; i < lines.length; i += 1) { + const line = lines[i]; if (line.startsWith('```')) { inCodeBlock = !inCodeBlock; if (inCodeBlock) { From d587485696c15d2a5dec35fdc9f317dfce3b9a39 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Tue, 5 Dec 2017 12:02:27 -0800 Subject: [PATCH 17/24] License headers --- src/client/language/textIterator.ts | 2 ++ src/client/language/textRangeCollection.ts | 2 ++ src/client/language/tokenizer.ts | 2 ++ src/client/language/types.ts | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/client/language/textIterator.ts b/src/client/language/textIterator.ts index 3984dfbe3458..d5eda4783e2c 100644 --- a/src/client/language/textIterator.ts +++ b/src/client/language/textIterator.ts @@ -1,3 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. 'use strict'; import { Position, Range, TextDocument } from 'vscode'; diff --git a/src/client/language/textRangeCollection.ts b/src/client/language/textRangeCollection.ts index 47983f8fe0cb..8ce5a744c9a6 100644 --- a/src/client/language/textRangeCollection.ts +++ b/src/client/language/textRangeCollection.ts @@ -1,3 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. 'use strict'; import { ITextRange, ITextRangeCollection } from './types'; diff --git a/src/client/language/tokenizer.ts b/src/client/language/tokenizer.ts index 8b122da7c346..60d9fadc7e2e 100644 --- a/src/client/language/tokenizer.ts +++ b/src/client/language/tokenizer.ts @@ -1,3 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. 'use strict'; // tslint:disable-next-line:import-name diff --git a/src/client/language/types.ts b/src/client/language/types.ts index d001adccbd88..121ee682c085 100644 --- a/src/client/language/types.ts +++ b/src/client/language/types.ts @@ -1,3 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. 'use strict'; export interface ITextRange { From 94a5a7e2ea218288bc53582e6c0cfbca2d0e227b Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 6 Dec 2017 15:27:10 -0800 Subject: [PATCH 18/24] Separate completion and doc fetching --- pythonFiles/completion.py | 46 +- src/client/providers/completionProvider.ts | 79 +-- src/client/providers/completionSource.ts | 125 +++++ src/client/providers/hoverProvider.ts | 106 +--- src/client/providers/hoverSource.ts | 143 +++++ src/client/providers/jediProxy.ts | 606 +++++++++++---------- 6 files changed, 640 insertions(+), 465 deletions(-) create mode 100644 src/client/providers/completionSource.ts create mode 100644 src/client/providers/hoverSource.ts diff --git a/pythonFiles/completion.py b/pythonFiles/completion.py index 8c7cd09410dc..15c6540cbfb2 100644 --- a/pythonFiles/completion.py +++ b/pythonFiles/completion.py @@ -208,16 +208,16 @@ def _serialize_completions(self, script, identifier=None, prefix=''): _completion['snippet'] = '%s=$1$0' % name _completion['text'] = name _completion['displayText'] = name - if self.show_doc_strings: - try: - _completion['description'] = signature.docstring() - _completion['raw_docstring'] = signature.docstring(raw=True) - except Exception: - _completion['description'] = '' - _completion['raw_docstring'] = '' - else: - _completion['description'] = self._generate_signature( - signature) + # if self.show_doc_strings: + # try: + # _completion['description'] = signature.docstring() + # _completion['raw_docstring'] = signature.docstring(raw=True) + # except Exception: + # _completion['description'] = '' + # _completion['raw_docstring'] = '' + # else: + # _completion['description'] = self._generate_signature( + # signature) _completions.append(_completion) try: @@ -227,22 +227,22 @@ def _serialize_completions(self, script, identifier=None, prefix=''): except : completions = [] for completion in completions: - if self.show_doc_strings: - try: - description = completion.docstring() - except Exception: - description = '' - else: - description = self._generate_signature(completion) + # if self.show_doc_strings: + # try: + # description = completion.docstring() + # except Exception: + # description = '' + # else: + # description = self._generate_signature(completion) try: - rawDocstring = completion.docstring(raw=True) + # rawDocstring = completion.docstring(raw=True) _completion = { 'text': completion.name, 'type': self._get_definition_type(completion), 'raw_type': completion.type, - 'description': description, - 'raw_docstring': rawDocstring, + # 'description': description, + # 'raw_docstring': rawDocstring, 'rightLabel': self._additional_info(completion) } except Exception: @@ -252,9 +252,9 @@ def _serialize_completions(self, script, identifier=None, prefix=''): if c['text'] == _completion['text']: c['type'] = _completion['type'] c['raw_type'] = _completion['raw_type'] - if len(c['description']) == 0 and len(c['raw_docstring']) == 0: - c['description'] = _completion['description'] - c['raw_docstring'] = _completion['description'] + # if len(c['description']) == 0 and len(c['raw_docstring']) == 0: + # c['description'] = _completion['description'] + # c['raw_docstring'] = _completion['description'] if any([c['text'].split('=')[0] == _completion['text'] diff --git a/src/client/providers/completionProvider.ts b/src/client/providers/completionProvider.ts index 7328bdaa22af..105e78b73c53 100644 --- a/src/client/providers/completionProvider.ts +++ b/src/client/providers/completionProvider.ts @@ -3,88 +3,25 @@ 'use strict'; import * as vscode from 'vscode'; -import { Position, ProviderResult, SnippetString, Uri } from 'vscode'; -import { PythonSettings } from '../common/configSettings'; -import { Tokenizer } from '../language/tokenizer'; -import { TokenType } from '../language/types'; import { JediFactory } from '../languageServices/jediProxyFactory'; import { captureTelemetry } from '../telemetry'; import { COMPLETION } from '../telemetry/constants'; -import { extractSignatureAndDocumentation } from './jediHelpers'; -import * as proxy from './jediProxy'; +import { CompletionSource } from './completionSource'; export class PythonCompletionItemProvider implements vscode.CompletionItemProvider { + private completionSource: CompletionSource; - public constructor(private jediFactory: JediFactory) { } - private static parseData(data: proxy.ICompletionResult, resource: Uri): vscode.CompletionItem[] { - if (data && data.items.length > 0) { - return data.items.map(item => { - const completionItem = new vscode.CompletionItem(item.text); - completionItem.kind = item.type; - if (PythonSettings.getInstance(resource).autoComplete.addBrackets === true && - (item.kind === vscode.SymbolKind.Function || item.kind === vscode.SymbolKind.Method)) { - completionItem.insertText = new SnippetString(item.text).appendText('(').appendTabstop().appendText(')'); - } - - // ensure the built in memebers are at the bottom - completionItem.sortText = (completionItem.label.startsWith('__') ? 'z' : (completionItem.label.startsWith('_') ? 'y' : '__')) + completionItem.label; - return completionItem; - }); - } - return []; - } - - private static parseDocumentation(item: proxy.IAutoCompleteItem, resource: Uri): vscode.CompletionItem { - const sigAndDocs = extractSignatureAndDocumentation(item); - const completionItem = new vscode.CompletionItem(item.text); - completionItem.documentation = sigAndDocs[1].length === 0 ? item.description : sigAndDocs[1]; - completionItem.detail = sigAndDocs[0].split(/\r?\n/).join(''); - return completionItem; + constructor(jediFactory: JediFactory) { + this.completionSource = new CompletionSource(jediFactory); } @captureTelemetry(COMPLETION) - public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): ProviderResult { - if (position.character <= 0) { - return Promise.resolve([]); - } - const filename = document.fileName; - const lineText = document.lineAt(position.line).text; - if (lineText.match(/^\s*\/\//)) { - return Promise.resolve([]); - } - // Suppress completion inside string and comments - if (this.isPositionInsideStringOrComment(document, position)) { - return Promise.resolve([]); - } - const type = proxy.CommandType.Completions; - const columnIndex = position.character; - - const source = document.getText(); - const cmd: proxy.ICommand = { - command: type, - fileName: filename, - columnIndex: columnIndex, - lineIndex: position.line, - source: source - }; - - return this.jediFactory.getJediProxyHandler(document.uri).sendCommand(cmd, token).then(data => { - return PythonCompletionItemProvider.parseData(data, document.uri); - }); + public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.ProviderResult { + return this.completionSource.getVsCodeCompletionItems(document, position, token); } - public resolveCompletionItem(item: vscode.CompletionItem): vscode.CompletionItem { - const docs = extractDocumentation(item); - item.documentation = docs[1].length === 0 ? item.description : sigAndDocs[1]; + public async resolveCompletionItem(item: vscode.CompletionItem): Promise { + item.documentation = await this.completionSource.getDocumentation(item); return item; } - - private isPositionInsideStringOrComment(document: vscode.TextDocument, position: vscode.Position): boolean { - const tokenizeTo = position.translate(1, 0); - const text = document.getText(new vscode.Range(new Position(0, 0), tokenizeTo)); - const t = new Tokenizer(); - const tokens = t.Tokenize(text); - const index = tokens.getItemContaining(document.offsetAt(position)); - return index >= 0 && (tokens[index].TokenType === TokenType.String || tokens[index].TokenType === TokenType.Comment); - } } diff --git a/src/client/providers/completionSource.ts b/src/client/providers/completionSource.ts new file mode 100644 index 000000000000..e5abb23fbe70 --- /dev/null +++ b/src/client/providers/completionSource.ts @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; + +import * as vscode from 'vscode'; +import { PythonSettings } from '../common/configSettings'; +import { Tokenizer } from '../language/tokenizer'; +import { TokenType } from '../language/types'; +import { JediFactory } from '../languageServices/jediProxyFactory'; +import { HoverSource } from './hoverSource'; +import * as proxy from './jediProxy'; + +class DocumentPosition { + constructor(public document: vscode.TextDocument, public position: vscode.Position) { } + + public static fromObject(item: object): DocumentPosition { + // tslint:disable-next-line:no-any + return (item as any).documentPosition as DocumentPosition; + } + + public attachTo(item: object): void { + // tslint:disable-next-line:no-any + (item as any).documentPosition = this; + } +} + +export class CompletionSource { + private jediFactory: JediFactory; + private hoverSource: HoverSource; + + constructor(jediFactory: JediFactory) { + this.jediFactory = jediFactory; + this.hoverSource = new HoverSource(jediFactory); + } + + public async getVsCodeCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) + : Promise { + const result = await this.getCompletionResult(document, position, token); + if (result === undefined) { + return Promise.resolve([]); + } + return this.toVsCodeCompletions(new DocumentPosition(document, position), result, document.uri); + } + + public async getDocumentation(completionItem: vscode.CompletionItem): Promise { + const documentPosition = DocumentPosition.fromObject(completionItem); + if (documentPosition === undefined) { + Promise.resolve(''); + } + + // Supply hover source with simulated document text where item in question was 'already typed'. + const document = documentPosition.document; + const position = documentPosition.position; + const itemText = completionItem.insertText ? completionItem.insertText : completionItem.label; + const wordRange = document.getWordRangeAtPosition(position); + let sourceText: string | null = null; + + if (wordRange !== undefined) { + const leadingRange = new vscode.Range(new vscode.Position(0, 0), wordRange.start); + sourceText = `${document.getText(leadingRange)}${itemText}`; + } + + const cts = new vscode.CancellationTokenSource(); + const hoverStrings = await this.hoverSource.getDocStrings(document, position, sourceText, cts.token); + if (!hoverStrings || hoverStrings.length === 0) { + return ''; + } + return hoverStrings.join('\n'); + } + + private async getCompletionResult(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) + : Promise { + if (position.character <= 0) { + return undefined; + } + const filename = document.fileName; + const lineText = document.lineAt(position.line).text; + if (lineText.match(/^\s*\/\//)) { + return undefined; + } + // Suppress completion inside string and comments + if (this.isPositionInsideStringOrComment(document, position)) { + return undefined; + } + const type = proxy.CommandType.Completions; + const columnIndex = position.character; + + const source = document.getText(); + const cmd: proxy.ICommand = { + command: type, + fileName: filename, + columnIndex: columnIndex, + lineIndex: position.line, + source: source + }; + + return await this.jediFactory.getJediProxyHandler(document.uri).sendCommand(cmd, token); + } + + private toVsCodeCompletions(documentPosition: DocumentPosition, data: proxy.ICompletionResult, resource: vscode.Uri): vscode.CompletionItem[] { + return data && data.items.length > 0 ? data.items.map(item => this.toVsCodeCompletion(documentPosition, item, resource)) : []; + } + + private toVsCodeCompletion(documentPosition: DocumentPosition, item: proxy.IAutoCompleteItem, resource: vscode.Uri): vscode.CompletionItem { + const completionItem = new vscode.CompletionItem(item.text); + completionItem.kind = item.type; + if (PythonSettings.getInstance(resource).autoComplete.addBrackets === true && + (item.kind === vscode.SymbolKind.Function || item.kind === vscode.SymbolKind.Method)) { + completionItem.insertText = new vscode.SnippetString(item.text).appendText('(').appendTabstop().appendText(')'); + } + // ensure the built in members are at the bottom + completionItem.sortText = (completionItem.label.startsWith('__') ? 'z' : (completionItem.label.startsWith('_') ? 'y' : '__')) + completionItem.label; + documentPosition.attachTo(completionItem); + return completionItem; + } + + private isPositionInsideStringOrComment(document: vscode.TextDocument, position: vscode.Position): boolean { + const tokenizeTo = position.translate(1, 0); + const text = document.getText(new vscode.Range(new vscode.Position(0, 0), tokenizeTo)); + const t = new Tokenizer(); + const tokens = t.Tokenize(text); + const index = tokens.getItemContaining(document.offsetAt(position)); + return index >= 0 && (tokens[index].TokenType === TokenType.String || tokens[index].TokenType === TokenType.Comment); + } +} diff --git a/src/client/providers/hoverProvider.ts b/src/client/providers/hoverProvider.ts index 8c50b336fcfa..0ea3e28c812d 100644 --- a/src/client/providers/hoverProvider.ts +++ b/src/client/providers/hoverProvider.ts @@ -1,107 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. 'use strict'; -import { EOL } from 'os'; import * as vscode from 'vscode'; import { JediFactory } from '../languageServices/jediProxyFactory'; import { captureTelemetry } from '../telemetry'; import { HOVER_DEFINITION } from '../telemetry/constants'; -import { highlightCode } from './jediHelpers'; -import * as proxy from './jediProxy'; +import { HoverSource } from './hoverSource'; export class PythonHoverProvider implements vscode.HoverProvider { - public constructor(private jediFactory: JediFactory) { } - private static parseData(data: proxy.IHoverResult, currentWord: string): vscode.Hover { - const results = []; - const capturedInfo: string[] = []; - data.items.forEach(item => { - let { signature } = item; - switch (item.kind) { - case vscode.SymbolKind.Constructor: - case vscode.SymbolKind.Function: - case vscode.SymbolKind.Method: { - signature = `def ${signature}`; - break; - } - case vscode.SymbolKind.Class: { - signature = `class ${signature}`; - break; - } - default: { - signature = typeof item.text === 'string' && item.text.length > 0 ? item.text : currentWord; - } - } - if (item.docstring) { - let lines = item.docstring.split(/\r?\n/); - // If the docstring starts with the signature, then remove those lines from the docstring. - if (lines.length > 0 && item.signature.indexOf(lines[0]) === 0) { - lines.shift(); - const endIndex = lines.findIndex(line => item.signature.endsWith(line)); - if (endIndex >= 0) { - lines = lines.filter((line, index) => index > endIndex); - } - } - if (lines.length > 0 && item.signature.startsWith(currentWord) && lines[0].startsWith(currentWord) && lines[0].endsWith(')')) { - lines.shift(); - } - const descriptionWithHighlightedCode = highlightCode(lines.join(EOL)); - const hoverInfo = ['```python', signature, '```', descriptionWithHighlightedCode].join(EOL); - const key = signature + lines.join(''); - // Sometimes we have duplicate documentation, one with a period at the end. - if (capturedInfo.indexOf(key) >= 0 || capturedInfo.indexOf(`${key}.`) >= 0) { - return; - } - capturedInfo.push(key); - capturedInfo.push(`${key}.`); - results.push(hoverInfo); - return; - } - if (item.description) { - const descriptionWithHighlightedCode = highlightCode(item.description); - // tslint:disable-next-line:prefer-template - const hoverInfo = '```python' + EOL + signature + EOL + '```' + EOL + descriptionWithHighlightedCode; - const lines = item.description.split(EOL); - const key = signature + lines.join(''); - // Sometimes we have duplicate documentation, one with a period at the end. - if (capturedInfo.indexOf(key) >= 0 || capturedInfo.indexOf(`${key}.`) >= 0) { - return; - } - capturedInfo.push(key); - capturedInfo.push(`${key}.`); - results.push(hoverInfo); - } - }); - return new vscode.Hover(results); - } - @captureTelemetry(HOVER_DEFINITION) - public async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { - const filename = document.fileName; - if (document.lineAt(position.line).text.match(/^\s*\/\//)) { - return null; - } - if (position.character <= 0) { - return null; - } - - const range = document.getWordRangeAtPosition(position); - if (!range || range.isEmpty) { - return null; - } - const word = document.getText(range); - const cmd: proxy.ICommand = { - command: proxy.CommandType.Hover, - fileName: filename, - columnIndex: range.end.character, - lineIndex: position.line - }; - if (document.isDirty) { - cmd.source = document.getText(); - } + private hoverSource: HoverSource; - const data = await this.jediFactory.getJediProxyHandler(document.uri).sendCommand(cmd, token); - if (!data || !data.items.length) { - return; - } + constructor(jediFactory: JediFactory) { + this.hoverSource = new HoverSource(jediFactory); + } - return PythonHoverProvider.parseData(data, word); + @captureTelemetry(HOVER_DEFINITION) + public async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) + : Promise { + return this.hoverSource.getVsCodeHover(document, position, token); } } diff --git a/src/client/providers/hoverSource.ts b/src/client/providers/hoverSource.ts new file mode 100644 index 000000000000..d3516cb40ca9 --- /dev/null +++ b/src/client/providers/hoverSource.ts @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; + +import { EOL } from 'os'; +import * as vscode from 'vscode'; +import { JediFactory } from '../languageServices/jediProxyFactory'; +import { highlightCode } from './jediHelpers'; +import * as proxy from './jediProxy'; + +export class HoverSource { + constructor(private jediFactory: JediFactory) { } + + public async getVsCodeHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) + : Promise { + const strings = await this.getHoverStrings(document, position, null, token); + if (!strings) { + return; + } + return new vscode.Hover(strings); + } + + // tslint:disable-next-line:no-any + public async getHoverStrings(document: vscode.TextDocument, position: vscode.Position, sourceText: string | null, token: vscode.CancellationToken): Promise { + const result = await this.getHoverResult(document, position, sourceText, token); + if (!result || !result.items.length) { + return []; + } + const range = document.getWordRangeAtPosition(position); + if (!range || range.isEmpty) { + return []; + } + const word = document.getText(range); + return this.getHoverStringsFromResult(result, word, true); + } + + // tslint:disable-next-line:no-any + public async getDocStrings(document: vscode.TextDocument, position: vscode.Position, sourceText: string | null, token: vscode.CancellationToken): Promise { + const result = await this.getHoverResult(document, position, sourceText, token); + if (!result || !result.items.length) { + return []; + } + return this.getHoverStringsFromResult(result, '', false); + } + + private async getHoverResult(document: vscode.TextDocument, position: vscode.Position, sourceText: string | null, token: vscode.CancellationToken) + : Promise { + if (document.lineAt(position.line).text.match(/^\s*\/\//)) { + return; + } + if (position.character <= 0) { + return; + } + const range = document.getWordRangeAtPosition(position); + if (!range || range.isEmpty) { + return; + } + + if (sourceText === null && document.isDirty) { + sourceText = document.getText(); + } + return await this.getHoverResultFromRange(document, range, sourceText, token); + } + + private async getHoverResultFromRange(document: vscode.TextDocument, range: vscode.Range, documentText: string | null, token: vscode.CancellationToken) + : Promise { + const cmd: proxy.ICommand = { + command: proxy.CommandType.Hover, + fileName: document.fileName, + columnIndex: range.end.character, + lineIndex: range.end.line + }; + if (documentText) { + cmd.source = documentText; + } + return await this.jediFactory.getJediProxyHandler(document.uri).sendCommand(cmd, token); + } + + // tslint:disable-next-line:no-any + private getHoverStringsFromResult(data: proxy.IHoverResult, currentWord: string, markedStringInfo: boolean): any[] { + const results: string[] = []; + const capturedInfo: string[] = []; + data.items.forEach(item => { + let { signature } = item; + switch (item.kind) { + case vscode.SymbolKind.Constructor: + case vscode.SymbolKind.Function: + case vscode.SymbolKind.Method: { + signature = `def ${signature}`; + break; + } + case vscode.SymbolKind.Class: { + signature = `class ${signature}`; + break; + } + default: { + signature = typeof item.text === 'string' && item.text.length > 0 ? item.text : currentWord; + } + } + if (item.docstring) { + let lines = item.docstring.split(/\r?\n/); + // If the docstring starts with the signature, then remove those lines from the docstring. + if (lines.length > 0 && item.signature.indexOf(lines[0]) === 0) { + lines.shift(); + const endIndex = lines.findIndex(line => item.signature.endsWith(line)); + if (endIndex >= 0) { + lines = lines.filter((line, index) => index > endIndex); + } + } + if (lines.length > 0 && item.signature.startsWith(currentWord) && lines[0].startsWith(currentWord) && lines[0].endsWith(')')) { + lines.shift(); + } + const descriptionWithHighlightedCode = highlightCode(lines.join(EOL)); + const hoverInfo = markedStringInfo ? ['```python', signature, '```', descriptionWithHighlightedCode].join(EOL) : descriptionWithHighlightedCode; + const key = signature + lines.join(''); + // Sometimes we have duplicate documentation, one with a period at the end. + if (capturedInfo.indexOf(key) >= 0 || capturedInfo.indexOf(`${key}.`) >= 0) { + return; + } + capturedInfo.push(key); + capturedInfo.push(`${key}.`); + results.push(hoverInfo); + return; + } + + if (item.description) { + const descriptionWithHighlightedCode = highlightCode(item.description); + // tslint:disable-next-line:prefer-template + const hoverInfo = '```python' + `${EOL}${signature}${EOL}` + '```' + `${EOL}${descriptionWithHighlightedCode}`; + const lines = item.description.split(EOL); + const key = signature + lines.join(''); + // Sometimes we have duplicate documentation, one with a period at the end. + if (capturedInfo.indexOf(key) >= 0 || capturedInfo.indexOf(`${key}.`) >= 0) { + return; + } + capturedInfo.push(key); + capturedInfo.push(`${key}.`); + results.push(hoverInfo); + } + }); + return results; + } +} diff --git a/src/client/providers/jediProxy.ts b/src/client/providers/jediProxy.ts index f30b1dd91012..f6e46327f993 100644 --- a/src/client/providers/jediProxy.ts +++ b/src/client/providers/jediProxy.ts @@ -1,16 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. 'use strict'; import * as child_process from 'child_process'; -import * as vscode from 'vscode'; import * as path from 'path'; -import * as settings from './../common/configSettings'; -import * as logger from './../common/logger'; -import * as telemetryHelper from '../telemetry'; -import { execPythonFile, getCustomEnvVarsSync, validatePath } from '../common/utils'; -import { createDeferred, Deferred } from '../common/helpers'; -import { getCustomEnvVars } from '../common/utils'; +import * as vscode from 'vscode'; +import { PythonSettings } from '../common/configSettings'; import { mergeEnvVariables } from '../common/envFileParser'; -import { IPythonSettings, PythonSettings } from '../common/configSettings'; +import { createDeferred, Deferred } from '../common/helpers'; +import { execPythonFile, getCustomEnvVarsSync, validatePath } from '../common/utils'; +import * as logger from './../common/logger'; const IS_WINDOWS = /^win/.test(process.platform); @@ -88,20 +87,22 @@ pythonVSCodeSymbolMappings.set('list', vscode.SymbolKind.Array); function getMappedVSCodeType(pythonType: string): vscode.CompletionItemKind { if (pythonVSCodeTypeMappings.has(pythonType)) { - return pythonVSCodeTypeMappings.get(pythonType); - } - else { - return vscode.CompletionItemKind.Keyword; + const value = pythonVSCodeTypeMappings.get(pythonType); + if (value) { + return value; + } } + return vscode.CompletionItemKind.Keyword; } function getMappedVSCodeSymbol(pythonType: string): vscode.SymbolKind { if (pythonVSCodeSymbolMappings.has(pythonType)) { - return pythonVSCodeSymbolMappings.get(pythonType); - } - else { - return vscode.SymbolKind.Variable; + const value = pythonVSCodeSymbolMappings.get(pythonType); + if (value) { + return value; + } } + return vscode.SymbolKind.Variable; } export enum CommandType { @@ -113,44 +114,86 @@ export enum CommandType { Symbols } -var commandNames = new Map(); -commandNames.set(CommandType.Arguments, "arguments"); -commandNames.set(CommandType.Completions, "completions"); -commandNames.set(CommandType.Definitions, "definitions"); -commandNames.set(CommandType.Hover, "tooltip"); -commandNames.set(CommandType.Usages, "usages"); -commandNames.set(CommandType.Symbols, "names"); +const commandNames = new Map(); +commandNames.set(CommandType.Arguments, 'arguments'); +commandNames.set(CommandType.Completions, 'completions'); +commandNames.set(CommandType.Definitions, 'definitions'); +commandNames.set(CommandType.Hover, 'tooltip'); +commandNames.set(CommandType.Usages, 'usages'); +commandNames.set(CommandType.Symbols, 'names'); export class JediProxy implements vscode.Disposable { - private proc: child_process.ChildProcess; + private proc: child_process.ChildProcess | null; private pythonSettings: PythonSettings; + private cmdId: number = 0; + private pythonProcessCWD = ''; + private lastKnownPythonInterpreter: string; + private previousData = ''; + private commands = new Map>(); + private commandQueue: number[] = []; + private spawnRetryAttempts = 0; + private lastKnownPythonPath: string; + private additionalAutoCopletePaths: string[] = []; + private workspacePath: string; - public constructor(private extensionRootDir: string, private workspacePath: string) { + public constructor(extensionRootDir: string, workspacePath: string) { + this.workspacePath = workspacePath; this.pythonSettings = PythonSettings.getInstance(vscode.Uri.file(workspacePath)); - this.lastKnownPythonInterpreter = this.pythonSettings.pythonPath + this.lastKnownPythonInterpreter = this.pythonSettings.pythonPath; this.pythonSettings.on('change', this.onPythonSettingsChanged.bind(this)); vscode.workspace.onDidChangeConfiguration(this.onConfigChanged.bind(this)); this.onConfigChanged(); this.initialize(extensionRootDir); } + + private static getProperty(o: object, name: string): T { + return o[name]; + } + public dispose() { this.killProcess(); } - private cmdId: number = 0; public getNextCommandId(): number { - return this.cmdId++; + const result = this.cmdId; + this.cmdId += 1; + return result; + } + + public sendCommand(cmd: ICommand): Promise { + if (!this.proc) { + return Promise.reject(new Error('Python proc not initialized')); + } + const executionCmd = >cmd; + const payload = this.createPayload(executionCmd); + executionCmd.deferred = createDeferred(); + // if (typeof executionCmd.telemetryEvent === 'string') { + // executionCmd.delays = new telemetryHelper.Delays(); + // } + try { + this.proc.stdin.write(`${JSON.stringify(payload)}\n`); + this.commands.set(executionCmd.id, executionCmd); + this.commandQueue.push(executionCmd.id); + } catch (ex) { + console.error(ex); + //If 'This socket is closed.' that means process didn't start at all (at least not properly). + if (ex.message === 'This socket is closed.') { + this.killProcess(); + } else { + this.handleError('sendCommand', ex.message); + } + return Promise.reject(ex); + } + return executionCmd.deferred.promise; } // keep track of the directory so we can re-spawn the process. - private pythonProcessCWD = ""; private initialize(dir: string) { this.pythonProcessCWD = dir; - this.spawnProcess(path.join(dir, "pythonFiles")); + this.spawnProcess(path.join(dir, 'pythonFiles')); } // Check if settings changes. - private lastKnownPythonInterpreter: string; private onPythonSettingsChanged() { if (this.lastKnownPythonInterpreter === this.pythonSettings.pythonPath) { return; @@ -163,312 +206,306 @@ export class JediProxy implements vscode.Disposable { private clearPendingRequests() { this.commandQueue = []; this.commands.forEach(item => { - item.deferred.resolve(); + if (item.deferred !== undefined) { + item.deferred.resolve(); + } }); this.commands.clear(); } - private previousData = ""; - private commands = new Map>(); - private commandQueue: number[] = []; private killProcess() { try { if (this.proc) { this.proc.kill(); } - } - catch (ex) { } + // tslint:disable-next-line:no-empty + } catch (ex) { } this.proc = null; } private handleError(source: string, errorMessage: string) { - logger.error(source + ' jediProxy', `Error (${source}) ${errorMessage}`); + logger.error(`${source} jediProxy`, `Error (${source}) ${errorMessage}`); } - private spawnRetryAttempts = 0; + // tslint:disable-next-line:max-func-body-length private spawnProcess(dir: string) { try { - let environmentVariables: Object & { [key: string]: string } = { 'PYTHONUNBUFFERED': '1' }; - let customEnvironmentVars = getCustomEnvVarsSync(vscode.Uri.file(dir)); + let environmentVariables: Object & { [key: string]: string } = { PYTHONUNBUFFERED: '1' }; + const customEnvironmentVars = getCustomEnvVarsSync(vscode.Uri.file(dir)); if (customEnvironmentVars) { environmentVariables = mergeEnvVariables(environmentVariables, customEnvironmentVars); } environmentVariables = mergeEnvVariables(environmentVariables); - logger.log('child_process.spawn in jediProxy', 'Value of pythonSettings.pythonPath is :' + this.pythonSettings.pythonPath); - const args = ["completion.py"]; + logger.log('child_process.spawn in jediProxy', `Value of pythonSettings.pythonPath is : ${this.pythonSettings.pythonPath}`); + const args = ['completion.py']; if (typeof this.pythonSettings.jediPath !== 'string' || this.pythonSettings.jediPath.length === 0) { if (Array.isArray(this.pythonSettings.devOptions) && this.pythonSettings.devOptions.some(item => item.toUpperCase().trim() === 'USERELEASEAUTOCOMP')) { // Use standard version of jedi library. args.push('std'); - } - else { + } else { // Use preview version of jedi library. args.push('preview'); } - } - else { + } else { args.push('custom'); args.push(this.pythonSettings.jediPath); } if (Array.isArray(this.pythonSettings.autoComplete.preloadModules) && this.pythonSettings.autoComplete.preloadModules.length > 0) { - var modules = this.pythonSettings.autoComplete.preloadModules.filter(m => m.trim().length > 0).join(','); + const modules = this.pythonSettings.autoComplete.preloadModules.filter(m => m.trim().length > 0).join(','); args.push(modules); } this.proc = child_process.spawn(this.pythonSettings.pythonPath, args, { cwd: dir, env: environmentVariables }); - } - catch (ex) { - return this.handleError("spawnProcess", ex.message); + } catch (ex) { + return this.handleError('spawnProcess', ex.message); } this.proc.stderr.setEncoding('utf8'); - this.proc.stderr.on("data", (data: string) => { - this.handleError("stderr", data); + this.proc.stderr.on('data', (data: string) => { + this.handleError('stderr', data); }); - this.proc.on("end", (end) => { - logger.error('spawnProcess.end', "End - " + end); + this.proc.on('end', (end) => { + logger.error('spawnProcess.end', `End - ${end}`); }); - this.proc.on("error", error => { - this.handleError("error", error + ''); - this.spawnRetryAttempts++; + this.proc.on('error', error => { + this.handleError('error', `${error}`); + this.spawnRetryAttempts += 1; if (this.spawnRetryAttempts < 10 && error && error.message && error.message.indexOf('This socket has been ended by the other party') >= 0) { this.spawnProcess(dir); } }); this.proc.stdout.setEncoding('utf8'); - this.proc.stdout.on("data", (data: string) => { + // tslint:disable-next-line:max-func-body-length + this.proc.stdout.on('data', (data: string) => { // Possible there was an exception in parsing the data returned, // so append the data then parse it. - var dataStr = this.previousData = this.previousData + data + ""; - var responses: any[]; + const dataStr = this.previousData = `${this.previousData}${data}`; + // tslint:disable-next-line:no-any + let responses: any[]; try { responses = dataStr.split(/\r?\n/g).filter(line => line.length > 0).map(resp => JSON.parse(resp)); - this.previousData = ""; - } - catch (ex) { + this.previousData = ''; + } catch (ex) { // Possible we've only received part of the data, hence don't clear previousData. // Don't log errors when we haven't received the entire response. if (ex.message.indexOf('Unexpected end of input') === -1 && ex.message.indexOf('Unexpected end of JSON input') === -1 && ex.message.indexOf('Unexpected token') === -1) { - this.handleError("stdout", ex.message); + this.handleError('stdout', ex.message); } return; } + // tslint:disable-next-line:max-func-body-length + // tslint:disable-next-line:cyclomatic-complexity responses.forEach((response) => { // What's this, can't remember, // Great example of poorly written code (this whole file is a mess). // I think this needs to be removed, because this is misspelt, it is argments, 'U' is missing, // And that case is handled further down // case CommandType.Arguments: { - // Rewrite this mess to use stratergy.. - if (response["argments"]) { - var index = this.commandQueue.indexOf(cmd.id); - this.commandQueue.splice(index, 1); + // Rewrite this mess to use stratergy. + + const responseId = JediProxy.getProperty(response, 'id'); + const cmd = >this.commands.get(responseId); + if (cmd === null) { return; } - var responseId = response["id"]; - - var cmd = >this.commands.get(responseId); - if (typeof cmd === "object" && cmd !== null) { - this.commands.delete(responseId); - var index = this.commandQueue.indexOf(cmd.id); - this.commandQueue.splice(index, 1); - if (cmd.delay && typeof cmd.telemetryEvent === 'string') { - // cmd.delays.stop(); - // telemetryHelper.sendTelemetryEvent(cmd.telemetryEvent, null, cmd.delays.toMeasures()); - } + if (JediProxy.getProperty(response, 'arguments')) { + this.commandQueue.splice(this.commandQueue.indexOf(cmd.id), 1); + return; + } - // Check if this command has expired. - if (cmd.token.isCancellationRequested) { - cmd.deferred.resolve(); - return; - } + this.commands.delete(responseId); + const index = this.commandQueue.indexOf(cmd.id); + if (index) { + this.commandQueue.splice(index, 1); + } - switch (cmd.command) { - case CommandType.Completions: { - let results = response['results']; - results = Array.isArray(results) ? results : []; - results.forEach(item => { - const originalType = item.type; - item.type = getMappedVSCodeType(originalType); - item.kind = getMappedVSCodeSymbol(originalType); - item.rawType = getMappedVSCodeType(originalType); - }); - - let completionResult: ICompletionResult = { - items: results, - requestId: cmd.id - }; - cmd.deferred.resolve(completionResult); - break; - } - case CommandType.Definitions: { - let defs = response['results']; - let defResult: IDefinitionResult = { - requestId: cmd.id, - definitions: [] - }; - if (defs.length > 0) { - defResult.definitions = defs.map(def => { - const originalType = def.type as string; - return { - fileName: def.fileName, - text: def.text, - rawType: originalType, - type: getMappedVSCodeType(originalType), - kind: getMappedVSCodeSymbol(originalType), - container: def.container, - range: { - startLine: def.range.start_line, - startColumn: def.range.start_column, - endLine: def.range.end_line, - endColumn: def.range.end_column - } - }; - }); - } - - cmd.deferred.resolve(defResult); - break; - } - case CommandType.Hover: { - let defs = response['results']; - var defResult: IHoverResult = { - requestId: cmd.id, - items: defs.map(def => { - return { - kind: getMappedVSCodeSymbol(def.type), - description: def.description, - signature: def.signature, - docstring: def.docstring, - text: def.text - }; - }) - }; - - cmd.deferred.resolve(defResult); - break; - } - case CommandType.Symbols: { - let defs = response['results']; - defs = Array.isArray(defs) ? defs : []; - var defResults: ISymbolResult = { - requestId: cmd.id, - definitions: [] - }; - defResults.definitions = defs.map(def => { - const originalType = def.type as string; - return { - fileName: def.fileName, - text: def.text, - rawType: originalType, - type: getMappedVSCodeType(originalType), - kind: getMappedVSCodeSymbol(originalType), - container: def.container, - range: { - startLine: def.range.start_line, - startColumn: def.range.start_column, - endLine: def.range.end_line, - endColumn: def.range.end_column - } - }; - }); - - cmd.deferred.resolve(defResults); - break; - } - case CommandType.Usages: { - let defs = response['results']; - defs = Array.isArray(defs) ? defs : []; - var refResult: IReferenceResult = { - requestId: cmd.id, - references: defs.map(item => { - return { - columnIndex: item.column, - fileName: item.fileName, - lineIndex: item.line - 1, - moduleName: item.moduleName, - name: item.name - }; - } - ) - }; - - cmd.deferred.resolve(refResult); - break; - } - case CommandType.Arguments: { - let defs = response["results"]; - cmd.deferred.resolve({ - requestId: cmd.id, - definitions: defs - }); - break; - } - } + // Check if this command has expired. + if (cmd.token.isCancellationRequested) { + this.safeResolve(cmd, undefined); + return; } - //Ok, check if too many pending requets. - if (this.commandQueue.length > 10) { - var items = this.commandQueue.splice(0, this.commandQueue.length - 10); - items.forEach(id => { - if (this.commands.has(id)) { - const cmd = this.commands.get(id); - try { - cmd.deferred.resolve(null); - } - catch (ex) { - } - this.commands.delete(id); - } - }); + switch (cmd.command) { + case CommandType.Completions: + this.onCompletion(cmd, response); + break; + case CommandType.Definitions: + this.onDefinition(cmd, response); + break; + case CommandType.Hover: + this.onHover(cmd, response); + break; + case CommandType.Symbols: + this.onSymbols(cmd, response); + break; + case CommandType.Usages: + this.onUsages(cmd, response); + break; + case CommandType.Arguments: + this.onArguments(cmd, response); + break; + default: + break; } + // Check if too many pending requets. + this.checkQueueLength(); }); }); } - public sendCommand(cmd: ICommand): Promise { - if (!this.proc) { - return Promise.reject(new Error("Python proc not initialized")); - } - var executionCmd = >cmd; - var payload = this.createPayload(executionCmd); - executionCmd.deferred = createDeferred(); - // if (typeof executionCmd.telemetryEvent === 'string') { - // executionCmd.delays = new telemetryHelper.Delays(); - // } - try { - this.proc.stdin.write(JSON.stringify(payload) + "\n"); - this.commands.set(executionCmd.id, executionCmd); - this.commandQueue.push(executionCmd.id); + private onCompletion(command: IExecutionCommand, response: object): void { + let results = JediProxy.getProperty(response, 'results'); + results = Array.isArray(results) ? results : []; + results.forEach(item => { + // tslint:disable-next-line:no-any + const originalType = item.type; + item.type = getMappedVSCodeType(originalType); + item.kind = getMappedVSCodeSymbol(originalType); + item.rawType = getMappedVSCodeType(originalType); + }); + const completionResult: ICompletionResult = { + items: results, + requestId: command.id + }; + this.safeResolve(command, completionResult); + } + + private onDefinition(command: IExecutionCommand, response: object): void { + // tslint:disable-next-line:no-any + const defs = JediProxy.getProperty(response, 'results'); + const defResult: IDefinitionResult = { + requestId: command.id, + definitions: [] + }; + if (defs.length > 0) { + defResult.definitions = defs.map(def => { + const originalType = def.type as string; + return { + fileName: def.fileName, + text: def.text, + rawType: originalType, + type: getMappedVSCodeType(originalType), + kind: getMappedVSCodeSymbol(originalType), + container: def.container, + range: { + startLine: def.range.start_line, + startColumn: def.range.start_column, + endLine: def.range.end_line, + endColumn: def.range.end_column + } + }; + }); } - catch (ex) { - console.error(ex); - //If 'This socket is closed.' that means process didn't start at all (at least not properly). - if (ex.message === "This socket is closed.") { + this.safeResolve(command, defResult); + } - this.killProcess(); - } - else { - this.handleError("sendCommand", ex.message); + private onHover(command: IExecutionCommand, response: object): void { + // tslint:disable-next-line:no-any + const defs = JediProxy.getProperty(response, 'results'); + const defResult: IHoverResult = { + requestId: command.id, + items: defs.map(def => { + return { + kind: getMappedVSCodeSymbol(def.type), + description: def.description, + signature: def.signature, + docstring: def.docstring, + text: def.text + }; + }) + }; + this.safeResolve(command, defResult); + } + + private onSymbols(command: IExecutionCommand, response: object): void { + // tslint:disable-next-line:no-any + let defs = JediProxy.getProperty(response, 'results'); + defs = Array.isArray(defs) ? defs : []; + const defResults: ISymbolResult = { + requestId: command.id, + definitions: [] + }; + defResults.definitions = defs.map(def => { + const originalType = def.type as string; + return { + fileName: def.fileName, + text: def.text, + rawType: originalType, + type: getMappedVSCodeType(originalType), + kind: getMappedVSCodeSymbol(originalType), + container: def.container, + range: { + startLine: def.range.start_line, + startColumn: def.range.start_column, + endLine: def.range.end_line, + endColumn: def.range.end_column + } + }; + }); + this.safeResolve(command, defResults); + } + + private onUsages(command: IExecutionCommand, response: object): void { + // tslint:disable-next-line:no-any + let defs = JediProxy.getProperty(response, 'results'); + defs = Array.isArray(defs) ? defs : []; + const refResult: IReferenceResult = { + requestId: command.id, + references: defs.map(item => { + return { + columnIndex: item.column, + fileName: item.fileName, + lineIndex: item.line - 1, + moduleName: item.moduleName, + name: item.name + }; } - return Promise.reject(ex); + ) + }; + this.safeResolve(command, refResult); + } + + private onArguments(command: IExecutionCommand, response: object): void { + // tslint:disable-next-line:no-any + const defs = JediProxy.getProperty(response, 'results'); + this.safeResolve(command, { + requestId: command.id, + definitions: defs + }); + } + + private checkQueueLength(): void { + if (this.commandQueue.length > 10) { + const items = this.commandQueue.splice(0, this.commandQueue.length - 10); + items.forEach(id => { + if (this.commands.has(id)) { + const cmd1 = this.commands.get(id); + try { + this.safeResolve(cmd1, undefined); + // tslint:disable-next-line:no-empty + } catch (ex) { + } finally { + this.commands.delete(id); + } + } + }); } - return executionCmd.deferred.promise; } + // tslint:disable-next-line:no-any private createPayload(cmd: IExecutionCommand): any { - var payload = { + const payload = { id: cmd.id, - prefix: "", + prefix: '', lookup: commandNames.get(cmd.command), path: cmd.fileName, source: cmd.source, @@ -485,19 +522,18 @@ export class JediProxy implements vscode.Disposable { return payload; } - private lastKnownPythonPath: string = null; - private additionalAutoCopletePaths: string[] = []; private getPathFromPythonCommand(args: string[]): Promise { return execPythonFile(this.workspacePath, this.pythonSettings.pythonPath, args, this.workspacePath).then(stdout => { if (stdout.length === 0) { - return ""; + return ''; } - let lines = stdout.split(/\r?\n/g).filter(line => line.length > 0); + const lines = stdout.split(/\r?\n/g).filter(line => line.length > 0); return validatePath(lines[0]); }).catch(() => { - return ""; + return ''; }); } + private onConfigChanged() { // We're only interested in changes to the python path. if (this.lastKnownPythonPath === this.pythonSettings.pythonPath) { @@ -505,28 +541,28 @@ export class JediProxy implements vscode.Disposable { } this.lastKnownPythonPath = this.pythonSettings.pythonPath; - let filePaths = [ + const filePaths = [ // Sysprefix. - this.getPathFromPythonCommand(["-c", "import sys;print(sys.prefix)"]), + this.getPathFromPythonCommand(['-c', 'import sys;print(sys.prefix)']), // exeucutable path. - this.getPathFromPythonCommand(["-c", "import sys;print(sys.executable)"]), + this.getPathFromPythonCommand(['-c', 'import sys;print(sys.executable)']), // Python specific site packages. - this.getPathFromPythonCommand(["-c", "from distutils.sysconfig import get_python_lib; print(get_python_lib())"]), + this.getPathFromPythonCommand(['-c', 'from distutils.sysconfig import get_python_lib; print(get_python_lib())']), // Python global site packages, as a fallback in case user hasn't installed them in custom environment. - this.getPathFromPythonCommand(["-m", "site", "--user-site"]), + this.getPathFromPythonCommand(['-m', 'site', '--user-site']) ]; - let PYTHONPATH: string = process.env['PYTHONPATH']; + let PYTHONPATH: string = JediProxy.getProperty(process.env, 'PYTHONPATH'); if (typeof PYTHONPATH !== 'string') { PYTHONPATH = ''; } - let customEnvironmentVars = getCustomEnvVarsSync(vscode.Uri.file(this.pythonProcessCWD)); - if (customEnvironmentVars && customEnvironmentVars['PYTHONPATH']) { - let PYTHONPATHFromEnvFile = customEnvironmentVars['PYTHONPATH'] as string; + const customEnvironmentVars = getCustomEnvVarsSync(vscode.Uri.file(this.pythonProcessCWD)); + if (customEnvironmentVars && JediProxy.getProperty(customEnvironmentVars, 'PYTHONPATH')) { + let PYTHONPATHFromEnvFile = JediProxy.getProperty(customEnvironmentVars, 'PYTHONPATH'); if (!path.isAbsolute(PYTHONPATHFromEnvFile) && this.workspacePath === 'string') { PYTHONPATHFromEnvFile = path.resolve(this.workspacePath, PYTHONPATHFromEnvFile); } - PYTHONPATH += (PYTHONPATH.length > 0 ? + path.delimiter : '') + PYTHONPATHFromEnvFile; + PYTHONPATH += `${(PYTHONPATH.length > 0 ? + path.delimiter : '')}${PYTHONPATHFromEnvFile}`; } if (typeof PYTHONPATH === 'string' && PYTHONPATH.length > 0) { filePaths.push(Promise.resolve(PYTHONPATH.trim())); @@ -541,7 +577,7 @@ export class JediProxy implements vscode.Disposable { // On windows we also need the libs path (second item will return c:\xxx\lib\site-packages). // This is returned by "from distutils.sysconfig import get_python_lib; print(get_python_lib())". if (IS_WINDOWS && paths[2].length > 0) { - paths.splice(3, 0, path.join(paths[2], "..")); + paths.splice(3, 0, path.join(paths[2], '..')); } this.additionalAutoCopletePaths = paths.filter(p => p.length > 0); }) @@ -550,7 +586,7 @@ export class JediProxy implements vscode.Disposable { private getConfig() { // Add support for paths relative to workspace. - let extraPaths = this.pythonSettings.autoComplete.extraPaths.map(extraPath => { + const extraPaths = this.pythonSettings.autoComplete.extraPaths.map(extraPath => { if (path.isAbsolute(extraPath)) { return extraPath; } @@ -565,7 +601,7 @@ export class JediProxy implements vscode.Disposable { extraPaths.unshift(this.workspacePath); } - let distinctExtraPaths = extraPaths.concat(this.additionalAutoCopletePaths) + const distinctExtraPaths = extraPaths.concat(this.additionalAutoCopletePaths) .filter(value => value.length > 0) .filter((value, index, self) => self.indexOf(value) === index); @@ -577,7 +613,17 @@ export class JediProxy implements vscode.Disposable { fuzzyMatcher: true }; } + + private safeResolve( + command: IExecutionCommand | undefined | null, + result: ICommandResult | PromiseLike | undefined): void { + if (command && command.deferred) { + command.deferred.resolve(result); + } + } } + +// tslint:disable-next-line:no-unused-variable export interface ICommand { telemetryEvent?: string; command: CommandType; @@ -588,7 +634,7 @@ export interface ICommand { } interface IExecutionCommand extends ICommand { - id?: number; + id: number; deferred?: Deferred; token: vscode.CancellationToken; delay?: number; @@ -651,7 +697,7 @@ export interface IAutoCompleteItem { raw_docstring: string; rightLabel: string; } -interface IDefinitionRange { +export interface IDefinitionRange { startLine: number; startColumn: number; endLine: number; @@ -682,19 +728,25 @@ export class JediProxyHandler implements vscode.Dispos return this.jediProxy; } - public constructor(private jediProxy: JediProxy = null) { + public constructor(private jediProxy: JediProxy) { this.commandCancellationTokenSources = new Map(); } + public dispose() { - this.jediProxy.dispose(); + if (this.jediProxy) { + this.jediProxy.dispose(); + } } - public sendCommand(cmd: ICommand, token?: vscode.CancellationToken): Promise { - var executionCmd = >cmd; + + public sendCommand(cmd: ICommand, token?: vscode.CancellationToken): Promise { + const executionCmd = >cmd; executionCmd.id = executionCmd.id || this.jediProxy.getNextCommandId(); if (this.commandCancellationTokenSources.has(cmd.command)) { - const cancellation = this.commandCancellationTokenSources.get(cmd.command); - cancellation.cancel(); + const ct = this.commandCancellationTokenSources.get(cmd.command); + if (ct) { + ct.cancel(); + } } const cancellation = new vscode.CancellationTokenSource(); @@ -708,10 +760,12 @@ export class JediProxyHandler implements vscode.Dispos }); } - public sendCommandNonCancellableCommand(cmd: ICommand, token?: vscode.CancellationToken): Promise { - var executionCmd = >cmd; + public sendCommandNonCancellableCommand(cmd: ICommand, token?: vscode.CancellationToken): Promise { + const executionCmd = >cmd; executionCmd.id = executionCmd.id || this.jediProxy.getNextCommandId(); - executionCmd.token = token; + if (token) { + executionCmd.token = token; + } return this.jediProxy.sendCommand(executionCmd) .catch(reason => { From df5af0e0bfdb7d90a78d81b4b0f95266e3c9fc4a Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Thu, 7 Dec 2017 13:24:34 -0800 Subject: [PATCH 19/24] Test fixes --- src/client/common/configSettings.ts | 2 +- src/client/common/helpers.ts | 9 ++-- src/client/providers/completionProvider.ts | 16 ++++++-- src/client/providers/completionSource.ts | 17 ++++---- src/client/providers/hoverSource.ts | 48 +++++++++++++--------- src/client/providers/jediHelpers.ts | 5 ++- src/test/autocomplete/base.test.ts | 15 ++++--- src/test/index.ts | 3 +- 8 files changed, 70 insertions(+), 45 deletions(-) diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index b0111424ceb0..2ec93dbe46aa 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -127,7 +127,7 @@ export interface ITerminalSettings { launchArgs: string[]; } -function isTestExecution(): boolean { +export function isTestExecution(): boolean { // tslint:disable-next-line:interface-name no-string-literal return process.env['VSC_PYTHON_CI_TEST'] === '1'; } diff --git a/src/client/common/helpers.ts b/src/client/common/helpers.ts index 1f7b28a34b83..da1d68b98766 100644 --- a/src/client/common/helpers.ts +++ b/src/client/common/helpers.ts @@ -1,3 +1,5 @@ +// tslint:disable-next-line:no-var-requires +// tslint:disable-next-line:no-require-imports const tmp = require('tmp'); export function isNotInstalledError(error: Error): boolean { @@ -11,6 +13,7 @@ export function isNotInstalledError(error: Error): boolean { return errorObj.code === 'ENOENT' || errorObj.code === 127 || isModuleNoInstalledError; } +// tslint:disable-next-line:interface-name export interface Deferred { readonly promise: Promise; readonly resolved: boolean; @@ -36,12 +39,12 @@ class DeferredImpl implements Deferred { this._reject = rej; }); } - resolve(value?: T | PromiseLike) { + public resolve(value?: T | PromiseLike) { this._resolve.apply(this.scope ? this.scope : this, arguments); this._resolved = true; } // tslint:disable-next-line:no-any - reject(reason?: any) { + public reject(reason?: any) { this._reject.apply(this.scope ? this.scope : this, arguments); this._rejected = true; } @@ -71,7 +74,7 @@ export function createTemporaryFile(extension: string, temporaryDirectory?: stri } return new Promise<{ filePath: string, cleanupCallback: Function }>((resolve, reject) => { - tmp.file(options, function _tempFileCreated(err, tmpFile, fd, cleanupCallback) { + tmp.file(options, (err, tmpFile, fd, cleanupCallback) => { if (err) { return reject(err); } diff --git a/src/client/providers/completionProvider.ts b/src/client/providers/completionProvider.ts index 105e78b73c53..8ed8c6f16f10 100644 --- a/src/client/providers/completionProvider.ts +++ b/src/client/providers/completionProvider.ts @@ -3,6 +3,7 @@ 'use strict'; import * as vscode from 'vscode'; +import { isTestExecution } from '../common/configSettings'; import { JediFactory } from '../languageServices/jediProxyFactory'; import { captureTelemetry } from '../telemetry'; import { COMPLETION } from '../telemetry/constants'; @@ -16,12 +17,19 @@ export class PythonCompletionItemProvider implements vscode.CompletionItemProvid } @captureTelemetry(COMPLETION) - public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.ProviderResult { - return this.completionSource.getVsCodeCompletionItems(document, position, token); + public async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): + Promise { + const items = await this.completionSource.getVsCodeCompletionItems(document, position, token); + if (isTestExecution()) { + for (let i = 0; i < Math.min(3, items.length); i += 1) { + items[i] = await this.resolveCompletionItem(items[i], token); + } + } + return items; } - public async resolveCompletionItem(item: vscode.CompletionItem): Promise { - item.documentation = await this.completionSource.getDocumentation(item); + public async resolveCompletionItem(item: vscode.CompletionItem, token: vscode.CancellationToken): Promise { + item.documentation = await this.completionSource.getDocumentation(item, token); return item; } } diff --git a/src/client/providers/completionSource.ts b/src/client/providers/completionSource.ts index e5abb23fbe70..40295c7e0fba 100644 --- a/src/client/providers/completionSource.ts +++ b/src/client/providers/completionSource.ts @@ -42,7 +42,7 @@ export class CompletionSource { return this.toVsCodeCompletions(new DocumentPosition(document, position), result, document.uri); } - public async getDocumentation(completionItem: vscode.CompletionItem): Promise { + public async getDocumentation(completionItem: vscode.CompletionItem, token: vscode.CancellationToken): Promise { const documentPosition = DocumentPosition.fromObject(completionItem); if (documentPosition === undefined) { Promise.resolve(''); @@ -53,15 +53,16 @@ export class CompletionSource { const position = documentPosition.position; const itemText = completionItem.insertText ? completionItem.insertText : completionItem.label; const wordRange = document.getWordRangeAtPosition(position); - let sourceText: string | null = null; - if (wordRange !== undefined) { - const leadingRange = new vscode.Range(new vscode.Position(0, 0), wordRange.start); - sourceText = `${document.getText(leadingRange)}${itemText}`; - } + const leadingRange = wordRange !== undefined + ? new vscode.Range(new vscode.Position(0, 0), wordRange.start) + : new vscode.Range(new vscode.Position(0, 0), position); + + const itemString = `${itemText}`; + const sourceText = `${document.getText(leadingRange)}${itemString}`; + const range = new vscode.Range(leadingRange.end, leadingRange.end.translate(0, itemString.length)); - const cts = new vscode.CancellationTokenSource(); - const hoverStrings = await this.hoverSource.getDocStrings(document, position, sourceText, cts.token); + const hoverStrings = await this.hoverSource.getHoverStringsFromText(document.uri, document.fileName, range, sourceText, token); if (!hoverStrings || hoverStrings.length === 0) { return ''; } diff --git a/src/client/providers/hoverSource.ts b/src/client/providers/hoverSource.ts index d3516cb40ca9..522995cea678 100644 --- a/src/client/providers/hoverSource.ts +++ b/src/client/providers/hoverSource.ts @@ -13,7 +13,7 @@ export class HoverSource { public async getVsCodeHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) : Promise { - const strings = await this.getHoverStrings(document, position, null, token); + const strings = await this.getHoverStringsFromDocument(document, position, token); if (!strings) { return; } @@ -21,29 +21,29 @@ export class HoverSource { } // tslint:disable-next-line:no-any - public async getHoverStrings(document: vscode.TextDocument, position: vscode.Position, sourceText: string | null, token: vscode.CancellationToken): Promise { - const result = await this.getHoverResult(document, position, sourceText, token); + public async getHoverStringsFromText(documentUri: vscode.Uri, fileName: string, range: vscode.Range, sourceText: string, token: vscode.CancellationToken): Promise { + const result = await this.getHoverResultFromTextRange(documentUri, fileName, range, sourceText, token); if (!result || !result.items.length) { return []; } + return this.getHoverStringsFromResult(result, '', false); + } + + // tslint:disable-next-line:no-any + private async getHoverStringsFromDocument(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { const range = document.getWordRangeAtPosition(position); if (!range || range.isEmpty) { return []; } - const word = document.getText(range); - return this.getHoverStringsFromResult(result, word, true); - } - - // tslint:disable-next-line:no-any - public async getDocStrings(document: vscode.TextDocument, position: vscode.Position, sourceText: string | null, token: vscode.CancellationToken): Promise { - const result = await this.getHoverResult(document, position, sourceText, token); + const result = await this.getHoverResultFromDocument(document, position, token); if (!result || !result.items.length) { return []; } - return this.getHoverStringsFromResult(result, '', false); + const word = document.getText(range); + return this.getHoverStringsFromResult(result, word, true); } - private async getHoverResult(document: vscode.TextDocument, position: vscode.Position, sourceText: string | null, token: vscode.CancellationToken) + private async getHoverResultFromDocument(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) : Promise { if (document.lineAt(position.line).text.match(/^\s*\/\//)) { return; @@ -55,14 +55,10 @@ export class HoverSource { if (!range || range.isEmpty) { return; } - - if (sourceText === null && document.isDirty) { - sourceText = document.getText(); - } - return await this.getHoverResultFromRange(document, range, sourceText, token); + return await this.getHoverResultFromDocumentRange(document, range, token); } - private async getHoverResultFromRange(document: vscode.TextDocument, range: vscode.Range, documentText: string | null, token: vscode.CancellationToken) + private async getHoverResultFromDocumentRange(document: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken) : Promise { const cmd: proxy.ICommand = { command: proxy.CommandType.Hover, @@ -70,12 +66,24 @@ export class HoverSource { columnIndex: range.end.character, lineIndex: range.end.line }; - if (documentText) { - cmd.source = documentText; + if (document.isDirty) { + cmd.source = document.getText(); } return await this.jediFactory.getJediProxyHandler(document.uri).sendCommand(cmd, token); } + private async getHoverResultFromTextRange(documentUri: vscode.Uri, fileName: string, range: vscode.Range, sourceText: string, token: vscode.CancellationToken) + : Promise { + const cmd: proxy.ICommand = { + command: proxy.CommandType.Hover, + fileName: fileName, + columnIndex: range.end.character, + lineIndex: range.end.line, + source: sourceText + }; + return await this.jediFactory.getJediProxyHandler(documentUri).sendCommand(cmd, token); + } + // tslint:disable-next-line:no-any private getHoverStringsFromResult(data: proxy.IHoverResult, currentWord: string, markedStringInfo: boolean): any[] { const results: string[] = []; diff --git a/src/client/providers/jediHelpers.ts b/src/client/providers/jediHelpers.ts index 6054cab301a9..00e0a7031731 100644 --- a/src/client/providers/jediHelpers.ts +++ b/src/client/providers/jediHelpers.ts @@ -6,7 +6,7 @@ import { EOL } from 'os'; import * as vscode from 'vscode'; import * as proxy from './jediProxy'; -export function extractSignatureAndDocumentation(definition: proxy.IAutoCompleteItem, highlightCode: boolean = false): [string, string] { +export function extractSignatureAndDocumentation(definition: proxy.IAutoCompleteItem, highlight: boolean = false): [string, string] { // Somtimes the signature of the function, class (whatever) is broken into multiple lines // Here's an example // ```python @@ -42,9 +42,10 @@ export function extractSignatureAndDocumentation(definition: proxy.IAutoComplete } // check if we have any sample code in the documentation - if (highlightCode) { + if (highlight) { lines = lines.map(line => { if (line.trim().startsWith('>>> ')) { + // tslint:disable-next-line:prefer-template return '```python\n' + line.substring(4).trim() + '\n```'; } return line; diff --git a/src/test/autocomplete/base.test.ts b/src/test/autocomplete/base.test.ts index 1873f86f0776..56f40efb1036 100644 --- a/src/test/autocomplete/base.test.ts +++ b/src/test/autocomplete/base.test.ts @@ -151,9 +151,10 @@ suite('Autocomplete', () => { const position = new vscode.Position(25, 4); return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); }).then(list => { - assert.equal(list.items.filter(item => item.label === 'bar').length, 1, 'bar not found'); + const items = list.items.filter(item => item.label === 'bar'); + assert.equal(items.length, 1, 'bar not found'); const documentation = `说明 - keep this line, it works${EOL}delete following line, it works${EOL}如果存在需要等待审批或正在执行的任务,将不刷新页面`; - assert.equal(list.items.filter(item => item.label === 'bar')[0].documentation, documentation, 'unicode documentation is incorrect'); + assert.equal(items[0].documentation, documentation, 'unicode documentation is incorrect'); }).then(done, done); }); @@ -169,12 +170,14 @@ suite('Autocomplete', () => { const position = new vscode.Position(1, 5); return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); }).then(list => { - assert.equal(list.items.filter(item => item.label === 'Foo').length, 1, 'Foo not found'); - assert.equal(list.items.filter(item => item.label === 'Foo')[0].documentation, '说明', 'Foo unicode documentation is incorrect'); + let items = list.items.filter(item => item.label === 'Foo'); + assert.equal(items.length, 1, 'Foo not found'); + assert.equal(items[0].documentation, '说明', 'Foo unicode documentation is incorrect'); - assert.equal(list.items.filter(item => item.label === 'showMessage').length, 1, 'showMessage not found'); + items = list.items.filter(item => item.label === 'showMessage'); + assert.equal(items.length, 1, 'showMessage not found'); const documentation = `Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи. ${EOL}Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.`; - assert.equal(list.items.filter(item => item.label === 'showMessage')[0].documentation, documentation, 'showMessage unicode documentation is incorrect'); + assert.equal(items[0].documentation, documentation, 'showMessage unicode documentation is incorrect'); }).then(done, done); }); diff --git a/src/test/index.ts b/src/test/index.ts index 4d3b12a351ca..eebaa9b59c8d 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -12,7 +12,8 @@ const options: MochaSetupOptions & { retries: number } = { ui: 'tdd', useColors: true, timeout: 25000, - retries: 3 + retries: 3, + grep: 'Autocomplete' }; testRunner.configure(options); module.exports = testRunner; From fdb0e574d36f741a1e8ea1cae895bda452cc1139 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Thu, 7 Dec 2017 13:32:50 -0800 Subject: [PATCH 20/24] Undo temp change --- src/test/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/index.ts b/src/test/index.ts index eebaa9b59c8d..4d3b12a351ca 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -12,8 +12,7 @@ const options: MochaSetupOptions & { retries: number } = { ui: 'tdd', useColors: true, timeout: 25000, - retries: 3, - grep: 'Autocomplete' + retries: 3 }; testRunner.configure(options); module.exports = testRunner; From 2c8b179c053cac56a1cb3c61763627fdec7e3066 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Fri, 8 Dec 2017 12:12:02 -0800 Subject: [PATCH 21/24] CR feedback --- pythonFiles/completion.py | 27 +----------------------- src/client/providers/completionSource.ts | 10 ++++----- src/client/providers/hoverProvider.ts | 2 -- src/client/providers/jediHelpers.ts | 2 -- 4 files changed, 6 insertions(+), 35 deletions(-) diff --git a/pythonFiles/completion.py b/pythonFiles/completion.py index 15c6540cbfb2..f072349d0999 100644 --- a/pythonFiles/completion.py +++ b/pythonFiles/completion.py @@ -208,16 +208,6 @@ def _serialize_completions(self, script, identifier=None, prefix=''): _completion['snippet'] = '%s=$1$0' % name _completion['text'] = name _completion['displayText'] = name - # if self.show_doc_strings: - # try: - # _completion['description'] = signature.docstring() - # _completion['raw_docstring'] = signature.docstring(raw=True) - # except Exception: - # _completion['description'] = '' - # _completion['raw_docstring'] = '' - # else: - # _completion['description'] = self._generate_signature( - # signature) _completions.append(_completion) try: @@ -227,22 +217,11 @@ def _serialize_completions(self, script, identifier=None, prefix=''): except : completions = [] for completion in completions: - # if self.show_doc_strings: - # try: - # description = completion.docstring() - # except Exception: - # description = '' - # else: - # description = self._generate_signature(completion) - try: - # rawDocstring = completion.docstring(raw=True) _completion = { 'text': completion.name, 'type': self._get_definition_type(completion), 'raw_type': completion.type, - # 'description': description, - # 'raw_docstring': rawDocstring, 'rightLabel': self._additional_info(completion) } except Exception: @@ -252,11 +231,7 @@ def _serialize_completions(self, script, identifier=None, prefix=''): if c['text'] == _completion['text']: c['type'] = _completion['type'] c['raw_type'] = _completion['raw_type'] - # if len(c['description']) == 0 and len(c['raw_docstring']) == 0: - # c['description'] = _completion['description'] - # c['raw_docstring'] = _completion['description'] - - + if any([c['text'].split('=')[0] == _completion['text'] for c in _completions]): # ignore function arguments we already have diff --git a/src/client/providers/completionSource.ts b/src/client/providers/completionSource.ts index 40295c7e0fba..6a69e4bb5f98 100644 --- a/src/client/providers/completionSource.ts +++ b/src/client/providers/completionSource.ts @@ -15,12 +15,12 @@ class DocumentPosition { public static fromObject(item: object): DocumentPosition { // tslint:disable-next-line:no-any - return (item as any).documentPosition as DocumentPosition; + return (item as any)._documentPosition as DocumentPosition; } public attachTo(item: object): void { // tslint:disable-next-line:no-any - (item as any).documentPosition = this; + (item as any)._documentPosition = this; } } @@ -45,7 +45,7 @@ export class CompletionSource { public async getDocumentation(completionItem: vscode.CompletionItem, token: vscode.CancellationToken): Promise { const documentPosition = DocumentPosition.fromObject(completionItem); if (documentPosition === undefined) { - Promise.resolve(''); + return ''; } // Supply hover source with simulated document text where item in question was 'already typed'. @@ -79,7 +79,7 @@ export class CompletionSource { if (lineText.match(/^\s*\/\//)) { return undefined; } - // Suppress completion inside string and comments + // Suppress completion inside string and comments. if (this.isPositionInsideStringOrComment(document, position)) { return undefined; } @@ -109,7 +109,7 @@ export class CompletionSource { (item.kind === vscode.SymbolKind.Function || item.kind === vscode.SymbolKind.Method)) { completionItem.insertText = new vscode.SnippetString(item.text).appendText('(').appendTabstop().appendText(')'); } - // ensure the built in members are at the bottom + // Ensure the built in members are at the bottom. completionItem.sortText = (completionItem.label.startsWith('__') ? 'z' : (completionItem.label.startsWith('_') ? 'y' : '__')) + completionItem.label; documentPosition.attachTo(completionItem); return completionItem; diff --git a/src/client/providers/hoverProvider.ts b/src/client/providers/hoverProvider.ts index 0ea3e28c812d..4bd3380d9353 100644 --- a/src/client/providers/hoverProvider.ts +++ b/src/client/providers/hoverProvider.ts @@ -1,5 +1,3 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. 'use strict'; import * as vscode from 'vscode'; diff --git a/src/client/providers/jediHelpers.ts b/src/client/providers/jediHelpers.ts index 00e0a7031731..8e6cc78132fa 100644 --- a/src/client/providers/jediHelpers.ts +++ b/src/client/providers/jediHelpers.ts @@ -1,5 +1,3 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. 'use strict'; import { EOL } from 'os'; From 3689667b9a54588c67d47a04a321f1d127c95df8 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Fri, 8 Dec 2017 17:01:54 -0800 Subject: [PATCH 22/24] Restore signature, make info colorized --- package-lock.json | 1116 ++--------------- src/client/jedi/parsers/CompletionParser.ts | 27 - src/client/jedi/parsers/HoverParser.ts | 71 -- src/client/providers/completionProvider.ts | 12 +- src/client/providers/completionSource.ts | 16 +- src/client/providers/hoverProvider.ts | 13 +- .../{hoverSource.ts => itemInfoSource.ts} | 127 +- src/client/providers/jediHelpers.ts | 112 -- src/test/autocomplete/base.test.ts | 27 +- 9 files changed, 237 insertions(+), 1284 deletions(-) delete mode 100644 src/client/jedi/parsers/CompletionParser.ts delete mode 100644 src/client/jedi/parsers/HoverParser.ts rename src/client/providers/{hoverSource.ts => itemInfoSource.ts} (50%) delete mode 100644 src/client/providers/jediHelpers.ts diff --git a/package-lock.json b/package-lock.json index df68a6697229..2aa9e6a53610 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@types/chai-as-promised": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.0.tgz", - "integrity": "sha512-MFiW54UOSt+f2bRw8J7LgQeIvE/9b4oGvwU7XW30S9QGAiHGnU/fmiOprsyMkdmH2rl8xSPc0/yrQw8juXU6bQ==", + "integrity": "sha1-AQsEzeeOrPtucr/ds+WP4jwueLk=", "dev": true, "requires": { "@types/chai": "4.0.6" @@ -22,7 +22,7 @@ "@types/commander": { "version": "2.12.2", "resolved": "https://registry.npmjs.org/@types/commander/-/commander-2.12.2.tgz", - "integrity": "sha512-0QEFiR8ljcHp9bAbWxecjVRuAMr16ivPiGOw6KFQBVrVd0RQIcM3xKdRisH2EDWgVWujiYtHwhSkSUoAAGzH7Q==", + "integrity": "sha1-GDBBojhC1CgUePpdI8XKeOb9CK4=", "dev": true, "requires": { "commander": "2.3.0" @@ -31,7 +31,7 @@ "@types/fs-extra": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-4.0.5.tgz", - "integrity": "sha512-tIG0GpHum5IFb8Qze/cSv0w/0gNzHB+MUDftTQaxenx46z50g51/MPkNLssLz9+uZLzCDd35bT9qtWOTXZ21Gw==", + "integrity": "sha1-iqYDPA6HxlOwmmcRaGkWhktI7J4=", "dev": true, "requires": { "@types/node": "6.0.92" @@ -40,7 +40,7 @@ "@types/get-port": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@types/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha512-TiNg8R1kjDde5Pub9F9vCwZA/BNW9HeXP5b9j7Qucqncy/McfPZ6xze/EyBdXS5FhMIGN6Fx3vg75l5KHy3V1Q==", + "integrity": "sha1-+eChFEPMITNkcBherj37pEldKbw=", "dev": true }, "@types/iconv-lite": { @@ -55,37 +55,37 @@ "@types/lodash": { "version": "4.14.87", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.87.tgz", - "integrity": "sha512-AqRC+aEF4N0LuNHtcjKtvF9OTfqZI0iaBoe3dA6m/W+/YZJBZjBmW/QIZ8fBeXC6cnytSY9tBoFBqZ9uSCeVsw==", + "integrity": "sha1-Vfkhg7BIwsZEAq/kcvgzP04xmms=", "dev": true }, "@types/mocha": { "version": "2.2.44", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.44.tgz", - "integrity": "sha512-k2tWTQU8G4+iSMvqKi0Q9IIsWAp/n8xzdZS4Q4YVIltApoMA00wFBFdlJnmoaK1/z7B0Cy0yPe6GgXteSmdUNw==", + "integrity": "sha1-HUp5jlPzUhL9WtTQQFBiAXHNW14=", "dev": true }, "@types/node": { "version": "6.0.92", "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.92.tgz", - "integrity": "sha512-awEYSSTn7dauwVCYSx2CJaPTu0Z1Ht2oR1b2AD3CYao6ZRb+opb6EL43fzmD7eMFgMHzTBWSUzlWSD+S8xN0Nw==", + "integrity": "sha1-5/chrigncuEroleZaMANnM5CLF0=", "dev": true }, "@types/semver": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.4.0.tgz", - "integrity": "sha512-PBHCvO98hNec9A491vBbh0ZNDOVxccwKL1u2pm6fs9oDgm7SEnw0lEHqHfjsYryDxnE3zaf7LvERWEXjOp1hig==", + "integrity": "sha1-82WFNa9/H1AqzW2n2vQF/+sffuQ=", "dev": true }, "@types/sinon": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-2.3.7.tgz", - "integrity": "sha512-w+LjztaZbgZWgt/y/VMP5BUAWLtSyoIJhXyW279hehLPyubDoBNwvhcj3WaSptcekuKYeTCVxrq60rdLc6ImJA==", + "integrity": "sha1-6Swv7TKX6uB4140doDKyZ4i0r4Y=", "dev": true }, "@types/uuid": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.3.tgz", - "integrity": "sha512-5fRLCYhLtDb3hMWqQyH10qtF+Ud2JnNCXTCZ+9ktNdCcgslcuXkDTkFcJNk++MT29yDntDnlF1+jD+uVGumsbw==", + "integrity": "sha1-EhrOJl9Vac5A9PbQ/3ijOMcyp1Q=", "dev": true, "requires": { "@types/node": "6.0.92" @@ -100,7 +100,7 @@ "@types/xml2js": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.2.tgz", - "integrity": "sha512-8aKUBSj3oGcnuiBmDLm3BIk09RYg01mz9HlQ2u4aS17oJ25DxjQrEUVGFSBVNOfM45pQW4OjcBPplq6r/exJdA==", + "integrity": "sha1-pLhLOHn/1HEJU/2Syr/emopOhFY=", "dev": true, "requires": { "@types/node": "6.0.92" @@ -133,7 +133,7 @@ "anymatch": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "integrity": "sha1-VT3Lj5HjyImEXf26NMd3IbkLnXo=", "dev": true, "requires": { "micromatch": "2.3.11", @@ -163,7 +163,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", "dev": true }, "array-differ": { @@ -187,7 +187,7 @@ "array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "integrity": "sha1-42jqFfibxwaff/uJrsOmx9SsItQ=", "dev": true }, "array-union": { @@ -392,7 +392,7 @@ "chai-as-promised": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "integrity": "sha1-CGRdgl3rhpbuYXJdv1kMAS6wDKA=", "dev": true, "requires": { "check-error": "1.0.2" @@ -425,7 +425,6 @@ "requires": { "anymatch": "1.3.2", "async-each": "1.0.1", - "fsevents": "1.1.3", "glob-parent": "2.0.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -437,7 +436,7 @@ "ci-info": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.2.tgz", - "integrity": "sha512-uTGIPNx/nSpBdsF6xnseRXLLtfr9VLqkz8ZqHXr3Y7b6SftyRxBGjwMtJj1OhNbmlc1wZzLNAlAcvyIiE8a6ZA==", + "integrity": "sha1-A1YSWdtI0EdMi9yQ9bR7Botrv7Q=", "dev": true }, "clone": { @@ -478,7 +477,7 @@ "color-convert": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "integrity": "sha1-wSYRB66y8pTr/+ye2eytUppgl+0=", "dev": true, "requires": { "color-name": "1.1.3" @@ -514,7 +513,7 @@ "commandpost": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/commandpost/-/commandpost-1.2.1.tgz", - "integrity": "sha512-V1wzc+DTFsO96te2W/U+fKNRSOWtOwXhkkZH2WRLLbucrY+YrDNsRr4vtfSf83MUZVF3E6B4nwT30fqaTpzipQ==", + "integrity": "sha1-LpxMdQi53HBK/vqpHKuS7mBUzGg=", "dev": true }, "concat-map": { @@ -614,7 +613,7 @@ "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "integrity": "sha1-38lARACtHI/gI+faHfHBR8S0RN8=", "dev": true, "requires": { "type-detect": "4.0.5" @@ -697,7 +696,7 @@ "duplexify": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.1.tgz", - "integrity": "sha512-j5goxHTwVED1Fpe5hh3q9R93Kip0Bg2KVAt4f8CEYM3UEwYcPSvWbXaUQOzdX/HtiNomipv+gU7ASQPDbV7pGQ==", + "integrity": "sha1-ThUWvmiDi8kKSZlPCzmm5ZYL780=", "dev": true, "requires": { "end-of-stream": "1.4.0", @@ -733,7 +732,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", "dev": true, "requires": { "core-util-is": "1.0.2", @@ -748,7 +747,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -769,7 +768,7 @@ "editorconfig": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.0.tgz", - "integrity": "sha512-j7JBoj/bpNzvoTQylfRZSc85MlLNKWQiq5y6gwKhmqD2h1eZ+tH4AXbkhEJD468gjDna/XMx2YtSkCxBRX9OGg==", + "integrity": "sha1-tt1KC2ueds5I4Ga9wVOBrruIBP0=", "dev": true, "requires": { "@types/commander": "2.12.2", @@ -783,13 +782,13 @@ "commander": { "version": "2.12.2", "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz", - "integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==", + "integrity": "sha1-D1lGxCftnsDZGka7ne9T5UZQ5VU=", "dev": true }, "lru-cache": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "integrity": "sha1-Yi4y6CSItJJ5EUpPns9F581rulU=", "dev": true, "requires": { "pseudomap": "1.0.2", @@ -1083,910 +1082,6 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "fsevents": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", - "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", - "dev": true, - "optional": true, - "requires": { - "nan": "2.8.0", - "node-pre-gyp": "0.6.39" - }, - "dependencies": { - "abbrev": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "ajv": { - "version": "4.11.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "aproba": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.2.9" - } - }, - "asn1": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "assert-plus": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws4": { - "version": "1.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "balanced-match": { - "version": "0.4.2", - "bundled": true, - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "block-stream": { - "version": "0.0.9", - "bundled": true, - "dev": true, - "requires": { - "inherits": "2.0.3" - } - }, - "boom": { - "version": "2.10.1", - "bundled": true, - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "brace-expansion": { - "version": "1.1.7", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "0.4.2", - "concat-map": "0.0.1" - } - }, - "buffer-shims": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "caseless": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true - }, - "co": { - "version": "4.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "combined-stream": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "requires": { - "delayed-stream": "1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "cryptiles": { - "version": "2.0.5", - "bundled": true, - "dev": true, - "requires": { - "boom": "2.10.1" - } - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "debug": { - "version": "2.6.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.4.2", - "bundled": true, - "dev": true, - "optional": true - }, - "delayed-stream": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "ecc-jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "extend": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "extsprintf": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "bundled": true, - "dev": true, - "optional": true - }, - "form-data": { - "version": "2.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.15" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "fstream": { - "version": "1.0.11", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.1" - } - }, - "fstream-ignore": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fstream": "1.0.11", - "inherits": "2.0.3", - "minimatch": "3.0.4" - } - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "1.1.1", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" - } - }, - "getpass": { - "version": "0.1.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "bundled": true, - "dev": true - }, - "har-schema": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "har-validator": { - "version": "4.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "hawk": { - "version": "3.1.3", - "bundled": true, - "dev": true, - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "hoek": { - "version": "2.16.3", - "bundled": true, - "dev": true - }, - "http-signature": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.0", - "sshpk": "1.13.0" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.4", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "isstream": { - "version": "0.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "jodid25519": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "jsonify": { - "version": "0.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "jsprim": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.0.2", - "json-schema": "0.2.3", - "verror": "1.3.6" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "mime-db": { - "version": "1.27.0", - "bundled": true, - "dev": true - }, - "mime-types": { - "version": "2.1.15", - "bundled": true, - "dev": true, - "requires": { - "mime-db": "1.27.0" - } - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "node-pre-gyp": { - "version": "0.6.39", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "1.0.2", - "hawk": "3.1.3", - "mkdirp": "0.5.1", - "nopt": "4.0.1", - "npmlog": "4.1.0", - "rc": "1.2.1", - "request": "2.81.0", - "rimraf": "2.6.1", - "semver": "5.3.0", - "tar": "2.2.1", - "tar-pack": "3.4.0" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1.1.0", - "osenv": "0.1.4" - } - }, - "npmlog": { - "version": "4.1.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "oauth-sign": { - "version": "0.8.2", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "performance-now": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "1.0.7", - "bundled": true, - "dev": true - }, - "punycode": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true - }, - "qs": { - "version": "6.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.2.9", - "bundled": true, - "dev": true, - "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.1", - "util-deprecate": "1.0.2" - } - }, - "request": { - "version": "2.81.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.15", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.0.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", - "tunnel-agent": "0.6.0", - "uuid": "3.0.1" - } - }, - "rimraf": { - "version": "2.6.1", - "bundled": true, - "dev": true, - "requires": { - "glob": "7.1.2" - } - }, - "safe-buffer": { - "version": "5.0.1", - "bundled": true, - "dev": true - }, - "semver": { - "version": "5.3.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sntp": { - "version": "1.0.9", - "bundled": true, - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "sshpk": { - "version": "1.13.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jodid25519": "1.0.2", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, - "stringstream": { - "version": "0.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "2.2.1", - "bundled": true, - "dev": true, - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - }, - "tar-pack": { - "version": "3.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "2.6.8", - "fstream": "1.0.11", - "fstream-ignore": "1.0.5", - "once": "1.4.0", - "readable-stream": "2.2.9", - "rimraf": "2.6.1", - "tar": "2.2.1", - "uid-number": "0.0.6" - } - }, - "tough-cookie": { - "version": "2.3.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true, - "dev": true, - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "bundled": true, - "dev": true, - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "uuid": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "verror": { - "version": "1.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "extsprintf": "1.0.2" - } - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - } - } - }, "fstream": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", @@ -2317,7 +1412,7 @@ "gulp-filter": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-5.0.1.tgz", - "integrity": "sha512-5olRzAhFdXB2klCu1lnazP65aO9YdA/5WfC9VdInIc8PrUeDIoZfaA3Edb0yUBGhVdHv4eHKL9Fg5tUoEJ9z5A==", + "integrity": "sha1-XYf2YuMX5YOe92UOYg5skAj/ktA=", "dev": true, "requires": { "gulp-util": "3.0.8", @@ -2817,7 +1912,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", "dev": true, "requires": { "core-util-is": "1.0.2", @@ -2832,7 +1927,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -2908,7 +2003,7 @@ "gulp-typescript": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-3.2.3.tgz", - "integrity": "sha512-Np2sJXgtDUwIAoMtlJ9uXsVmpu1FWXlKZw164hLuo56uJa7qo5W2KZ0yAYiYH/HUsaz5L0O2toMOcLIokpFCPg==", + "integrity": "sha1-MtUquXuXxM4HDAQZ2wjqOvUU1yA=", "dev": true, "requires": { "gulp-util": "3.0.8", @@ -3032,7 +2127,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", "dev": true, "requires": { "core-util-is": "1.0.2", @@ -3047,7 +2142,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -3301,7 +2396,7 @@ "queue": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/queue/-/queue-4.4.2.tgz", - "integrity": "sha512-fSMRXbwhMwipcDZ08enW2vl+YDmAmhcNcr43sCJL8DIg+CFOsoRLG23ctxA+fwNk1w55SePSiS7oqQQSgQoVJQ==", + "integrity": "sha1-Wpcz2ai4vRs26TS8nFWribKOKcc=", "dev": true, "requires": { "inherits": "2.0.3" @@ -3310,7 +2405,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", "dev": true, "requires": { "core-util-is": "1.0.2", @@ -3331,7 +2426,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -3496,7 +2591,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", "dev": true, "requires": { "core-util-is": "1.0.2", @@ -3511,7 +2606,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -3560,7 +2655,7 @@ "commander": { "version": "2.12.2", "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz", - "integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==", + "integrity": "sha1-D1lGxCftnsDZGka7ne9T5UZQ5VU=", "dev": true } } @@ -3642,7 +2737,7 @@ "husky": { "version": "0.14.3", "resolved": "https://registry.npmjs.org/husky/-/husky-0.14.3.tgz", - "integrity": "sha512-e21wivqHpstpoiWA/Yi8eFti8E+sQDSS53cpJsPptPs295QTOQR0ZwnHo2TXy1XOpZFD9rPOd3NpmqTK6uMLJA==", + "integrity": "sha1-xp7XTi0neXaaF7qDmbVM4LY8EsM=", "dev": true, "requires": { "is-ci": "1.0.10", @@ -3667,7 +2762,7 @@ "iconv-lite": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + "integrity": "sha1-90aPYBNfXl2tM5nAqBvpoWA6CCs=" }, "indent-string": { "version": "2.1.0", @@ -3697,7 +2792,7 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=", "dev": true }, "interpret": { @@ -3709,7 +2804,7 @@ "inversify": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/inversify/-/inversify-4.5.2.tgz", - "integrity": "sha512-nyXPqxMVdJDCbeq+eWSq5wEYvm6jlBNkBS+rMAXdxlEibAIXuzXXNgvETsP0MVcsWc4bAwUMhqr6X/5sUzNrFw==" + "integrity": "sha1-8keP0UDzmINrLh/ug583WI8eaps=" }, "is": { "version": "3.2.1", @@ -3745,7 +2840,7 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", "dev": true }, "is-builtin-module": { @@ -3814,7 +2909,7 @@ "is-my-json-valid": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz", - "integrity": "sha512-ochPsqWS1WXj8ZnMIV0vnNXooaMhp7cyL4FMSIPKTtnV0Ha/T19G2b9kkhcNsabV9bxYkze7/aLZJb/bYuFduQ==", + "integrity": "sha1-WoRnd+LCYg0eaRBOXToDsfYIjxE=", "dev": true, "requires": { "generate-function": "2.0.0", @@ -3841,7 +2936,7 @@ "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", "dev": true, "requires": { "isobject": "3.0.1" @@ -4087,7 +3182,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", "dev": true, "requires": { "core-util-is": "1.0.2", @@ -4102,7 +3197,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -4586,7 +3681,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", "dev": true, "requires": { "core-util-is": "1.0.2", @@ -4601,7 +3696,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -4648,7 +3743,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "requires": { "brace-expansion": "1.1.8" } @@ -4758,14 +3853,7 @@ "named-js-regexp": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/named-js-regexp/-/named-js-regexp-1.3.3.tgz", - "integrity": "sha512-zIUAXzGQOp16VR0Ct89SDstU62hzAPBluNUrUrsdD7MNSRbm/vyqGhEnp+4hnsMjmX3C2wh1cbIEP0joKMFLxw==" - }, - "nan": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", - "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=", - "dev": true, - "optional": true + "integrity": "sha1-ousWVcdMuCITpPyCd337Z7iV2Mg=" }, "native-promise-only": { "version": "0.8.1", @@ -5122,7 +4210,7 @@ "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=", "dev": true, "requires": { "is-number": "3.0.0", @@ -5214,7 +4302,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", "dev": true, "requires": { "core-util-is": "1.0.2", @@ -5229,7 +4317,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -5264,7 +4352,7 @@ "regex-cache": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", "dev": true, "requires": { "is-equal-shallow": "0.1.3" @@ -5315,7 +4403,7 @@ "request": { "version": "2.83.0", "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", - "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", + "integrity": "sha1-ygtl2gLtYpNYh4COb1EDgQNOM1Y=", "dev": true, "requires": { "aws-sign2": "0.7.0", @@ -5381,7 +4469,7 @@ "boom": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "integrity": "sha1-XdnabuOl8wIHdDYpDLcX0/SlTgI=", "dev": true, "requires": { "hoek": "4.2.0" @@ -5413,7 +4501,7 @@ "hawk": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "integrity": "sha1-r02RTrBl+bXOTZ0RwcshJu7MMDg=", "dev": true, "requires": { "boom": "4.3.1", @@ -5425,7 +4513,7 @@ "hoek": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", - "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==", + "integrity": "sha1-ctnQdU9/4lyi0BrY+PmpRJqJUm0=", "dev": true }, "http-signature": { @@ -5442,13 +4530,13 @@ "qs": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "integrity": "sha1-NJzfbu+J7EXBLX1es/wMhwNDptg=", "dev": true }, "sntp": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "integrity": "sha1-LGzsFP7cIiJznK+bXD2F0cxaLMg=", "dev": true, "requires": { "hoek": "4.2.0" @@ -5474,7 +4562,7 @@ "resolve": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", - "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "integrity": "sha1-HwmsznlsmnYlefMbLBzEw83fnzY=", "dev": true, "requires": { "path-parse": "1.0.5" @@ -5499,7 +4587,7 @@ "rimraf": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=", "dev": true, "requires": { "glob": "7.1.2" @@ -5508,7 +4596,7 @@ "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "dev": true, "requires": { "fs.realpath": "1.0.0", @@ -5524,7 +4612,7 @@ "rxjs": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.3.tgz", - "integrity": "sha512-VWockSz7xmDveeZ7wv8RvdipGGZ1NmL/m4jnpvN9BH4x1fW/TPoD23yXh+qDkbWSlajXVVfLIbGmyxa94Ls84w==", + "integrity": "sha1-tiIn50uE9Od730QOULXuAaG8fc0=", "requires": { "symbol-observable": "1.1.0" } @@ -5532,24 +4620,24 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=", "dev": true }, "samsam": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", - "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", + "integrity": "sha1-jR2TUOJWItow3j5EumkrUiGrfFA=", "dev": true }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=" }, "semver": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" + "integrity": "sha1-4FnAnYVx8FQII3M0M1BdOi8AsY4=" }, "sequencify": { "version": "0.0.7", @@ -5578,7 +4666,7 @@ "sinon": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.4.1.tgz", - "integrity": "sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==", + "integrity": "sha1-Ah/WS1TLd9nS+w1Dze3652KcOjY=", "dev": true, "requires": { "diff": "3.4.0", @@ -5594,7 +4682,7 @@ "diff": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.4.0.tgz", - "integrity": "sha512-QpVuMTEoJMF7cKzi6bvWhRulU1fZqZnvyVQgNhPaxxuTYwyjn/j1v9falseQ/uXWwPnO56RBfwtg4h/EQXmucA==", + "integrity": "sha1-sdhVB9rzlkgo3lSzfQ1zumfdpWw=", "dev": true } } @@ -5623,7 +4711,7 @@ "source-map-support": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.0.tgz", - "integrity": "sha512-vUoN3I7fHQe0R/SJLKRdKYuEdRGogsviXFkHHo17AWaTGv17VLnxw+CFXvqy+y4ORZ3doWLQcxRYfwKrsd/H7Q==", + "integrity": "sha1-IBinrSvfj68mkeX92rJr7VorrKs=", "dev": true, "requires": { "source-map": "0.6.1" @@ -5632,7 +4720,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", "dev": true } } @@ -5727,7 +4815,7 @@ "streamfilter": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-1.0.6.tgz", - "integrity": "sha512-JM3zxd/lvOuo+EZJlZNYdQucfybA+Jr6jRtZwWlMAq1dAV0LIjZrqSNBBK62qCtflJ8rL/+cAnxy69CPhkTJNA==", + "integrity": "sha1-jQhxfKwHewg0yCYH0Pqbx3en/Lw=", "dev": true, "requires": { "readable-stream": "2.3.3" @@ -5742,7 +4830,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", "dev": true, "requires": { "core-util-is": "1.0.2", @@ -5757,7 +4845,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -5830,7 +4918,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", "dev": true, "requires": { "core-util-is": "1.0.2", @@ -5845,7 +4933,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -5880,7 +4968,7 @@ "symbol-observable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.1.0.tgz", - "integrity": "sha512-dQoid9tqQ+uotGhuTKEY11X4xhyYePVnqGSoSm3OGKh2E8LZ6RPULp1uXTctk33IeERlrRJYoVSBglsL05F5Uw==" + "integrity": "sha1-XGj9jVQRXZ37cqhHIFSSIujbmzI=" }, "tar": { "version": "2.2.1", @@ -5924,7 +5012,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", "dev": true, "requires": { "core-util-is": "1.0.2", @@ -5939,7 +5027,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -6007,7 +5095,7 @@ "tree-kill": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.0.tgz", - "integrity": "sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg==" + "integrity": "sha1-WEZ4Yje0I5AU8F2xVrZDIS1MbzY=" }, "trim-newlines": { "version": "1.0.0", @@ -6018,7 +5106,7 @@ "tslib": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.8.0.tgz", - "integrity": "sha512-ymKWWZJST0/CkgduC2qkzjMOWr4bouhuURNXCn/inEX0L57BnRG6FhX76o7FOnsjHazCjfU2LKeSrlS2sIKQJg==", + "integrity": "sha1-3GBOutZLy/aW1hPabJVKoOfqHrY=", "dev": true }, "tslint": { @@ -6043,7 +5131,7 @@ "ansi-styles": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "integrity": "sha1-wVm41b4PnlpvNG2rlPFs4CIWG4g=", "dev": true, "requires": { "color-convert": "1.9.1" @@ -6052,7 +5140,7 @@ "chalk": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "integrity": "sha1-tepI78nBeT3MybR2fJORTT8tUro=", "dev": true, "requires": { "ansi-styles": "3.2.0", @@ -6063,19 +5151,19 @@ "commander": { "version": "2.12.2", "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz", - "integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==", + "integrity": "sha1-D1lGxCftnsDZGka7ne9T5UZQ5VU=", "dev": true }, "diff": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.4.0.tgz", - "integrity": "sha512-QpVuMTEoJMF7cKzi6bvWhRulU1fZqZnvyVQgNhPaxxuTYwyjn/j1v9falseQ/uXWwPnO56RBfwtg4h/EQXmucA==", + "integrity": "sha1-sdhVB9rzlkgo3lSzfQ1zumfdpWw=", "dev": true }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "dev": true, "requires": { "fs.realpath": "1.0.0", @@ -6136,7 +5224,7 @@ "tsutils": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.13.0.tgz", - "integrity": "sha512-FuWzNJbMsp3gcZMbI3b5DomhW4Ia41vMxjN63nKWI0t7f+I3UmHfRl0TrXJTwI2LUduDG+eR1Mksp3pvtlyCFQ==", + "integrity": "sha1-D1K2qrvEIW5yeWtm2wKMbPFz4UQ=", "dev": true, "requires": { "tslib": "1.8.0" @@ -6158,7 +5246,7 @@ "type-detect": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.5.tgz", - "integrity": "sha512-N9IvkQslUGYGC24RkJk1ba99foK6TkwC2FHAEBlQFBP0RxQZS8ZpJuAZcwiY/w9ZJHFQb1aOXBI60OdxhTrwEQ==", + "integrity": "sha1-1w5byB223io4G8rKDG4MvcdjXeI=", "dev": true }, "typescript": { @@ -6175,7 +5263,7 @@ "typescript-formatter": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/typescript-formatter/-/typescript-formatter-6.1.0.tgz", - "integrity": "sha512-o2TJSBDSdua8yl8gXNJonM4jr4f7ZuLhsbG7zMG80iJluhE8MWI0R/mPzA3in3omwAJwkcbkc5Xxm74io/J7WA==", + "integrity": "sha1-RCWsK6uKrqmgQlHAePR6t6AgLxM=", "dev": true, "requires": { "commandpost": "1.2.1", @@ -6212,7 +5300,7 @@ "url-parse": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.2.0.tgz", - "integrity": "sha512-DT1XbYAfmQP65M/mE6OALxmXzZ/z1+e5zk2TcSKe/KiYbNGZxgtttzC0mR/sjopbpOXcbniq7eIKmocJnUWlEw==", + "integrity": "sha1-OhnoqqbQI93SfcxEy0/I9/7COYY=", "dev": true, "requires": { "querystringify": "1.0.0", @@ -6234,7 +5322,7 @@ "uuid": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", + "integrity": "sha1-PdPT55Crwk17DToDT/q6vijrvAQ=", "dev": true }, "v8flags": { @@ -6446,7 +5534,7 @@ "vscode": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.9.tgz", - "integrity": "sha512-xhSh410NntYViiBYIPimAwF0U6NZgORtqQenlpTI/qKBGwHMtZSCJcL3/JIhfCw43HBXqPg59kA947OT3UvZKQ==", + "integrity": "sha1-F87oj+YozQtTzNZ9PGeDyGY3sHQ=", "dev": true, "requires": { "glob": "7.1.2", @@ -6468,13 +5556,13 @@ "commander": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "integrity": "sha1-FXFS/R56bI2YpbcVzzdt+SgARWM=", "dev": true }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "integrity": "sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE=", "dev": true, "requires": { "ms": "2.0.0" @@ -6483,13 +5571,13 @@ "diff": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", - "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "integrity": "sha1-qoVnpu7QPFMfyJ0/cRzQ5SWd7HU=", "dev": true }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "dev": true, "requires": { "fs.realpath": "1.0.0", @@ -6503,13 +5591,13 @@ "growl": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", - "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "integrity": "sha1-GSa6kM8+3+KttJJ/WIC8IsZseQ8=", "dev": true }, "mocha": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.0.1.tgz", - "integrity": "sha512-evDmhkoA+cBNiQQQdSKZa2b9+W2mpLoj50367lhy+Klnx9OV8XlCIhigUnn1gaTFLQCa0kdNhEGDr0hCXOQFDw==", + "integrity": "sha1-Cu5alc9ppGGIIPXlH6MXFxF9rxs=", "dev": true, "requires": { "browser-stdout": "1.3.0", @@ -6533,7 +5621,7 @@ "supports-color": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", - "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "integrity": "sha1-iD992rwWUUKyphQn8zUt7RldGj4=", "dev": true, "requires": { "has-flag": "2.0.0" @@ -6614,7 +5702,7 @@ "which": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=", "dev": true, "requires": { "isexe": "2.0.0" @@ -6634,7 +5722,7 @@ "xml2js": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "integrity": "sha1-aGwg8hMgnpSr8NG88e+qKRx4J6c=", "requires": { "sax": "1.2.4", "xmlbuilder": "9.0.4" diff --git a/src/client/jedi/parsers/CompletionParser.ts b/src/client/jedi/parsers/CompletionParser.ts deleted file mode 100644 index eedc47842730..000000000000 --- a/src/client/jedi/parsers/CompletionParser.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as proxy from '../../providers/jediProxy'; -import { extractSignatureAndDocumentation } from '../../providers/jediHelpers'; -import { PythonSettings } from '../../common/configSettings'; -import { CompletionItem, SymbolKind, SnippetString, Uri } from 'vscode'; - -export class CompletionParser { - public static parse(data: proxy.ICompletionResult, resource: Uri): CompletionItem[] { - if (!data || data.items.length === 0) { - return []; - } - return data.items.map(item => { - const sigAndDocs = extractSignatureAndDocumentation(item); - let completionItem = new CompletionItem(item.text); - completionItem.kind = item.type; - completionItem.documentation = sigAndDocs[1].length === 0 ? item.description : sigAndDocs[1]; - completionItem.detail = sigAndDocs[0].split(/\r?\n/).join(''); - if (PythonSettings.getInstance(resource).autoComplete.addBrackets === true && - (item.kind === SymbolKind.Function || item.kind === SymbolKind.Method)) { - completionItem.insertText = new SnippetString(item.text).appendText("(").appendTabstop().appendText(")"); - } - - // ensure the built in memebers are at the bottom - completionItem.sortText = (completionItem.label.startsWith('__') ? 'z' : (completionItem.label.startsWith('_') ? 'y' : '__')) + completionItem.label; - return completionItem; - }); - } -} \ No newline at end of file diff --git a/src/client/jedi/parsers/HoverParser.ts b/src/client/jedi/parsers/HoverParser.ts deleted file mode 100644 index 86bbf680fdef..000000000000 --- a/src/client/jedi/parsers/HoverParser.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Hover, SymbolKind } from 'vscode'; -import * as proxy from '../../providers/jediProxy'; -import { highlightCode } from '../../providers/jediHelpers'; -import { EOL } from 'os'; -export class HoverParser { - public static parse(data: proxy.IHoverResult, currentWord: string): Hover { - if (!data || !Array.isArray(data.items) || data.items.length === 0) { - return new Hover([]); - } - - let results = []; - let capturedInfo: string[] = []; - data.items.forEach(item => { - let { signature } = item; - switch (item.kind) { - case SymbolKind.Constructor: - case SymbolKind.Function: - case SymbolKind.Method: { - signature = 'def ' + signature; - break; - } - case SymbolKind.Class: { - signature = 'class ' + signature; - break; - } - default: { - signature = typeof item.text === 'string' && item.text.length > 0 ? item.text : currentWord; - } - } - if (item.docstring) { - let lines = item.docstring.split(/\r?\n/); - // If the docstring starts with the signature, then remove those lines from the docstring - if (lines.length > 0 && item.signature.indexOf(lines[0]) === 0) { - lines.shift(); - let endIndex = lines.findIndex(line => item.signature.endsWith(line)); - if (endIndex >= 0) { - lines = lines.filter((line, index) => index > endIndex); - } - } - if (lines.length > 0 && item.signature.startsWith(currentWord) && lines[0].startsWith(currentWord) && lines[0].endsWith(')')) { - lines.shift(); - } - let descriptionWithHighlightedCode = highlightCode(lines.join(EOL)); - let hoverInfo = ['```python', signature, '```', descriptionWithHighlightedCode].join(EOL); - let key = signature + lines.join(''); - // Sometimes we have duplicate documentation, one with a period at the end - if (capturedInfo.indexOf(key) >= 0 || capturedInfo.indexOf(key + '.') >= 0) { - return; - } - capturedInfo.push(key); - capturedInfo.push(key + '.'); - results.push(hoverInfo); - return; - } - if (item.description) { - let descriptionWithHighlightedCode = highlightCode(item.description); - let hoverInfo = '```python' + EOL + signature + EOL + '```' + EOL + descriptionWithHighlightedCode; - let lines = item.description.split(EOL); - let key = signature + lines.join(''); - // Sometimes we have duplicate documentation, one with a period at the end - if (capturedInfo.indexOf(key) >= 0 || capturedInfo.indexOf(key + '.') >= 0) { - return; - } - capturedInfo.push(key); - capturedInfo.push(key + '.'); - results.push(hoverInfo); - } - }); - return new Hover(results); - } -} \ No newline at end of file diff --git a/src/client/providers/completionProvider.ts b/src/client/providers/completionProvider.ts index 8ed8c6f16f10..442c90b96651 100644 --- a/src/client/providers/completionProvider.ts +++ b/src/client/providers/completionProvider.ts @@ -1,5 +1,3 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. 'use strict'; import * as vscode from 'vscode'; @@ -29,7 +27,15 @@ export class PythonCompletionItemProvider implements vscode.CompletionItemProvid } public async resolveCompletionItem(item: vscode.CompletionItem, token: vscode.CancellationToken): Promise { - item.documentation = await this.completionSource.getDocumentation(item, token); + if (!item.documentation) { + const itemInfos = await this.completionSource.getDocumentation(item, token); + if (itemInfos && itemInfos.length > 0) { + // Only filling documentation since tooltip already contains + // all the necessary information and is colorized via markdown. + item.documentation = itemInfos[0].tooltip; + item.detail = ''; + } + } return item; } } diff --git a/src/client/providers/completionSource.ts b/src/client/providers/completionSource.ts index 6a69e4bb5f98..58a0a48ae835 100644 --- a/src/client/providers/completionSource.ts +++ b/src/client/providers/completionSource.ts @@ -7,7 +7,7 @@ import { PythonSettings } from '../common/configSettings'; import { Tokenizer } from '../language/tokenizer'; import { TokenType } from '../language/types'; import { JediFactory } from '../languageServices/jediProxyFactory'; -import { HoverSource } from './hoverSource'; +import { ItemInfoSource, LanguageItemInfo } from './itemInfoSource'; import * as proxy from './jediProxy'; class DocumentPosition { @@ -26,11 +26,11 @@ class DocumentPosition { export class CompletionSource { private jediFactory: JediFactory; - private hoverSource: HoverSource; + private itemInfoSource: ItemInfoSource; constructor(jediFactory: JediFactory) { this.jediFactory = jediFactory; - this.hoverSource = new HoverSource(jediFactory); + this.itemInfoSource = new ItemInfoSource(jediFactory); } public async getVsCodeCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) @@ -42,10 +42,10 @@ export class CompletionSource { return this.toVsCodeCompletions(new DocumentPosition(document, position), result, document.uri); } - public async getDocumentation(completionItem: vscode.CompletionItem, token: vscode.CancellationToken): Promise { + public async getDocumentation(completionItem: vscode.CompletionItem, token: vscode.CancellationToken): Promise { const documentPosition = DocumentPosition.fromObject(completionItem); if (documentPosition === undefined) { - return ''; + return; } // Supply hover source with simulated document text where item in question was 'already typed'. @@ -62,11 +62,7 @@ export class CompletionSource { const sourceText = `${document.getText(leadingRange)}${itemString}`; const range = new vscode.Range(leadingRange.end, leadingRange.end.translate(0, itemString.length)); - const hoverStrings = await this.hoverSource.getHoverStringsFromText(document.uri, document.fileName, range, sourceText, token); - if (!hoverStrings || hoverStrings.length === 0) { - return ''; - } - return hoverStrings.join('\n'); + return await this.itemInfoSource.getItemInfoFromText(document.uri, document.fileName, range, sourceText, token); } private async getCompletionResult(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) diff --git a/src/client/providers/hoverProvider.ts b/src/client/providers/hoverProvider.ts index 4bd3380d9353..745300986189 100644 --- a/src/client/providers/hoverProvider.ts +++ b/src/client/providers/hoverProvider.ts @@ -4,18 +4,21 @@ import * as vscode from 'vscode'; import { JediFactory } from '../languageServices/jediProxyFactory'; import { captureTelemetry } from '../telemetry'; import { HOVER_DEFINITION } from '../telemetry/constants'; -import { HoverSource } from './hoverSource'; +import { ItemInfoSource } from './itemInfoSource'; export class PythonHoverProvider implements vscode.HoverProvider { - private hoverSource: HoverSource; + private itemInfoSource: ItemInfoSource; constructor(jediFactory: JediFactory) { - this.hoverSource = new HoverSource(jediFactory); + this.itemInfoSource = new ItemInfoSource(jediFactory); } @captureTelemetry(HOVER_DEFINITION) public async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) : Promise { - return this.hoverSource.getVsCodeHover(document, position, token); - } + const itemInfos = await this.itemInfoSource.getItemInfoFromDocument(document, position, token); + if (itemInfos) { + return new vscode.Hover(itemInfos.map(item => item.tooltip)); + } + } } diff --git a/src/client/providers/hoverSource.ts b/src/client/providers/itemInfoSource.ts similarity index 50% rename from src/client/providers/hoverSource.ts rename to src/client/providers/itemInfoSource.ts index 522995cea678..a3616bbfe5db 100644 --- a/src/client/providers/hoverSource.ts +++ b/src/client/providers/itemInfoSource.ts @@ -5,50 +5,43 @@ import { EOL } from 'os'; import * as vscode from 'vscode'; import { JediFactory } from '../languageServices/jediProxyFactory'; -import { highlightCode } from './jediHelpers'; import * as proxy from './jediProxy'; -export class HoverSource { - constructor(private jediFactory: JediFactory) { } +export class LanguageItemInfo { + constructor( + public tooltip: vscode.MarkdownString, + public description: string) { } +} - public async getVsCodeHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) - : Promise { - const strings = await this.getHoverStringsFromDocument(document, position, token); - if (!strings) { - return; - } - return new vscode.Hover(strings); - } +export class ItemInfoSource { + constructor(private jediFactory: JediFactory) { } - // tslint:disable-next-line:no-any - public async getHoverStringsFromText(documentUri: vscode.Uri, fileName: string, range: vscode.Range, sourceText: string, token: vscode.CancellationToken): Promise { + public async getItemInfoFromText(documentUri: vscode.Uri, fileName: string, range: vscode.Range, sourceText: string, token: vscode.CancellationToken) + : Promise { const result = await this.getHoverResultFromTextRange(documentUri, fileName, range, sourceText, token); if (!result || !result.items.length) { - return []; + return; } - return this.getHoverStringsFromResult(result, '', false); + return this.getItemInfoFromHoverResult(result, ''); } - // tslint:disable-next-line:no-any - private async getHoverStringsFromDocument(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { + public async getItemInfoFromDocument(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) + : Promise { const range = document.getWordRangeAtPosition(position); if (!range || range.isEmpty) { - return []; + return; } const result = await this.getHoverResultFromDocument(document, position, token); if (!result || !result.items.length) { - return []; + return; } const word = document.getText(range); - return this.getHoverStringsFromResult(result, word, true); + return this.getItemInfoFromHoverResult(result, word); } private async getHoverResultFromDocument(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) : Promise { - if (document.lineAt(position.line).text.match(/^\s*\/\//)) { - return; - } - if (position.character <= 0) { + if (position.character <= 0 || document.lineAt(position.line).text.match(/^\s*\/\//)) { return; } const range = document.getWordRangeAtPosition(position); @@ -84,10 +77,10 @@ export class HoverSource { return await this.jediFactory.getJediProxyHandler(documentUri).sendCommand(cmd, token); } - // tslint:disable-next-line:no-any - private getHoverStringsFromResult(data: proxy.IHoverResult, currentWord: string, markedStringInfo: boolean): any[] { - const results: string[] = []; + private getItemInfoFromHoverResult(data: proxy.IHoverResult, currentWord: string): LanguageItemInfo[] { + const infos: LanguageItemInfo[] = []; const capturedInfo: string[] = []; + data.items.forEach(item => { let { signature } = item; switch (item.kind) { @@ -118,34 +111,98 @@ export class HoverSource { if (lines.length > 0 && item.signature.startsWith(currentWord) && lines[0].startsWith(currentWord) && lines[0].endsWith(')')) { lines.shift(); } - const descriptionWithHighlightedCode = highlightCode(lines.join(EOL)); - const hoverInfo = markedStringInfo ? ['```python', signature, '```', descriptionWithHighlightedCode].join(EOL) : descriptionWithHighlightedCode; + const description = lines.join(EOL); + const descriptionWithHighlightedCode = this.highlightCode(description); + const tooltip = new vscode.MarkdownString(['```python', signature, '```', descriptionWithHighlightedCode].join(EOL)); + infos.push(new LanguageItemInfo(tooltip, description)); + const key = signature + lines.join(''); // Sometimes we have duplicate documentation, one with a period at the end. if (capturedInfo.indexOf(key) >= 0 || capturedInfo.indexOf(`${key}.`) >= 0) { return; } capturedInfo.push(key); - capturedInfo.push(`${key}.`); - results.push(hoverInfo); + capturedInfo.push(`${key}.`); return; } if (item.description) { - const descriptionWithHighlightedCode = highlightCode(item.description); + const descriptionWithHighlightedCode = this.highlightCode(item.description); // tslint:disable-next-line:prefer-template - const hoverInfo = '```python' + `${EOL}${signature}${EOL}` + '```' + `${EOL}${descriptionWithHighlightedCode}`; + const tooltip = new vscode.MarkdownString('```python' + `${EOL}${signature}${EOL}` + '```' + `${EOL}${descriptionWithHighlightedCode}`); + infos.push(new LanguageItemInfo(tooltip, item.description)); + const lines = item.description.split(EOL); const key = signature + lines.join(''); // Sometimes we have duplicate documentation, one with a period at the end. if (capturedInfo.indexOf(key) >= 0 || capturedInfo.indexOf(`${key}.`) >= 0) { return; } + capturedInfo.push(key); capturedInfo.push(`${key}.`); - results.push(hoverInfo); + return; } }); - return results; + return infos; + } + + private highlightCode(docstring: string): string { + /********** + * + * Magic. Do not touch. [What is the best comment in source code](https://stackoverflow.com/a/185106) + * + * This method uses several regexs to 'translate' reStructruedText syntax (Python doc syntax) to Markdown syntax. + * + * Let's just keep it unchanged unless a better solution becomes possible. + * + **********/ + // Add 2 line break before and after docstring (used to match a blank line) + docstring = EOL + EOL + docstring.trim() + EOL + EOL; + // Section title -> heading level 2 + docstring = docstring.replace(/(.+\r?\n)[-=]+\r?\n/g, `## $1${EOL}`); + // Directives: '.. directive::' -> '**directive**' + docstring = docstring.replace(/\.\. (.*)::/g, '**$1**'); + // Pattern of 'var : description' + const paramLinePattern = '[\\*\\w_]+ ?:[^:\r\n]+'; + // Add new line after and before param line + docstring = docstring.replace(new RegExp(`(${EOL + paramLinePattern})`, 'g'), `$1${EOL}`); + docstring = docstring.replace(new RegExp(`(${EOL + paramLinePattern + EOL})`, 'g'), `${EOL}$1`); + // 'var : description' -> '`var` description' + docstring = docstring.replace(/\r?\n([\*\w]+) ?: ?([^:\r\n]+\r?\n)/g, `${EOL}\`$1\` $2`); + // Doctest blocks: begin with `>>>` and end with blank line + // tslint:disable-next-line:prefer-template + docstring = docstring.replace(/(>>>[\w\W]+?\r?\n)\r?\n/g, `${'```python' + EOL}$1${'```' + EOL + EOL}`); + // Literal blocks: begin with `::` (literal blocks are indented or quoted; for simplicity, we end literal blocks with blank line) + // tslint:disable-next-line:prefer-template + docstring = docstring.replace(/(\r?\n[^\.]*)::\r?\n\r?\n([\w\W]+?\r?\n)\r?\n/g, `$1${EOL + '```' + EOL}$2${'```' + EOL + EOL}`); + // Remove indentation in Field lists and Literal blocks + let inCodeBlock = false; + let codeIndentation = 0; + const lines = docstring.split(/\r?\n/); + for (let i = 0; i < lines.length; i += 1) { + const line = lines[i]; + if (line.startsWith('```')) { + inCodeBlock = !inCodeBlock; + if (inCodeBlock) { + codeIndentation = lines[i + 1].match(/^ */)[0].length; + } + continue; + } + if (!inCodeBlock) { + lines[i] = line.replace(/^ {4,8}/, ''); + // Field lists: ':field:' -> '**field**' + lines[i] = lines[i].replace(/:(.+?):/g, '**$1** '); + } else { + if (codeIndentation !== 0) { + lines[i] = line.substring(codeIndentation); + } + } + } + docstring = lines.join(EOL); + // Grid Tables + docstring = docstring.replace(/\r?\n[\+-]+\r?\n/g, EOL); + docstring = docstring.replace(/\r?\n[\+=]+\r?\n/g, s => s.replace(/\+/g, '|').replace(/=/g, '-')); + return docstring.trim(); } } diff --git a/src/client/providers/jediHelpers.ts b/src/client/providers/jediHelpers.ts deleted file mode 100644 index 8e6cc78132fa..000000000000 --- a/src/client/providers/jediHelpers.ts +++ /dev/null @@ -1,112 +0,0 @@ -'use strict'; - -import { EOL } from 'os'; -import * as vscode from 'vscode'; -import * as proxy from './jediProxy'; - -export function extractSignatureAndDocumentation(definition: proxy.IAutoCompleteItem, highlight: boolean = false): [string, string] { - // Somtimes the signature of the function, class (whatever) is broken into multiple lines - // Here's an example - // ```python - // def __init__(self, group=None, target=None, name=None, - // args=(), kwargs=None, verbose=None): - // """This constructor should always be called with keyword arguments. Arguments are: - - // *group* should be None; reserved for future extension when a ThreadGroup - // class is implemented. - /// """ - /// ``` - const txt = definition.description || definition.text; - const rawDocString = typeof definition.raw_docstring === 'string' ? definition.raw_docstring.trim() : ''; - const firstLineOfRawDocString = rawDocString.length > 0 ? rawDocString.split(/\r?\n/)[0] : ''; - let lines = txt.split(/\r?\n/); - const startIndexOfDocString = firstLineOfRawDocString === '' ? -1 : lines.findIndex(line => line.indexOf(firstLineOfRawDocString) === 0); - - const signatureLines = startIndexOfDocString === -1 ? [lines.shift()] : lines.splice(0, startIndexOfDocString); - let signature = signatureLines.filter(line => line.trim().length > 0).join(EOL); - - // tslint:disable-next-line:switch-default - switch (definition.type) { - case vscode.CompletionItemKind.Constructor: - case vscode.CompletionItemKind.Function: - case vscode.CompletionItemKind.Method: { - signature = `def ${signature}`; - break; - } - case vscode.CompletionItemKind.Class: { - signature = `class ${signature}`; - break; - } - } - - // check if we have any sample code in the documentation - if (highlight) { - lines = lines.map(line => { - if (line.trim().startsWith('>>> ')) { - // tslint:disable-next-line:prefer-template - return '```python\n' + line.substring(4).trim() + '\n```'; - } - return line; - }); - } - return [signature, lines.join(EOL).trim().replace(/^\s+|\s+$/g, '').trim()]; -} - -export function highlightCode(docstring: string): string { - /********** - * - * Magic. Do not touch. [What is the best comment in source code](https://stackoverflow.com/a/185106) - * - * This method uses several regexs to 'translate' reStructruedText syntax (Python doc syntax) to Markdown syntax. - * - * Let's just keep it unchanged unless a better solution becomes possible. - * - **********/ - // Add 2 line break before and after docstring (used to match a blank line) - docstring = EOL + EOL + docstring.trim() + EOL + EOL; - // Section title -> heading level 2 - docstring = docstring.replace(/(.+\r?\n)[-=]+\r?\n/g, `## $1${EOL}`); - // Directives: '.. directive::' -> '**directive**' - docstring = docstring.replace(/\.\. (.*)::/g, '**$1**'); - // Pattern of 'var : description' - const paramLinePattern = '[\\*\\w_]+ ?:[^:\r\n]+'; - // Add new line after and before param line - docstring = docstring.replace(new RegExp(`(${EOL + paramLinePattern})`, 'g'), `$1${EOL}`); - docstring = docstring.replace(new RegExp(`(${EOL + paramLinePattern + EOL})`, 'g'), `${EOL}$1`); - // 'var : description' -> '`var` description' - docstring = docstring.replace(/\r?\n([\*\w]+) ?: ?([^:\r\n]+\r?\n)/g, `${EOL}\`$1\` $2`); - // Doctest blocks: begin with `>>>` and end with blank line - // tslint:disable-next-line:prefer-template - docstring = docstring.replace(/(>>>[\w\W]+?\r?\n)\r?\n/g, `${'```python' + EOL}$1${'```' + EOL + EOL}`); - // Literal blocks: begin with `::` (literal blocks are indented or quoted; for simplicity, we end literal blocks with blank line) - // tslint:disable-next-line:prefer-template - docstring = docstring.replace(/(\r?\n[^\.]*)::\r?\n\r?\n([\w\W]+?\r?\n)\r?\n/g, `$1${EOL + '```' + EOL}$2${'```' + EOL + EOL}`); - // Remove indentation in Field lists and Literal blocks - let inCodeBlock = false; - let codeIndentation = 0; - const lines = docstring.split(/\r?\n/); - for (let i = 0; i < lines.length; i += 1) { - const line = lines[i]; - if (line.startsWith('```')) { - inCodeBlock = !inCodeBlock; - if (inCodeBlock) { - codeIndentation = lines[i + 1].match(/^ */)[0].length; - } - continue; - } - if (!inCodeBlock) { - lines[i] = line.replace(/^ {4,8}/, ''); - // Field lists: ':field:' -> '**field**' - lines[i] = lines[i].replace(/:(.+?):/g, '**$1** '); - } else { - if (codeIndentation !== 0) { - lines[i] = line.substring(codeIndentation); - } - } - } - docstring = lines.join(EOL); - // Grid Tables - docstring = docstring.replace(/\r?\n[\+-]+\r?\n/g, EOL); - docstring = docstring.replace(/\r?\n[\+=]+\r?\n/g, s => s.replace(/\+/g, '|').replace(/=/g, '-')); - return docstring.trim(); -} diff --git a/src/test/autocomplete/base.test.ts b/src/test/autocomplete/base.test.ts index 56f40efb1036..5db1a0a4d420 100644 --- a/src/test/autocomplete/base.test.ts +++ b/src/test/autocomplete/base.test.ts @@ -118,8 +118,11 @@ suite('Autocomplete', () => { await vscode.window.showTextDocument(textDocument); const position = new vscode.Position(10, 9); const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list.items.filter(item => item.label === 'sleep').length, 0, 'sleep not found'); - assert.notEqual(list.items.filter(item => item.documentation.toString().startsWith('Delay execution for a given number of seconds. The argument may be')).length, 0, 'Documentation incorrect'); + + const items = list.items.filter(item => item.label === 'sleep'); + assert.notEqual(items.length, 0, 'sleep not found'); + + checkDocumentation(items[0].documentation, 'Delay execution for a given number of seconds. The argument may be'); }); test('For custom class', done => { @@ -153,8 +156,9 @@ suite('Autocomplete', () => { }).then(list => { const items = list.items.filter(item => item.label === 'bar'); assert.equal(items.length, 1, 'bar not found'); - const documentation = `说明 - keep this line, it works${EOL}delete following line, it works${EOL}如果存在需要等待审批或正在执行的任务,将不刷新页面`; - assert.equal(items[0].documentation, documentation, 'unicode documentation is incorrect'); + + const expected = `说明 - keep this line, it works${EOL}delete following line, it works${EOL}如果存在需要等待审批或正在执行的任务,将不刷新页面`; + checkDocumentation(items[0].documentation, expected); }).then(done, done); }); @@ -172,12 +176,13 @@ suite('Autocomplete', () => { }).then(list => { let items = list.items.filter(item => item.label === 'Foo'); assert.equal(items.length, 1, 'Foo not found'); - assert.equal(items[0].documentation, '说明', 'Foo unicode documentation is incorrect'); + checkDocumentation(items[0].documentation, '说明'); items = list.items.filter(item => item.label === 'showMessage'); assert.equal(items.length, 1, 'showMessage not found'); - const documentation = `Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи. ${EOL}Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.`; - assert.equal(items[0].documentation, documentation, 'showMessage unicode documentation is incorrect'); + + const expected = `Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи. ${EOL}Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.`; + checkDocumentation(items[0].documentation, expected); }).then(done, done); }); @@ -208,3 +213,11 @@ suite('Autocomplete', () => { } }); }); + +function checkDocumentation(itemDoc: any, expectedContains: string): void { + const documentation = itemDoc as vscode.MarkdownString; + assert.notEqual(documentation, null, "Documentation is not MarkdownString"); + + assert.equal(documentation.value.indexOf(expectedContains) > 0, true, 'Documentation incorrect'); + assert.equal(documentation.value.indexOf('```python'), 0, 'Documentation is not colorized as Python'); +} From e193805a0e36ef5c405a88c742b568fc97460b5d Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Mon, 11 Dec 2017 13:19:51 -0800 Subject: [PATCH 23/24] Provide details string --- src/client/common/helpers.ts | 1 + .../languageServices/jediProxyFactory.ts | 5 +-- src/client/providers/completionProvider.ts | 8 ++-- src/client/providers/itemInfoSource.ts | 38 ++++++++++++++----- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/client/common/helpers.ts b/src/client/common/helpers.ts index 345d55fdda93..6c1ed9bd910d 100644 --- a/src/client/common/helpers.ts +++ b/src/client/common/helpers.ts @@ -1,3 +1,4 @@ +/* tslint:disable:no-require-imports no-var-requires */ const tmp = require('tmp'); export function isNotInstalledError(error: Error): boolean { diff --git a/src/client/languageServices/jediProxyFactory.ts b/src/client/languageServices/jediProxyFactory.ts index 9af0b012d648..ea1745b59b3c 100644 --- a/src/client/languageServices/jediProxyFactory.ts +++ b/src/client/languageServices/jediProxyFactory.ts @@ -1,5 +1,5 @@ import { Disposable, Uri, workspace } from 'vscode'; -import { JediProxy, JediProxyHandler, ICommandResult } from '../providers/jediProxy'; +import { ICommandResult, JediProxy, JediProxyHandler } from '../providers/jediProxy'; export class JediFactory implements Disposable { private disposables: Disposable[]; @@ -20,8 +20,7 @@ export class JediFactory implements Disposable { if (!workspacePath) { if (Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 0) { workspacePath = workspace.workspaceFolders[0].uri.fsPath; - } - else { + } else { workspacePath = __dirname; } } diff --git a/src/client/providers/completionProvider.ts b/src/client/providers/completionProvider.ts index 442c90b96651..7f02343f60d6 100644 --- a/src/client/providers/completionProvider.ts +++ b/src/client/providers/completionProvider.ts @@ -30,12 +30,10 @@ export class PythonCompletionItemProvider implements vscode.CompletionItemProvid if (!item.documentation) { const itemInfos = await this.completionSource.getDocumentation(item, token); if (itemInfos && itemInfos.length > 0) { - // Only filling documentation since tooltip already contains - // all the necessary information and is colorized via markdown. - item.documentation = itemInfos[0].tooltip; - item.detail = ''; + item.detail = itemInfos[0].detail; + item.documentation = itemInfos[0].documentation; } - } + } return item; } } diff --git a/src/client/providers/itemInfoSource.ts b/src/client/providers/itemInfoSource.ts index a3616bbfe5db..15d579270ec2 100644 --- a/src/client/providers/itemInfoSource.ts +++ b/src/client/providers/itemInfoSource.ts @@ -6,11 +6,13 @@ import { EOL } from 'os'; import * as vscode from 'vscode'; import { JediFactory } from '../languageServices/jediProxyFactory'; import * as proxy from './jediProxy'; +import { IHoverItem } from './jediProxy'; export class LanguageItemInfo { constructor( public tooltip: vscode.MarkdownString, - public description: string) { } + public detail: string, + public documentation: vscode.MarkdownString) { } } export class ItemInfoSource { @@ -111,10 +113,11 @@ export class ItemInfoSource { if (lines.length > 0 && item.signature.startsWith(currentWord) && lines[0].startsWith(currentWord) && lines[0].endsWith(')')) { lines.shift(); } - const description = lines.join(EOL); - const descriptionWithHighlightedCode = this.highlightCode(description); - const tooltip = new vscode.MarkdownString(['```python', signature, '```', descriptionWithHighlightedCode].join(EOL)); - infos.push(new LanguageItemInfo(tooltip, description)); + + const dd = this.getDetailAndDescription(item, lines); + const descriptionWithHighlightedCode = this.highlightCode(dd[1]); + const tooltip = new vscode.MarkdownString(['y```python', signature, '```', descriptionWithHighlightedCode].join(EOL)); + infos.push(new LanguageItemInfo(tooltip, dd[0], new vscode.MarkdownString(dd[1]))); const key = signature + lines.join(''); // Sometimes we have duplicate documentation, one with a period at the end. @@ -122,7 +125,7 @@ export class ItemInfoSource { return; } capturedInfo.push(key); - capturedInfo.push(`${key}.`); + capturedInfo.push(`${key}.`); return; } @@ -130,8 +133,7 @@ export class ItemInfoSource { const descriptionWithHighlightedCode = this.highlightCode(item.description); // tslint:disable-next-line:prefer-template const tooltip = new vscode.MarkdownString('```python' + `${EOL}${signature}${EOL}` + '```' + `${EOL}${descriptionWithHighlightedCode}`); - infos.push(new LanguageItemInfo(tooltip, item.description)); - + const lines = item.description.split(EOL); const key = signature + lines.join(''); // Sometimes we have duplicate documentation, one with a period at the end. @@ -139,6 +141,9 @@ export class ItemInfoSource { return; } + const dd = this.getDetailAndDescription(item, lines); + infos.push(new LanguageItemInfo(tooltip, dd[0], new vscode.MarkdownString(dd[1]))); + capturedInfo.push(key); capturedInfo.push(`${key}.`); return; @@ -147,6 +152,20 @@ export class ItemInfoSource { return infos; } + private getDetailAndDescription(item: IHoverItem, lines: string[]): [string, string] { + let detail: string; + let description: string; + + if (item.signature && item.signature.length > 0) { + detail = lines.length > 0 ? lines[0] : ''; + description = lines.filter((line, index) => index > 0).join(EOL); + } else { + detail = item.description; + description = lines.join(EOL); + } + return [detail, description]; + } + private highlightCode(docstring: string): string { /********** * @@ -185,7 +204,8 @@ export class ItemInfoSource { if (line.startsWith('```')) { inCodeBlock = !inCodeBlock; if (inCodeBlock) { - codeIndentation = lines[i + 1].match(/^ */)[0].length; + const match = lines[i + 1].match(/^ */); + codeIndentation = match && match.length > 0 ? match[0].length : 0; } continue; } From 92655ecc7434aabbc13dcb359f98343105398d18 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Mon, 11 Dec 2017 13:57:32 -0800 Subject: [PATCH 24/24] Fix tests --- src/client/providers/itemInfoSource.ts | 17 +++++++++-------- src/test/autocomplete/base.test.ts | 17 +++++++++-------- src/test/index.ts | 3 ++- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/client/providers/itemInfoSource.ts b/src/client/providers/itemInfoSource.ts index 15d579270ec2..9dc906e23580 100644 --- a/src/client/providers/itemInfoSource.ts +++ b/src/client/providers/itemInfoSource.ts @@ -102,6 +102,8 @@ export class ItemInfoSource { } if (item.docstring) { let lines = item.docstring.split(/\r?\n/); + const dnd = this.getDetailAndDescription(item, lines); + // If the docstring starts with the signature, then remove those lines from the docstring. if (lines.length > 0 && item.signature.indexOf(lines[0]) === 0) { lines.shift(); @@ -114,10 +116,9 @@ export class ItemInfoSource { lines.shift(); } - const dd = this.getDetailAndDescription(item, lines); - const descriptionWithHighlightedCode = this.highlightCode(dd[1]); + const descriptionWithHighlightedCode = this.highlightCode(dnd[1]); const tooltip = new vscode.MarkdownString(['y```python', signature, '```', descriptionWithHighlightedCode].join(EOL)); - infos.push(new LanguageItemInfo(tooltip, dd[0], new vscode.MarkdownString(dd[1]))); + infos.push(new LanguageItemInfo(tooltip, dnd[0], new vscode.MarkdownString(dnd[1]))); const key = signature + lines.join(''); // Sometimes we have duplicate documentation, one with a period at the end. @@ -135,15 +136,15 @@ export class ItemInfoSource { const tooltip = new vscode.MarkdownString('```python' + `${EOL}${signature}${EOL}` + '```' + `${EOL}${descriptionWithHighlightedCode}`); const lines = item.description.split(EOL); + const dd = this.getDetailAndDescription(item, lines); + infos.push(new LanguageItemInfo(tooltip, dd[0], new vscode.MarkdownString(dd[1]))); + const key = signature + lines.join(''); // Sometimes we have duplicate documentation, one with a period at the end. if (capturedInfo.indexOf(key) >= 0 || capturedInfo.indexOf(`${key}.`) >= 0) { return; } - const dd = this.getDetailAndDescription(item, lines); - infos.push(new LanguageItemInfo(tooltip, dd[0], new vscode.MarkdownString(dd[1]))); - capturedInfo.push(key); capturedInfo.push(`${key}.`); return; @@ -158,10 +159,10 @@ export class ItemInfoSource { if (item.signature && item.signature.length > 0) { detail = lines.length > 0 ? lines[0] : ''; - description = lines.filter((line, index) => index > 0).join(EOL); + description = lines.filter((line, index) => index > 0).join(EOL).trim(); } else { detail = item.description; - description = lines.join(EOL); + description = lines.join(EOL).trim(); } return [detail, description]; } diff --git a/src/test/autocomplete/base.test.ts b/src/test/autocomplete/base.test.ts index a8431ab3c1e3..e9efcfa8e15e 100644 --- a/src/test/autocomplete/base.test.ts +++ b/src/test/autocomplete/base.test.ts @@ -122,7 +122,7 @@ suite('Autocomplete', () => { const items = list.items.filter(item => item.label === 'sleep'); assert.notEqual(items.length, 0, 'sleep not found'); - checkDocumentation(items[0].documentation, 'Delay execution for a given number of seconds. The argument may be'); + checkDocumentation(items[0], 'Delay execution for a given number of seconds. The argument may be'); }); test('For custom class', done => { @@ -158,7 +158,7 @@ suite('Autocomplete', () => { assert.equal(items.length, 1, 'bar not found'); const expected = `说明 - keep this line, it works${EOL}delete following line, it works${EOL}如果存在需要等待审批或正在执行的任务,将不刷新页面`; - checkDocumentation(items[0].documentation, expected); + checkDocumentation(items[0], expected); }).then(done, done); }); @@ -176,13 +176,13 @@ suite('Autocomplete', () => { }).then(list => { let items = list.items.filter(item => item.label === 'Foo'); assert.equal(items.length, 1, 'Foo not found'); - checkDocumentation(items[0].documentation, '说明'); + checkDocumentation(items[0], '说明'); items = list.items.filter(item => item.label === 'showMessage'); assert.equal(items.length, 1, 'showMessage not found'); const expected = `Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи. ${EOL}Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.`; - checkDocumentation(items[0].documentation, expected); + checkDocumentation(items[0], expected); }).then(done, done); }); @@ -215,10 +215,11 @@ suite('Autocomplete', () => { }); // tslint:disable-next-line:no-any -function checkDocumentation(itemDoc: any, expectedContains: string): void { - const documentation = itemDoc as vscode.MarkdownString; +function checkDocumentation(item: vscode.CompletionItem, expectedContains: string): void { + const documentation = item.documentation as vscode.MarkdownString; assert.notEqual(documentation, null, 'Documentation is not MarkdownString'); - assert.equal(documentation.value.indexOf(expectedContains) > 0, true, 'Documentation incorrect'); - assert.equal(documentation.value.indexOf('```python'), 0, 'Documentation is not colorized as Python'); + const inDoc = documentation.value.indexOf(expectedContains) >= 0; + const inDetails = item.detail.indexOf(expectedContains) >= 0; + assert.equal(inDoc !== inDetails, true, 'Documentation incorrect'); } diff --git a/src/test/index.ts b/src/test/index.ts index 4d3b12a351ca..eebaa9b59c8d 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -12,7 +12,8 @@ const options: MochaSetupOptions & { retries: number } = { ui: 'tdd', useColors: true, timeout: 25000, - retries: 3 + retries: 3, + grep: 'Autocomplete' }; testRunner.configure(options); module.exports = testRunner;