From d4b7de7599102a2451972c8827026f0a172e8820 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 23 Apr 2018 10:33:01 -0700 Subject: [PATCH] Fixes multiple issues with formatting on type (#1450) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant * Harden LS startup error checks * #1364 Intellisense doesn't work after specific const string * Telemetry for the analysis enging * PR feedback * Fix typo * Test baseline update * Jedi 0.12 * Priority to goto_defition * News * Replace unzip * Linux flavors + test * Grammar check * Grammar test * Test baselines * Pin dependency [skip ci] --- package.json | 6 + src/client/activation/analysis.ts | 2 +- src/client/activation/analysisEngineHashes.ts | 15 +- src/client/activation/downloader.ts | 8 +- src/client/activation/platformData.ts | 67 +- src/client/formatters/lineFormatter.ts | 132 +- src/client/language/tokenizer.ts | 139 +- src/test/activation/platformData.test.ts | 84 + src/test/definitions/hover.ptvs.test.ts | 32 +- .../format/extension.lineFormatter.test.ts | 52 + src/test/language/tokenizer.test.ts | 129 +- .../pythonFiles/formatting/pythonGrammar.py | 1572 +++++++++++++++++ src/test/signature/signature.ptvs.test.ts | 3 +- 13 files changed, 2089 insertions(+), 152 deletions(-) create mode 100644 src/test/activation/platformData.test.ts create mode 100644 src/test/pythonFiles/formatting/pythonGrammar.py diff --git a/package.json b/package.json index a763aab7fc57..0b518adeb974 100644 --- a/package.json +++ b/package.json @@ -1581,6 +1581,12 @@ "description": "Automatically add brackets for functions.", "scope": "resource" }, + "python.autoComplete.showAdvancedMembers": { + "type": "boolean", + "default": false, + "description": "Controls appearance of methods with double underscores in the completion list.", + "scope": "resource" + }, "python.workspaceSymbols.tagFilePath": { "type": "string", "default": "${workspaceFolder}/.vscode/tags", diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 7ed94f893a8f..d2e853dc7dda 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -57,7 +57,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator { this.appShell = this.services.get(IApplicationShell); this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); this.fs = this.services.get(IFileSystem); - this.platformData = new PlatformData(services.get(IPlatformService)); + this.platformData = new PlatformData(services.get(IPlatformService), this.fs); } public async activate(context: ExtensionContext): Promise { diff --git a/src/client/activation/analysisEngineHashes.ts b/src/client/activation/analysisEngineHashes.ts index 52761329113e..2f9123a46c59 100644 --- a/src/client/activation/analysisEngineHashes.ts +++ b/src/client/activation/analysisEngineHashes.ts @@ -3,7 +3,14 @@ // This file will be replaced by a generated one during the release build // with actual hashes of the uploaded packages. -export const analysis_engine_win_x86_sha512 = ''; -export const analysis_engine_win_x64_sha512 = ''; -export const analysis_engine_osx_x64_sha512 = ''; -export const analysis_engine_linux_x64_sha512 = ''; +// Values are for test purposes only +export const analysis_engine_win_x86_sha512 = 'win-x86'; +export const analysis_engine_win_x64_sha512 = 'win-x64'; +export const analysis_engine_osx_x64_sha512 = 'osx-x64'; +export const analysis_engine_centos_x64_sha512 = 'centos-x64'; +export const analysis_engine_debian_x64_sha512 = 'debian-x64'; +export const analysis_engine_fedora_x64_sha512 = 'fedora-x64'; +export const analysis_engine_ol_x64_sha512 = 'ol-x64'; +export const analysis_engine_opensuse_x64_sha512 = 'opensuse-x64'; +export const analysis_engine_rhel_x64_sha512 = 'rhel-x64'; +export const analysis_engine_ubuntu_x64_sha512 = 'ubuntu-x64'; diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index c48b0b4b2503..98a2d2e1bfc2 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -9,7 +9,7 @@ import { ExtensionContext, OutputChannel, ProgressLocation, window } from 'vscod import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { noop } from '../common/core.utils'; import { createDeferred, createTemporaryFile } from '../common/helpers'; -import { IPlatformService } from '../common/platform/types'; +import { IFileSystem, IPlatformService } from '../common/platform/types'; import { IOutputChannel } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { HashVerifier } from './hashVerifier'; @@ -31,7 +31,7 @@ export class AnalysisEngineDownloader { constructor(private readonly services: IServiceContainer, private engineFolder: string) { this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); this.platform = this.services.get(IPlatformService); - this.platformData = new PlatformData(this.platform); + this.platformData = new PlatformData(this.platform, this.services.get(IFileSystem)); } public async downloadAnalysisEngine(context: ExtensionContext): Promise { @@ -49,7 +49,7 @@ export class AnalysisEngineDownloader { } private async downloadFile(): Promise { - const platformString = this.platformData.getPlatformDesignator(); + const platformString = await this.platformData.getPlatformName(); const remoteFileName = `${downloadBaseFileName}-${platformString}.${downloadVersion}${downloadFileExtension}`; const uri = `${downloadUriPrefix}/${remoteFileName}`; this.output.append(`Downloading ${uri}... `); @@ -98,7 +98,7 @@ export class AnalysisEngineDownloader { this.output.appendLine(''); this.output.append('Verifying download... '); const verifier = new HashVerifier(); - if (!await verifier.verifyHash(filePath, this.platformData.getExpectedHash())) { + if (!await verifier.verifyHash(filePath, await this.platformData.getExpectedHash())) { throw new Error('Hash of the downloaded file does not match.'); } this.output.append('valid.'); diff --git a/src/client/activation/platformData.ts b/src/client/activation/platformData.ts index 541e5a602bef..2a1cb29da461 100644 --- a/src/client/activation/platformData.ts +++ b/src/client/activation/platformData.ts @@ -1,27 +1,54 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { IPlatformService } from '../common/platform/types'; +import { IFileSystem, IPlatformService } from '../common/platform/types'; import { - analysis_engine_linux_x64_sha512, + analysis_engine_centos_x64_sha512, + analysis_engine_debian_x64_sha512, + analysis_engine_fedora_x64_sha512, + analysis_engine_ol_x64_sha512, + analysis_engine_opensuse_x64_sha512, analysis_engine_osx_x64_sha512, + analysis_engine_rhel_x64_sha512, + analysis_engine_ubuntu_x64_sha512, analysis_engine_win_x64_sha512, analysis_engine_win_x86_sha512 } from './analysisEngineHashes'; +// '/etc/os-release', ID=flavor +const supportedLinuxFlavors = [ + 'centos', + 'debian', + 'fedora', + 'ol', + 'opensuse', + 'rhel', + 'ubuntu' +]; + export class PlatformData { - constructor(private platform: IPlatformService) { } - public getPlatformDesignator(): string { + constructor(private platform: IPlatformService, private fs: IFileSystem) { } + public async getPlatformName(): Promise { if (this.platform.isWindows) { return this.platform.is64bit ? 'win-x64' : 'win-x86'; } if (this.platform.isMac) { return 'osx-x64'; } - if (this.platform.isLinux && this.platform.is64bit) { - return 'linux-x64'; + if (this.platform.isLinux) { + if (!this.platform.is64bit) { + throw new Error('Python Analysis Engine does not support 32-bit Linux.'); + } + const linuxFlavor = await this.getLinuxFlavor(); + if (linuxFlavor.length === 0) { + throw new Error('Unable to determine Linux flavor from /etc/os-release.'); + } + if (supportedLinuxFlavors.indexOf(linuxFlavor) < 0) { + throw new Error(`${linuxFlavor} is not supported.`); + } + return `${linuxFlavor}-x64`; } - throw new Error('Python Analysis Engine does not support 32-bit Linux.'); + throw new Error('Unknown OS platform.'); } public getEngineDllName(): string { @@ -34,7 +61,7 @@ export class PlatformData { : 'Microsoft.PythonTools.VsCode'; } - public getExpectedHash(): string { + public async getExpectedHash(): Promise { if (this.platform.isWindows) { return this.platform.is64bit ? analysis_engine_win_x64_sha512 : analysis_engine_win_x86_sha512; } @@ -42,8 +69,30 @@ export class PlatformData { return analysis_engine_osx_x64_sha512; } if (this.platform.isLinux && this.platform.is64bit) { - return analysis_engine_linux_x64_sha512; + const linuxFlavor = await this.getLinuxFlavor(); + // tslint:disable-next-line:switch-default + switch (linuxFlavor) { + case 'centos': return analysis_engine_centos_x64_sha512; + case 'debian': return analysis_engine_debian_x64_sha512; + case 'fedora': return analysis_engine_fedora_x64_sha512; + case 'ol': return analysis_engine_ol_x64_sha512; + case 'opensuse': return analysis_engine_opensuse_x64_sha512; + case 'rhel': return analysis_engine_rhel_x64_sha512; + case 'ubuntu': return analysis_engine_ubuntu_x64_sha512; + } } throw new Error('Unknown platform.'); } + + private async getLinuxFlavor(): Promise { + const verFile = '/etc/os-release'; + const data = await this.fs.readFile(verFile); + if (data) { + const res = /ID=(.*)/.exec(data); + if (res && res.length > 1) { + return res[1]; + } + } + return ''; + } } diff --git a/src/client/formatters/lineFormatter.ts b/src/client/formatters/lineFormatter.ts index 046533952464..2c7b37580f11 100644 --- a/src/client/formatters/lineFormatter.ts +++ b/src/client/formatters/lineFormatter.ts @@ -43,7 +43,7 @@ export class LineFormatter { case TokenType.Comma: this.builder.append(','); - if (next && !this.isCloseBraceType(next.type)) { + if (next && !this.isCloseBraceType(next.type) && next.type !== TokenType.Colon) { this.builder.softAppendSpace(); } break; @@ -52,7 +52,12 @@ export class LineFormatter { if (prev && !this.isOpenBraceType(prev.type) && prev.type !== TokenType.Colon && prev.type !== TokenType.Operator) { this.builder.softAppendSpace(); } - this.builder.append(this.text.substring(t.start, t.end)); + const id = this.text.substring(t.start, t.end); + this.builder.append(id); + if (this.keywordWithSpaceAfter(id) && next && this.isOpenBraceType(next.type)) { + // for x in () + this.builder.softAppendSpace(); + } break; case TokenType.Colon: @@ -84,8 +89,10 @@ export class LineFormatter { return this.builder.getText(); } + // tslint:disable-next-line:cyclomatic-complexity private handleOperator(index: number): void { const t = this.tokens.getItemAt(index); + const prev = index > 0 ? this.tokens.getItemAt(index - 1) : undefined; if (t.length === 1) { const opCode = this.text.charCodeAt(t.start); switch (opCode) { @@ -99,18 +106,36 @@ export class LineFormatter { case Char.ExclamationMark: this.builder.append(this.text[t.start]); return; + case Char.Asterisk: + if (prev && prev.type === TokenType.Identifier && prev.length === 6 && this.text.substr(prev.start, prev.length) === 'lambda') { + this.builder.softAppendSpace(); + this.builder.append('*'); + return; + } + break; default: break; } + } else if (t.length === 2) { + if (this.text.charCodeAt(t.start) === Char.Asterisk && this.text.charCodeAt(t.start + 1) === Char.Asterisk) { + if (!prev || (prev.type !== TokenType.Identifier && prev.type !== TokenType.Number)) { + this.builder.append('**'); + return; + } + if (prev && prev.type === TokenType.Identifier && prev.length === 6 && this.text.substr(prev.start, prev.length) === 'lambda') { + this.builder.softAppendSpace(); + this.builder.append('**'); + return; + } + } } + // Do not append space if operator is preceded by '(' or ',' as in foo(**kwarg) - if (index > 0) { - const prev = this.tokens.getItemAt(index - 1); - if (this.isOpenBraceType(prev.type) || prev.type === TokenType.Comma) { - this.builder.append(this.text.substring(t.start, t.end)); - return; - } + if (prev && (this.isOpenBraceType(prev.type) || prev.type === TokenType.Comma)) { + this.builder.append(this.text.substring(t.start, t.end)); + return; } + this.builder.softAppendSpace(); this.builder.append(this.text.substring(t.start, t.end)); this.builder.softAppendSpace(); @@ -135,43 +160,82 @@ export class LineFormatter { return; } - if (this.isEqualsInsideArguments(index - 1)) { + const prev = index > 0 ? this.tokens.getItemAt(index - 1) : undefined; + if (prev && prev.length === 1 && this.text.charCodeAt(prev.start) === Char.Equal && this.isEqualsInsideArguments(index - 1)) { // Don't add space around = inside function arguments. this.builder.append(this.text.substring(t.start, t.end)); return; } - if (index > 0) { - const prev = this.tokens.getItemAt(index - 1); - if (this.isOpenBraceType(prev.type) || prev.type === TokenType.Colon) { - // Don't insert space after (, [ or { . - this.builder.append(this.text.substring(t.start, t.end)); - return; - } + if (prev && (this.isOpenBraceType(prev.type) || prev.type === TokenType.Colon)) { + // Don't insert space after (, [ or { . + this.builder.append(this.text.substring(t.start, t.end)); + return; } - // In general, keep tokens separated. - this.builder.softAppendSpace(); - this.builder.append(this.text.substring(t.start, t.end)); + if (t.type === TokenType.Unknown) { + this.handleUnknown(t); + } else { + // In general, keep tokens separated. + this.builder.softAppendSpace(); + this.builder.append(this.text.substring(t.start, t.end)); + } } + private handleUnknown(t: IToken): void { + const prevChar = t.start > 0 ? this.text.charCodeAt(t.start - 1) : 0; + if (prevChar === Char.Space || prevChar === Char.Tab) { + this.builder.softAppendSpace(); + } + this.builder.append(this.text.substring(t.start, t.end)); + + const nextChar = t.end < this.text.length - 1 ? this.text.charCodeAt(t.end) : 0; + if (nextChar === Char.Space || nextChar === Char.Tab) { + this.builder.softAppendSpace(); + } + } private isEqualsInsideArguments(index: number): boolean { + // Since we don't have complete statement, this is mostly heuristics. + // Therefore the code may not be handling all possible ways of the + // argument list continuation. if (index < 1) { return false; } + const prev = this.tokens.getItemAt(index - 1); - if (prev.type === TokenType.Identifier) { - if (index >= 2) { - // (x=1 or ,x=1 - const prevPrev = this.tokens.getItemAt(index - 2); - return prevPrev.type === TokenType.Comma || prevPrev.type === TokenType.OpenBrace; - } else if (index < this.tokens.count - 2) { - const next = this.tokens.getItemAt(index + 1); - const nextNext = this.tokens.getItemAt(index + 2); - // x=1, or x=1) - if (this.isValueType(next.type)) { - return nextNext.type === TokenType.Comma || nextNext.type === TokenType.CloseBrace; - } + if (prev.type !== TokenType.Identifier) { + return false; + } + + const first = this.tokens.getItemAt(0); + if (first.type === TokenType.Comma) { + return true; // Line starts with commma + } + + const last = this.tokens.getItemAt(this.tokens.count - 1); + if (last.type === TokenType.Comma) { + return true; // Line ends in comma + } + + if (index >= 2) { + // (x=1 or ,x=1 + const prevPrev = this.tokens.getItemAt(index - 2); + return prevPrev.type === TokenType.Comma || prevPrev.type === TokenType.OpenBrace; + } + + if (index >= this.tokens.count - 2) { + return false; + } + + const next = this.tokens.getItemAt(index + 1); + const nextNext = this.tokens.getItemAt(index + 2); + // x=1, or x=1) + if (this.isValueType(next.type)) { + if (nextNext.type === TokenType.CloseBrace) { + return true; + } + if (nextNext.type === TokenType.Comma) { + return last.type === TokenType.CloseBrace; } } return false; @@ -198,4 +262,10 @@ export class LineFormatter { } return false; } + private keywordWithSpaceAfter(s: string): boolean { + return s === 'in' || s === 'return' || s === 'and' || + s === 'or' || s === 'not' || s === 'from' || + s === 'import' || s === 'except' || s === 'for' || + s === 'as' || s === 'is'; + } } diff --git a/src/client/language/tokenizer.ts b/src/client/language/tokenizer.ts index e1c8c4b03d9e..7ceafdccb0e6 100644 --- a/src/client/language/tokenizer.ts +++ b/src/client/language/tokenizer.ts @@ -27,13 +27,6 @@ class Token extends TextRange implements IToken { } export class Tokenizer implements ITokenizer { - // private keywords = [ - // 'and', 'assert', 'break', 'class', 'continue', 'def', 'del', - // 'elif', 'else', 'except', 'exec', 'False', 'finally', 'for', 'from', - // 'global', 'if', 'import', 'in', 'is', 'lambda', 'None', 'nonlocal', - // 'not', 'or', 'pass', 'print', 'raise', 'return', 'True', 'try', - // 'while', 'with', 'yield' - // ]; private cs: ICharacterStream = new CharacterStream(''); private tokens: IToken[] = []; private floatRegex = /[-+]?(?:(?:\d*\.\d+)|(?:\d+\.?))(?:[Ee][+-]?\d+)?/; @@ -87,15 +80,17 @@ export class Tokenizer implements ITokenizer { // tslint:disable-next-line:cyclomatic-complexity private handleCharacter(): boolean { - // f-strings - const fString = this.cs.currentChar === Char.f && (this.cs.nextChar === Char.SingleQuote || this.cs.nextChar === Char.DoubleQuote); - if (fString) { - this.cs.moveNext(); - } - const quoteType = this.getQuoteType(); - if (quoteType !== QuoteType.None) { - this.handleString(quoteType, fString); - return true; + // f-strings, b-strings, etc + const stringPrefixLength = this.getStringPrefixLength(); + if (stringPrefixLength >= 0) { + // Indeed a string + this.cs.advance(stringPrefixLength); + + const quoteType = this.getQuoteType(); + if (quoteType !== QuoteType.None) { + this.handleString(quoteType, stringPrefixLength); + return true; + } } if (this.cs.currentChar === Char.Hash) { this.handleComment(); @@ -133,16 +128,16 @@ export class Tokenizer implements ITokenizer { case Char.Colon: this.tokens.push(new Token(TokenType.Colon, this.cs.position, 1)); break; - case Char.At: - case Char.Period: - this.tokens.push(new Token(TokenType.Operator, this.cs.position, 1)); - break; default: if (this.isPossibleNumber()) { if (this.tryNumber()) { return true; } } + if (this.cs.currentChar === Char.Period) { + this.tokens.push(new Token(TokenType.Operator, this.cs.position, 1)); + break; + } if (!this.tryIdentifier()) { if (!this.tryOperator()) { this.handleUnknown(); @@ -170,29 +165,8 @@ export class Tokenizer implements ITokenizer { return false; } + // tslint:disable-next-line:cyclomatic-complexity private isPossibleNumber(): boolean { - if (this.cs.currentChar === Char.Hyphen || this.cs.currentChar === Char.Plus) { - // Next character must be decimal or a dot otherwise - // it is not a number. No whitespace is allowed. - if (isDecimal(this.cs.nextChar) || this.cs.nextChar === Char.Period) { - // Check what previous token is, if any - if (this.tokens.length === 0) { - // At the start of the file this can only be a number - return true; - } - - const prev = this.tokens[this.tokens.length - 1]; - if (prev.type === TokenType.OpenBrace - || prev.type === TokenType.OpenBracket - || prev.type === TokenType.Comma - || prev.type === TokenType.Semicolon - || prev.type === TokenType.Operator) { - return true; - } - } - return false; - } - if (isDecimal(this.cs.currentChar)) { return true; } @@ -201,12 +175,52 @@ export class Tokenizer implements ITokenizer { return true; } + const next = (this.cs.currentChar === Char.Hyphen || this.cs.currentChar === Char.Plus) ? 1 : 0; + // Next character must be decimal or a dot otherwise + // it is not a number. No whitespace is allowed. + if (isDecimal(this.cs.lookAhead(next)) || this.cs.lookAhead(next) === Char.Period) { + // Check what previous token is, if any + if (this.tokens.length === 0) { + // At the start of the file this can only be a number + return true; + } + + const prev = this.tokens[this.tokens.length - 1]; + if (prev.type === TokenType.OpenBrace + || prev.type === TokenType.OpenBracket + || prev.type === TokenType.Comma + || prev.type === TokenType.Colon + || prev.type === TokenType.Semicolon + || prev.type === TokenType.Operator) { + return true; + } + } + + if (this.cs.lookAhead(next) === Char._0) { + const nextNext = this.cs.lookAhead(next + 1); + if (nextNext === Char.x || nextNext === Char.X) { + return true; + } + if (nextNext === Char.b || nextNext === Char.B) { + return true; + } + if (nextNext === Char.o || nextNext === Char.O) { + return true; + } + } + return false; } // tslint:disable-next-line:cyclomatic-complexity private tryNumber(): boolean { const start = this.cs.position; + let leadingSign = 0; + + if (this.cs.currentChar === Char.Hyphen || this.cs.currentChar === Char.Plus) { + this.cs.moveNext(); // Skip leading +/- + leadingSign = 1; + } if (this.cs.currentChar === Char._0) { let radix = 0; @@ -234,20 +248,19 @@ export class Tokenizer implements ITokenizer { } radix = 8; } - const text = this.cs.getText().substr(start, this.cs.position - start); + const text = this.cs.getText().substr(start + leadingSign, this.cs.position - start - leadingSign); if (radix > 0 && parseInt(text.substr(2), radix)) { - this.tokens.push(new Token(TokenType.Number, start, text.length)); + this.tokens.push(new Token(TokenType.Number, start, text.length + leadingSign)); return true; } } - if (isDecimal(this.cs.currentChar) || - this.cs.currentChar === Char.Plus || this.cs.currentChar === Char.Hyphen || this.cs.currentChar === Char.Period) { + if (isDecimal(this.cs.currentChar) || this.cs.currentChar === Char.Period) { const candidate = this.cs.getText().substr(this.cs.position); const re = this.floatRegex.exec(candidate); if (re && re.length > 0 && re[0] && candidate.startsWith(re[0])) { - this.tokens.push(new Token(TokenType.Number, start, re[0].length)); - this.cs.position = start + re[0].length; + this.tokens.push(new Token(TokenType.Number, start, re[0].length + leadingSign)); + this.cs.position = start + re[0].length + leadingSign; return true; } } @@ -262,7 +275,6 @@ export class Tokenizer implements ITokenizer { const nextChar = this.cs.nextChar; switch (this.cs.currentChar) { case Char.Plus: - case Char.Hyphen: case Char.Ampersand: case Char.Bar: case Char.Caret: @@ -271,6 +283,10 @@ export class Tokenizer implements ITokenizer { length = nextChar === Char.Equal ? 2 : 1; break; + case Char.Hyphen: + length = nextChar === Char.Equal || nextChar === Char.Greater ? 2 : 1; + break; + case Char.Asterisk: if (nextChar === Char.Asterisk) { length = this.cs.lookAhead(2) === Char.Equal ? 3 : 2; @@ -306,7 +322,7 @@ export class Tokenizer implements ITokenizer { break; case Char.At: - length = nextChar === Char.Equal ? 2 : 0; + length = nextChar === Char.Equal ? 2 : 1; break; default: @@ -334,6 +350,25 @@ export class Tokenizer implements ITokenizer { this.tokens.push(new Token(TokenType.Comment, start, this.cs.position - start)); } + private getStringPrefixLength(): number { + if (this.cs.currentChar === Char.f && (this.cs.nextChar === Char.SingleQuote || this.cs.nextChar === Char.DoubleQuote)) { + return 1; // f-string + } + if (this.cs.currentChar === Char.b || this.cs.currentChar === Char.B || this.cs.currentChar === Char.u || this.cs.currentChar === Char.U) { + if (this.cs.nextChar === Char.SingleQuote || this.cs.nextChar === Char.DoubleQuote) { + // b-string or u-string + return 1; + } + if (this.cs.nextChar === Char.r || this.cs.nextChar === Char.R) { + // b-string or u-string with 'r' suffix + if (this.cs.lookAhead(2) === Char.SingleQuote || this.cs.lookAhead(2) === Char.DoubleQuote) { + return 2; + } + } + } + return this.cs.currentChar === Char.SingleQuote || this.cs.currentChar === Char.DoubleQuote ? 0 : -1; + } + private getQuoteType(): QuoteType { if (this.cs.currentChar === Char.SingleQuote) { return this.cs.nextChar === Char.SingleQuote && this.cs.lookAhead(2) === Char.SingleQuote @@ -348,8 +383,8 @@ export class Tokenizer implements ITokenizer { return QuoteType.None; } - private handleString(quoteType: QuoteType, fString: boolean): void { - const start = fString ? this.cs.position - 1 : this.cs.position; + private handleString(quoteType: QuoteType, stringPrefixLength: number): void { + const start = this.cs.position - stringPrefixLength; if (quoteType === QuoteType.Single || quoteType === QuoteType.Double) { this.cs.moveNext(); this.skipToSingleEndQuote(quoteType === QuoteType.Single diff --git a/src/test/activation/platformData.test.ts b/src/test/activation/platformData.test.ts new file mode 100644 index 000000000000..4270f4c6a3fc --- /dev/null +++ b/src/test/activation/platformData.test.ts @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// tslint:disable:no-unused-variable +import * as assert from 'assert'; +import * as TypeMoq from 'typemoq'; +import { PlatformData } from '../../client/activation/platformData'; +import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; +import { initialize } from '../initialize'; + +const testDataWinMac = [ + { isWindows: true, is64Bit: true, expectedName: 'win-x64' }, + { isWindows: true, is64Bit: false, expectedName: 'win-x86' }, + { isWindows: false, is64Bit: true, expectedName: 'osx-x64' } +]; + +const testDataLinux = [ + { name: 'centos', expectedName: 'centos-x64' }, + { name: 'debian', expectedName: 'debian-x64' }, + { name: 'fedora', expectedName: 'fedora-x64' }, + { name: 'ol', expectedName: 'ol-x64' }, + { name: 'opensuse', expectedName: 'opensuse-x64' }, + { name: 'rhel', expectedName: 'rhel-x64' }, + { name: 'ubuntu', expectedName: 'ubuntu-x64' } +]; + +const testDataModuleName = [ + { isWindows: true, expectedName: 'Microsoft.PythonTools.VsCode.exe' }, + { isWindows: false, expectedName: 'Microsoft.PythonTools.VsCode' } +]; + +// tslint:disable-next-line:max-func-body-length +suite('Activation - platform data', () => { + suiteSetup(initialize); + + test('Name and hash (Windows/Mac)', async () => { + for (const t of testDataWinMac) { + const platformService = TypeMoq.Mock.ofType(); + platformService.setup(x => x.isWindows).returns(() => t.isWindows); + platformService.setup(x => x.isMac).returns(() => !t.isWindows); + platformService.setup(x => x.is64bit).returns(() => t.is64Bit); + + const fs = TypeMoq.Mock.ofType(); + const pd = new PlatformData(platformService.object, fs.object); + + let actual = await pd.getPlatformName(); + assert.equal(actual, t.expectedName, `${actual} does not match ${t.expectedName}`); + + actual = await pd.getExpectedHash(); + assert.equal(actual, t.expectedName, `${actual} hash not match ${t.expectedName}`); + } + }); + test('Name and hash (Linux)', async () => { + for (const t of testDataLinux) { + const platformService = TypeMoq.Mock.ofType(); + platformService.setup(x => x.isWindows).returns(() => false); + platformService.setup(x => x.isMac).returns(() => false); + platformService.setup(x => x.isLinux).returns(() => true); + platformService.setup(x => x.is64bit).returns(() => true); + + const fs = TypeMoq.Mock.ofType(); + fs.setup(x => x.readFile(TypeMoq.It.isAnyString())).returns(() => Promise.resolve(`NAME="name"\nID=${t.name}\nID_LIKE=debian`)); + const pd = new PlatformData(platformService.object, fs.object); + + let actual = await pd.getPlatformName(); + assert.equal(actual, t.expectedName, `${actual} does not match ${t.expectedName}`); + + actual = await pd.getExpectedHash(); + assert.equal(actual, t.expectedName, `${actual} hash not match ${t.expectedName}`); + } + }); + test('Module name', async () => { + for (const t of testDataModuleName) { + const platformService = TypeMoq.Mock.ofType(); + platformService.setup(x => x.isWindows).returns(() => t.isWindows); + + const fs = TypeMoq.Mock.ofType(); + const pd = new PlatformData(platformService.object, fs.object); + + const actual = pd.getEngineExecutableName(); + assert.equal(actual, t.expectedName, `${actual} does not match ${t.expectedName}`); + } + }); +}); diff --git a/src/test/definitions/hover.ptvs.test.ts b/src/test/definitions/hover.ptvs.test.ts index d2a456efd4bd..4f0f014c7bff 100644 --- a/src/test/definitions/hover.ptvs.test.ts +++ b/src/test/definitions/hover.ptvs.test.ts @@ -53,9 +53,7 @@ suite('Hover Definition (Analysis Engine)', () => { const expected = [ 'obj.method1:', 'method method1 of one.Class1 objects', - '```html', - 'This is method1', - '```' + 'This is method1' ]; verifySignatureLines(actual, expected); }); @@ -70,9 +68,7 @@ suite('Hover Definition (Analysis Engine)', () => { const expected = [ 'two.ct().fun:', 'method fun of two.ct objects', - '```html', - 'This is fun', - '```' + 'This is fun' ]; verifySignatureLines(actual, expected); }); @@ -87,11 +83,9 @@ suite('Hover Definition (Analysis Engine)', () => { const expected = [ 'Foo.bar:', 'four.Foo.bar() -> bool', - '```html', '说明 - keep this line, it works', 'delete following line, it works', '如果存在需要等待审批或正在执行的任务,将不刷新页面', - '```', 'declared in Foo' ]; verifySignatureLines(actual, expected); @@ -107,22 +101,26 @@ suite('Hover Definition (Analysis Engine)', () => { const expected = [ 'four.showMessage:', 'four.showMessage()', - '```html', 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи.', - 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.', - '```' + 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.' ]; verifySignatureLines(actual, expected); }); test('Nothing for keywords (class)', async () => { const def = await openAndHover(fileOne, 5, 1); - assert.equal(def.length, 0, 'Definition length is incorrect'); + if (def.length > 0) { + const actual = normalizeMarkedString(def[0].contents[0]); + assert.equal(actual, '', 'Definition length is incorrect'); + } }); test('Nothing for keywords (for)', async () => { const def = await openAndHover(fileHover, 3, 1); - assert.equal(def!.length, 0, 'Definition length is incorrect'); + if (def.length > 0) { + const actual = normalizeMarkedString(def[0].contents[0]); + assert.equal(actual, '', 'Definition length is incorrect'); + } }); test('Highlighting Class', async () => { @@ -136,15 +134,13 @@ suite('Hover Definition (Analysis Engine)', () => { 'misc.Random:', 'class misc.Random(_random.Random)', 'Random number generator base class used by bound module functions.', - '```html', 'Used to instantiate instances of Random to get generators that don\'t', 'share state.', 'Class Random can also be subclassed if you want to use a different basic', 'generator of your own devising: in that case, override the following', 'methods: random(), seed(), getstate(), and setstate().', 'Optionally, implement a getrandbits() method so that randrange()', - 'can cover arbitrarily large ranges.', - '```' + 'can cover arbitrarily large ranges.' ]; verifySignatureLines(actual, expected); }); @@ -191,9 +187,7 @@ suite('Hover Definition (Analysis Engine)', () => { 'misc.Thread:', 'class misc.Thread(_Verbose)', 'A class that represents a thread of control.', - '```html', - 'This class can be safely subclassed in a limited fashion.', - '```' + 'This class can be safely subclassed in a limited fashion.' ]; verifySignatureLines(actual, expected); }); diff --git a/src/test/format/extension.lineFormatter.test.ts b/src/test/format/extension.lineFormatter.test.ts index 3325c19382a2..a9cb0fa04447 100644 --- a/src/test/format/extension.lineFormatter.test.ts +++ b/src/test/format/extension.lineFormatter.test.ts @@ -3,9 +3,16 @@ // Licensed under the MIT License. import * as assert from 'assert'; +import * as fs from 'fs'; +import * as path from 'path'; +import '../../client/common/extensions'; import { LineFormatter } from '../../client/formatters/lineFormatter'; +const formatFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'formatting'); +const grammarFile = path.join(formatFilesPath, 'pythonGrammar.py'); + // https://www.python.org/dev/peps/pep-0008/#code-lay-out +// tslint:disable-next-line:max-func-body-length suite('Formatting - line formatter', () => { const formatter = new LineFormatter(); @@ -81,8 +88,53 @@ suite('Formatting - line formatter', () => { const actual = formatter.formatLine(',x = 1,y =m)'); assert.equal(actual, ', x=1, y=m)'); }); + test('Equals in multiline arguments ending comma', () => { + const actual = formatter.formatLine('x = 1,y =m,'); + assert.equal(actual, 'x=1, y=m,'); + }); test('Operators without following space', () => { const actual = formatter.formatLine('foo( *a, ** b, ! c)'); assert.equal(actual, 'foo(*a, **b, !c)'); }); + test('Brace after keyword', () => { + const actual = formatter.formatLine('for x in(1,2,3)'); + assert.equal(actual, 'for x in (1, 2, 3)'); + }); + test('Dot operator', () => { + const actual = formatter.formatLine('x.y'); + assert.equal(actual, 'x.y'); + }); + test('Unknown tokens no space', () => { + const actual = formatter.formatLine('abc\\n\\'); + assert.equal(actual, 'abc\\n\\'); + }); + test('Unknown tokens with space', () => { + const actual = formatter.formatLine('abc \\n \\'); + assert.equal(actual, 'abc \\n \\'); + }); + test('Double asterisk', () => { + const actual = formatter.formatLine('a**2, ** k'); + assert.equal(actual, 'a ** 2, **k'); + }); + test('Lambda', () => { + const actual = formatter.formatLine('lambda * args, :0'); + assert.equal(actual, 'lambda *args,: 0'); + }); + test('Comma expression', () => { + const actual = formatter.formatLine('x=1,2,3'); + assert.equal(actual, 'x = 1, 2, 3'); + }); + test('is exression', () => { + const actual = formatter.formatLine('a( (False is 2) is 3)'); + assert.equal(actual, 'a((False is 2) is 3)'); + }); + test('Grammar file', () => { + const content = fs.readFileSync(grammarFile).toString('utf8'); + const lines = content.splitLines({ trim: false, removeEmptyEntries: false }); + for (let i = 0; i < lines.length; i += 1) { + const line = lines[i]; + const actual = formatter.formatLine(line); + assert.equal(actual, line, `Line ${i + 1} changed: '${line}' to '${actual}'`); + } + }); }); diff --git a/src/test/language/tokenizer.test.ts b/src/test/language/tokenizer.test.ts index 202f0c774297..d7119b7b4f6f 100644 --- a/src/test/language/tokenizer.test.ts +++ b/src/test/language/tokenizer.test.ts @@ -9,14 +9,14 @@ import { TokenType } from '../../client/language/types'; // tslint:disable-next-line:max-func-body-length suite('Language.Tokenizer', () => { - test('Empty', async () => { + test('Empty', () => { 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: unclosed', async () => { + test('Strings: unclosed', () => { const t = new Tokenizer(); const tokens = t.tokenize(' "string" """line1\n#line2"""\t\'un#closed'); assert.equal(tokens.count, 3); @@ -28,7 +28,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(i).type, TokenType.String); } }); - test('Strings: block next to regular, double-quoted', async () => { + test('Strings: block next to regular, double-quoted', () => { const t = new Tokenizer(); const tokens = t.tokenize('"string""""s2"""'); assert.equal(tokens.count, 2); @@ -40,7 +40,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(i).type, TokenType.String); } }); - test('Strings: block next to block, double-quoted', async () => { + test('Strings: block next to block, double-quoted', () => { const t = new Tokenizer(); const tokens = t.tokenize('""""""""'); assert.equal(tokens.count, 2); @@ -52,7 +52,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(i).type, TokenType.String); } }); - test('Strings: unclosed sequence of quotes', async () => { + test('Strings: unclosed sequence of quotes', () => { const t = new Tokenizer(); const tokens = t.tokenize('"""""'); assert.equal(tokens.count, 1); @@ -64,7 +64,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(i).type, TokenType.String); } }); - test('Strings: single quote escape', async () => { + test('Strings: single quote escape', () => { const t = new Tokenizer(); // tslint:disable-next-line:quotemark const tokens = t.tokenize("'\\'quoted\\''"); @@ -72,14 +72,14 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(0).type, TokenType.String); assert.equal(tokens.getItemAt(0).length, 12); }); - test('Strings: double quote escape', async () => { + test('Strings: double quote escape', () => { const t = new Tokenizer(); const tokens = t.tokenize('"\\"quoted\\""'); assert.equal(tokens.count, 1); assert.equal(tokens.getItemAt(0).type, TokenType.String); assert.equal(tokens.getItemAt(0).length, 12); }); - test('Strings: single quoted f-string ', async () => { + test('Strings: single quoted f-string ', () => { const t = new Tokenizer(); // tslint:disable-next-line:quotemark const tokens = t.tokenize("a+f'quoted'"); @@ -89,7 +89,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(2).type, TokenType.String); assert.equal(tokens.getItemAt(2).length, 9); }); - test('Strings: double quoted f-string ', async () => { + test('Strings: double quoted f-string ', () => { const t = new Tokenizer(); const tokens = t.tokenize('x(1,f"quoted")'); assert.equal(tokens.count, 6); @@ -101,7 +101,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(4).length, 9); assert.equal(tokens.getItemAt(5).type, TokenType.CloseBrace); }); - test('Strings: single quoted multiline f-string ', async () => { + test('Strings: single quoted multiline f-string ', () => { const t = new Tokenizer(); // tslint:disable-next-line:quotemark const tokens = t.tokenize("f'''quoted'''"); @@ -109,14 +109,14 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(0).type, TokenType.String); assert.equal(tokens.getItemAt(0).length, 13); }); - test('Strings: double quoted multiline f-string ', async () => { + test('Strings: double quoted multiline f-string ', () => { const t = new Tokenizer(); const tokens = t.tokenize('f"""quoted """'); assert.equal(tokens.count, 1); assert.equal(tokens.getItemAt(0).type, TokenType.String); assert.equal(tokens.getItemAt(0).length, 14); }); - test('Strings: escape at the end of single quoted string ', async () => { + test('Strings: escape at the end of single quoted string ', () => { const t = new Tokenizer(); // tslint:disable-next-line:quotemark const tokens = t.tokenize("'quoted\\'\nx"); @@ -125,7 +125,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(0).length, 9); assert.equal(tokens.getItemAt(1).type, TokenType.Identifier); }); - test('Strings: escape at the end of double quoted string ', async () => { + test('Strings: escape at the end of double quoted string ', () => { const t = new Tokenizer(); const tokens = t.tokenize('"quoted\\"\nx'); assert.equal(tokens.count, 2); @@ -133,7 +133,28 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(0).length, 9); assert.equal(tokens.getItemAt(1).type, TokenType.Identifier); }); - test('Comments', async () => { + test('Strings: b/u/r-string', () => { + const t = new Tokenizer(); + const tokens = t.tokenize('b"b" u"u" br"br" ur"ur"'); + assert.equal(tokens.count, 4); + assert.equal(tokens.getItemAt(0).type, TokenType.String); + assert.equal(tokens.getItemAt(0).length, 4); + assert.equal(tokens.getItemAt(1).type, TokenType.String); + assert.equal(tokens.getItemAt(1).length, 4); + assert.equal(tokens.getItemAt(2).type, TokenType.String); + assert.equal(tokens.getItemAt(2).length, 6); + assert.equal(tokens.getItemAt(3).type, TokenType.String); + assert.equal(tokens.getItemAt(3).length, 6); + }); + test('Strings: escape at the end of double quoted string ', () => { + const t = new Tokenizer(); + const tokens = t.tokenize('"quoted\\"\nx'); + assert.equal(tokens.count, 2); + assert.equal(tokens.getItemAt(0).type, TokenType.String); + assert.equal(tokens.getItemAt(0).length, 9); + assert.equal(tokens.getItemAt(1).type, TokenType.Identifier); + }); + test('Comments', () => { const t = new Tokenizer(); const tokens = t.tokenize(' #co"""mment1\n\t\n#comm\'ent2 '); assert.equal(tokens.count, 2); @@ -145,7 +166,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(i).type, TokenType.Comment); } }); - test('Period to operator token', async () => { + test('Period to operator token', () => { const t = new Tokenizer(); const tokens = t.tokenize('x.y'); assert.equal(tokens.count, 3); @@ -154,7 +175,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(1).type, TokenType.Operator); assert.equal(tokens.getItemAt(2).type, TokenType.Identifier); }); - test('@ to operator token', async () => { + test('@ to operator token', () => { const t = new Tokenizer(); const tokens = t.tokenize('@x'); assert.equal(tokens.count, 2); @@ -162,14 +183,14 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(0).type, TokenType.Operator); assert.equal(tokens.getItemAt(1).type, TokenType.Identifier); }); - test('Unknown token', async () => { + test('Unknown token', () => { const t = new Tokenizer(); const tokens = t.tokenize('~$'); assert.equal(tokens.count, 1); assert.equal(tokens.getItemAt(0).type, TokenType.Unknown); }); - test('Hex number', async () => { + test('Hex number', () => { const t = new Tokenizer(); const tokens = t.tokenize('1 0X2 0x3 0x'); assert.equal(tokens.count, 4); @@ -186,7 +207,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(3).type, TokenType.Unknown); assert.equal(tokens.getItemAt(3).length, 2); }); - test('Binary number', async () => { + test('Binary number', () => { const t = new Tokenizer(); const tokens = t.tokenize('1 0B1 0b010 0b3 0b'); assert.equal(tokens.count, 6); @@ -209,10 +230,10 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(5).type, TokenType.Unknown); assert.equal(tokens.getItemAt(5).length, 2); }); - test('Octal number', async () => { + test('Octal number', () => { const t = new Tokenizer(); - const tokens = t.tokenize('1 0o4 0o077 0o9 0oO'); - assert.equal(tokens.count, 6); + const tokens = t.tokenize('1 0o4 0o077 -0o200 0o9 0oO'); + assert.equal(tokens.count, 7); assert.equal(tokens.getItemAt(0).type, TokenType.Number); assert.equal(tokens.getItemAt(0).length, 1); @@ -224,21 +245,69 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(2).length, 5); assert.equal(tokens.getItemAt(3).type, TokenType.Number); - assert.equal(tokens.getItemAt(3).length, 1); + assert.equal(tokens.getItemAt(3).length, 6); - assert.equal(tokens.getItemAt(4).type, TokenType.Identifier); - assert.equal(tokens.getItemAt(4).length, 2); + assert.equal(tokens.getItemAt(4).type, TokenType.Number); + assert.equal(tokens.getItemAt(4).length, 1); - assert.equal(tokens.getItemAt(5).type, TokenType.Unknown); - assert.equal(tokens.getItemAt(5).length, 3); + assert.equal(tokens.getItemAt(5).type, TokenType.Identifier); + assert.equal(tokens.getItemAt(5).length, 2); + + assert.equal(tokens.getItemAt(6).type, TokenType.Unknown); + assert.equal(tokens.getItemAt(6).length, 3); + }); + test('Decimal number', () => { + const t = new Tokenizer(); + const tokens = t.tokenize('-2147483647 ++2147483647'); + assert.equal(tokens.count, 3); + + assert.equal(tokens.getItemAt(0).type, TokenType.Number); + assert.equal(tokens.getItemAt(0).length, 11); + + assert.equal(tokens.getItemAt(1).type, TokenType.Operator); + assert.equal(tokens.getItemAt(1).length, 1); + + assert.equal(tokens.getItemAt(2).type, TokenType.Number); + assert.equal(tokens.getItemAt(2).length, 11); + }); + test('Decimal number operator', () => { + const t = new Tokenizer(); + const tokens = t.tokenize('a[: -1]'); + assert.equal(tokens.count, 5); + + assert.equal(tokens.getItemAt(3).type, TokenType.Number); + assert.equal(tokens.getItemAt(3).length, 2); + }); + test('Floating point number', () => { + const t = new Tokenizer(); + const tokens = t.tokenize('3.0 .2 ++.3e+12 --.4e1'); + assert.equal(tokens.count, 6); + + assert.equal(tokens.getItemAt(0).type, TokenType.Number); + assert.equal(tokens.getItemAt(0).length, 3); + + assert.equal(tokens.getItemAt(1).type, TokenType.Number); + assert.equal(tokens.getItemAt(1).length, 2); + + assert.equal(tokens.getItemAt(2).type, TokenType.Operator); + assert.equal(tokens.getItemAt(2).length, 1); + + assert.equal(tokens.getItemAt(3).type, TokenType.Number); + assert.equal(tokens.getItemAt(3).length, 7); + + assert.equal(tokens.getItemAt(4).type, TokenType.Operator); + assert.equal(tokens.getItemAt(4).length, 1); + + assert.equal(tokens.getItemAt(5).type, TokenType.Number); + assert.equal(tokens.getItemAt(5).length, 5); }); - test('Operators', async () => { + test('Operators', () => { const text = '< <> << <<= ' + '== != > >> >>= >= <=' + '+ -' + '* ** / /= //=' + '*= += -= **= ' + - '& &= | |= ^ ^='; + '& &= | |= ^ ^= ->'; const tokens = new Tokenizer().tokenize(text); const lengths = [ 1, 2, 2, 3, @@ -246,7 +315,7 @@ suite('Language.Tokenizer', () => { 1, 1, 1, 2, 1, 2, 3, 2, 2, 2, 3, - 1, 2, 1, 2, 1, 2]; + 1, 2, 1, 2, 1, 2, 2]; assert.equal(tokens.count, lengths.length); for (let i = 0; i < tokens.count; i += 1) { const t = tokens.getItemAt(i); diff --git a/src/test/pythonFiles/formatting/pythonGrammar.py b/src/test/pythonFiles/formatting/pythonGrammar.py new file mode 100644 index 000000000000..32b82285c12f --- /dev/null +++ b/src/test/pythonFiles/formatting/pythonGrammar.py @@ -0,0 +1,1572 @@ +# Python test set -- part 1, grammar. +# This just tests whether the parser accepts them all. + +from test.support import check_syntax_error +import inspect +import unittest +import sys +# testing import * +from sys import * + +# different import patterns to check that __annotations__ does not interfere +# with import machinery +import test.ann_module as ann_module +import typing +from collections import ChainMap +from test import ann_module2 +import test + +# These are shared with test_tokenize and other test modules. +# +# Note: since several test cases filter out floats by looking for "e" and ".", +# don't add hexadecimal literals that contain "e" or "E". +VALID_UNDERSCORE_LITERALS = [ + '0_0_0', + '4_2', + '1_0000_0000', + '0b1001_0100', + '0xffff_ffff', + '0o5_7_7', + '1_00_00.5', + '1_00_00.5e5', + '1_00_00e5_1', + '1e1_0', + '.1_4', + '.1_4e1', + '0b_0', + '0x_f', + '0o_5', + '1_00_00j', + '1_00_00.5j', + '1_00_00e5_1j', + '.1_4j', + '(1_2.5+3_3j)', + '(.5_6j)', +] +INVALID_UNDERSCORE_LITERALS = [ + # Trailing underscores: + '0_', + '42_', + '1.4j_', + '0x_', + '0b1_', + '0xf_', + '0o5_', + '0 if 1_Else 1', + # Underscores in the base selector: + '0_b0', + '0_xf', + '0_o5', + # Old-style octal, still disallowed: + '0_7', + '09_99', + # Multiple consecutive underscores: + '4_______2', + '0.1__4', + '0.1__4j', + '0b1001__0100', + '0xffff__ffff', + '0x___', + '0o5__77', + '1e1__0', + '1e1__0j', + # Underscore right before a dot: + '1_.4', + '1_.4j', + # Underscore right after a dot: + '1._4', + '1._4j', + '._5', + '._5j', + # Underscore right after a sign: + '1.0e+_1', + '1.0e+_1j', + # Underscore right before j: + '1.4_j', + '1.4e5_j', + # Underscore right before e: + '1_e1', + '1.4_e1', + '1.4_e1j', + # Underscore right after e: + '1e_1', + '1.4e_1', + '1.4e_1j', + # Complex cases with parens: + '(1+1.5_j_)', + '(1+1.5_j)', +] + + +class TokenTests(unittest.TestCase): + + def test_backslash(self): + # Backslash means line continuation: + x = 1 \ + + 1 + self.assertEqual(x, 2, 'backslash for line continuation') + + # Backslash does not means continuation in comments :\ + x = 0 + self.assertEqual(x, 0, 'backslash ending comment') + + def test_plain_integers(self): + self.assertEqual(type(000), type(0)) + self.assertEqual(0xff, 255) + self.assertEqual(0o377, 255) + self.assertEqual(2147483647, 0o17777777777) + self.assertEqual(0b1001, 9) + # "0x" is not a valid literal + self.assertRaises(SyntaxError, eval, "0x") + from sys import maxsize + if maxsize == 2147483647: + self.assertEqual(-2147483647 - 1, -0o20000000000) + # XXX -2147483648 + self.assertTrue(0o37777777777 > 0) + self.assertTrue(0xffffffff > 0) + self.assertTrue(0b1111111111111111111111111111111 > 0) + for s in ('2147483648', '0o40000000000', '0x100000000', + '0b10000000000000000000000000000000'): + try: + x = eval(s) + except OverflowError: + self.fail("OverflowError on huge integer literal %r" % s) + elif maxsize == 9223372036854775807: + self.assertEqual(-9223372036854775807 - 1, -0o1000000000000000000000) + self.assertTrue(0o1777777777777777777777 > 0) + self.assertTrue(0xffffffffffffffff > 0) + self.assertTrue(0b11111111111111111111111111111111111111111111111111111111111111 > 0) + for s in '9223372036854775808', '0o2000000000000000000000', \ + '0x10000000000000000', \ + '0b100000000000000000000000000000000000000000000000000000000000000': + try: + x = eval(s) + except OverflowError: + self.fail("OverflowError on huge integer literal %r" % s) + else: + self.fail('Weird maxsize value %r' % maxsize) + + def test_long_integers(self): + x = 0 + x = 0xffffffffffffffff + x = 0Xffffffffffffffff + x = 0o77777777777777777 + x = 0O77777777777777777 + x = 123456789012345678901234567890 + x = 0b100000000000000000000000000000000000000000000000000000000000000000000 + x = 0B111111111111111111111111111111111111111111111111111111111111111111111 + + def test_floats(self): + x = 3.14 + x = 314. + x = 0.314 + # XXX x = 000.314 + x = .314 + x = 3e14 + x = 3E14 + x = 3e-14 + x = 3e+14 + x = 3.e14 + x = .3e14 + x = 3.1e4 + + def test_float_exponent_tokenization(self): + # See issue 21642. + self.assertEqual(1 if 1 else 0, 1) + self.assertEqual(1 if 0 else 0, 0) + self.assertRaises(SyntaxError, eval, "0 if 1Else 0") + + def test_underscore_literals(self): + for lit in VALID_UNDERSCORE_LITERALS: + self.assertEqual(eval(lit), eval(lit.replace('_', ''))) + for lit in INVALID_UNDERSCORE_LITERALS: + self.assertRaises(SyntaxError, eval, lit) + # Sanity check: no literal begins with an underscore + self.assertRaises(NameError, eval, "_0") + + def test_string_literals(self): + x = ''; y = ""; self.assertTrue(len(x) == 0 and x == y) + x = '\''; y = "'"; self.assertTrue(len(x) == 1 and x == y and ord(x) == 39) + x = '"'; y = "\""; self.assertTrue(len(x) == 1 and x == y and ord(x) == 34) + x = "doesn't \"shrink\" does it" + y = 'doesn\'t "shrink" does it' + self.assertTrue(len(x) == 24 and x == y) + x = "does \"shrink\" doesn't it" + y = 'does "shrink" doesn\'t it' + self.assertTrue(len(x) == 24 and x == y) + x = """ +The "quick" +brown fox +jumps over +the 'lazy' dog. +""" + y = '\nThe "quick"\nbrown fox\njumps over\nthe \'lazy\' dog.\n' + self.assertEqual(x, y) + y = ''' +The "quick" +brown fox +jumps over +the 'lazy' dog. +''' + self.assertEqual(x, y) + y = "\n\ +The \"quick\"\n\ +brown fox\n\ +jumps over\n\ +the 'lazy' dog.\n\ +" + self.assertEqual(x, y) + y = '\n\ +The \"quick\"\n\ +brown fox\n\ +jumps over\n\ +the \'lazy\' dog.\n\ +' + self.assertEqual(x, y) + + def test_ellipsis(self): + x = ... + self.assertTrue(x is Ellipsis) + self.assertRaises(SyntaxError, eval, ".. .") + + def test_eof_error(self): + samples = ("def foo(", "\ndef foo(", "def foo(\n") + for s in samples: + with self.assertRaises(SyntaxError) as cm: + compile(s, "", "exec") + self.assertIn("unexpected EOF", str(cm.exception)) + +var_annot_global: int # a global annotated is necessary for test_var_annot + +# custom namespace for testing __annotations__ + +class CNS: + def __init__(self): + self._dct = {} + def __setitem__(self, item, value): + self._dct[item.lower()] = value + def __getitem__(self, item): + return self._dct[item] + + +class GrammarTests(unittest.TestCase): + + check_syntax_error = check_syntax_error + + # single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE + # XXX can't test in a script -- this rule is only used when interactive + + # file_input: (NEWLINE | stmt)* ENDMARKER + # Being tested as this very moment this very module + + # expr_input: testlist NEWLINE + # XXX Hard to test -- used only in calls to input() + + def test_eval_input(self): + # testlist ENDMARKER + x = eval('1, 0 or 1') + + def test_var_annot_basics(self): + # all these should be allowed + var1: int = 5 + var2: [int, str] + my_lst = [42] + def one(): + return 1 + int.new_attr: int + [list][0]: type + my_lst[one() - 1]: int = 5 + self.assertEqual(my_lst, [5]) + + def test_var_annot_syntax_errors(self): + # parser pass + check_syntax_error(self, "def f: int") + check_syntax_error(self, "x: int: str") + check_syntax_error(self, "def f():\n" + " nonlocal x: int\n") + # AST pass + check_syntax_error(self, "[x, 0]: int\n") + check_syntax_error(self, "f(): int\n") + check_syntax_error(self, "(x,): int") + check_syntax_error(self, "def f():\n" + " (x, y): int = (1, 2)\n") + # symtable pass + check_syntax_error(self, "def f():\n" + " x: int\n" + " global x\n") + check_syntax_error(self, "def f():\n" + " global x\n" + " x: int\n") + + def test_var_annot_basic_semantics(self): + # execution order + with self.assertRaises(ZeroDivisionError): + no_name[does_not_exist]: no_name_again = 1 / 0 + with self.assertRaises(NameError): + no_name[does_not_exist]: 1 / 0 = 0 + global var_annot_global + + # function semantics + def f(): + st: str = "Hello" + a.b: int = (1, 2) + return st + self.assertEqual(f.__annotations__, {}) + def f_OK(): + x: 1 / 0 + f_OK() + def fbad(): + x: int + print(x) + with self.assertRaises(UnboundLocalError): + fbad() + def f2bad(): + (no_such_global): int + print(no_such_global) + try: + f2bad() + except Exception as e: + self.assertIs(type(e), NameError) + + # class semantics + class C: + __foo: int + s: str = "attr" + z = 2 + def __init__(self, x): + self.x: int = x + self.assertEqual(C.__annotations__, {'_C__foo': int, 's': str}) + with self.assertRaises(NameError): + class CBad: + no_such_name_defined.attr: int = 0 + with self.assertRaises(NameError): + class Cbad2(C): + x: int + x.y: list = [] + + def test_var_annot_metaclass_semantics(self): + class CMeta(type): + @classmethod + def __prepare__(metacls, name, bases, **kwds): + return {'__annotations__': CNS()} + class CC(metaclass=CMeta): + XX: 'ANNOT' + self.assertEqual(CC.__annotations__['xx'], 'ANNOT') + + def test_var_annot_module_semantics(self): + with self.assertRaises(AttributeError): + print(test.__annotations__) + self.assertEqual(ann_module.__annotations__, + {1: 2, 'x': int, 'y': str, 'f': typing.Tuple[int, int]}) + self.assertEqual(ann_module.M.__annotations__, + {'123': 123, 'o': type}) + self.assertEqual(ann_module2.__annotations__, {}) + + def test_var_annot_in_module(self): + # check that functions fail the same way when executed + # outside of module where they were defined + from test.ann_module3 import f_bad_ann, g_bad_ann, D_bad_ann + with self.assertRaises(NameError): + f_bad_ann() + with self.assertRaises(NameError): + g_bad_ann() + with self.assertRaises(NameError): + D_bad_ann(5) + + def test_var_annot_simple_exec(self): + gns = {}; lns = {} + exec("'docstring'\n" + "__annotations__[1] = 2\n" + "x: int = 5\n", gns, lns) + self.assertEqual(lns["__annotations__"], {1: 2, 'x': int}) + with self.assertRaises(KeyError): + gns['__annotations__'] + + def test_var_annot_custom_maps(self): + # tests with custom locals() and __annotations__ + ns = {'__annotations__': CNS()} + exec('X: int; Z: str = "Z"; (w): complex = 1j', ns) + self.assertEqual(ns['__annotations__']['x'], int) + self.assertEqual(ns['__annotations__']['z'], str) + with self.assertRaises(KeyError): + ns['__annotations__']['w'] + nonloc_ns = {} + class CNS2: + def __init__(self): + self._dct = {} + def __setitem__(self, item, value): + nonlocal nonloc_ns + self._dct[item] = value + nonloc_ns[item] = value + def __getitem__(self, item): + return self._dct[item] + exec('x: int = 1', {}, CNS2()) + self.assertEqual(nonloc_ns['__annotations__']['x'], int) + + def test_var_annot_refleak(self): + # complex case: custom locals plus custom __annotations__ + # this was causing refleak + cns = CNS() + nonloc_ns = {'__annotations__': cns} + class CNS2: + def __init__(self): + self._dct = {'__annotations__': cns} + def __setitem__(self, item, value): + nonlocal nonloc_ns + self._dct[item] = value + nonloc_ns[item] = value + def __getitem__(self, item): + return self._dct[item] + exec('X: str', {}, CNS2()) + self.assertEqual(nonloc_ns['__annotations__']['x'], str) + + def test_funcdef(self): + ### [decorators] 'def' NAME parameters ['->' test] ':' suite + ### decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE + ### decorators: decorator+ + ### parameters: '(' [typedargslist] ')' + ### typedargslist: ((tfpdef ['=' test] ',')* + ### ('*' [tfpdef] (',' tfpdef ['=' test])* [',' '**' tfpdef] | '**' tfpdef) + ### | tfpdef ['=' test] (',' tfpdef ['=' test])* [',']) + ### tfpdef: NAME [':' test] + ### varargslist: ((vfpdef ['=' test] ',')* + ### ('*' [vfpdef] (',' vfpdef ['=' test])* [',' '**' vfpdef] | '**' vfpdef) + ### | vfpdef ['=' test] (',' vfpdef ['=' test])* [',']) + ### vfpdef: NAME + def f1(): pass + f1() + f1(*()) + f1(*(), **{}) + def f2(one_argument): pass + def f3(two, arguments): pass + self.assertEqual(f2.__code__.co_varnames, ('one_argument',)) + self.assertEqual(f3.__code__.co_varnames, ('two', 'arguments')) + def a1(one_arg,): pass + def a2(two, args,): pass + def v0(*rest): pass + def v1(a, *rest): pass + def v2(a, b, *rest): pass + + f1() + f2(1) + f2(1,) + f3(1, 2) + f3(1, 2,) + v0() + v0(1) + v0(1,) + v0(1, 2) + v0(1, 2, 3, 4, 5, 6, 7, 8, 9, 0) + v1(1) + v1(1,) + v1(1, 2) + v1(1, 2, 3) + v1(1, 2, 3, 4, 5, 6, 7, 8, 9, 0) + v2(1, 2) + v2(1, 2, 3) + v2(1, 2, 3, 4) + v2(1, 2, 3, 4, 5, 6, 7, 8, 9, 0) + + def d01(a=1): pass + d01() + d01(1) + d01(*(1,)) + d01(*[] or [2]) + d01(*() or (), *{} and (), **() or {}) + d01(**{'a': 2}) + d01(**{'a': 2} or {}) + def d11(a, b=1): pass + d11(1) + d11(1, 2) + d11(1, **{'b': 2}) + def d21(a, b, c=1): pass + d21(1, 2) + d21(1, 2, 3) + d21(*(1, 2, 3)) + d21(1, *(2, 3)) + d21(1, 2, *(3,)) + d21(1, 2, **{'c': 3}) + def d02(a=1, b=2): pass + d02() + d02(1) + d02(1, 2) + d02(*(1, 2)) + d02(1, *(2,)) + d02(1, **{'b': 2}) + d02(**{'a': 1, 'b': 2}) + def d12(a, b=1, c=2): pass + d12(1) + d12(1, 2) + d12(1, 2, 3) + def d22(a, b, c=1, d=2): pass + d22(1, 2) + d22(1, 2, 3) + d22(1, 2, 3, 4) + def d01v(a=1, *rest): pass + d01v() + d01v(1) + d01v(1, 2) + d01v(*(1, 2, 3, 4)) + d01v(*(1,)) + d01v(**{'a': 2}) + def d11v(a, b=1, *rest): pass + d11v(1) + d11v(1, 2) + d11v(1, 2, 3) + def d21v(a, b, c=1, *rest): pass + d21v(1, 2) + d21v(1, 2, 3) + d21v(1, 2, 3, 4) + d21v(*(1, 2, 3, 4)) + d21v(1, 2, **{'c': 3}) + def d02v(a=1, b=2, *rest): pass + d02v() + d02v(1) + d02v(1, 2) + d02v(1, 2, 3) + d02v(1, *(2, 3, 4)) + d02v(**{'a': 1, 'b': 2}) + def d12v(a, b=1, c=2, *rest): pass + d12v(1) + d12v(1, 2) + d12v(1, 2, 3) + d12v(1, 2, 3, 4) + d12v(*(1, 2, 3, 4)) + d12v(1, 2, *(3, 4, 5)) + d12v(1, *(2,), **{'c': 3}) + def d22v(a, b, c=1, d=2, *rest): pass + d22v(1, 2) + d22v(1, 2, 3) + d22v(1, 2, 3, 4) + d22v(1, 2, 3, 4, 5) + d22v(*(1, 2, 3, 4)) + d22v(1, 2, *(3, 4, 5)) + d22v(1, *(2, 3), **{'d': 4}) + + # keyword argument type tests + try: + str('x', **{b'foo': 1}) + except TypeError: + pass + else: + self.fail('Bytes should not work as keyword argument names') + # keyword only argument tests + def pos0key1(*, key): return key + pos0key1(key=100) + def pos2key2(p1, p2, *, k1, k2=100): return p1, p2, k1, k2 + pos2key2(1, 2, k1=100) + pos2key2(1, 2, k1=100, k2=200) + pos2key2(1, 2, k2=100, k1=200) + def pos2key2dict(p1, p2, *, k1=100, k2, **kwarg): return p1, p2, k1, k2, kwarg + pos2key2dict(1, 2, k2=100, tokwarg1=100, tokwarg2=200) + pos2key2dict(1, 2, tokwarg1=100, tokwarg2=200, k2=100) + + self.assertRaises(SyntaxError, eval, "def f(*): pass") + self.assertRaises(SyntaxError, eval, "def f(*,): pass") + self.assertRaises(SyntaxError, eval, "def f(*, **kwds): pass") + + # keyword arguments after *arglist + def f(*args, **kwargs): + return args, kwargs + self.assertEqual(f(1, x=2, *[3, 4], y=5), ((1, 3, 4), + {'x': 2, 'y': 5})) + self.assertEqual(f(1, *(2, 3), 4), ((1, 2, 3, 4), {})) + self.assertRaises(SyntaxError, eval, "f(1, x=2, *(3,4), x=5)") + self.assertEqual(f(**{'eggs': 'scrambled', 'spam': 'fried'}), + ((), {'eggs': 'scrambled', 'spam': 'fried'})) + self.assertEqual(f(spam='fried', **{'eggs': 'scrambled'}), + ((), {'eggs': 'scrambled', 'spam': 'fried'})) + + # Check ast errors in *args and *kwargs + check_syntax_error(self, "f(*g(1=2))") + check_syntax_error(self, "f(**g(1=2))") + + # argument annotation tests + def f(x) -> list: pass + self.assertEqual(f.__annotations__, {'return': list}) + def f(x: int): pass + self.assertEqual(f.__annotations__, {'x': int}) + def f(*x: str): pass + self.assertEqual(f.__annotations__, {'x': str}) + def f(**x: float): pass + self.assertEqual(f.__annotations__, {'x': float}) + def f(x, y: 1 + 2): pass + self.assertEqual(f.__annotations__, {'y': 3}) + def f(a, b: 1, c: 2, d): pass + self.assertEqual(f.__annotations__, {'b': 1, 'c': 2}) + def f(a, b: 1, c: 2, d, e: 3 = 4, f=5, *g: 6): pass + self.assertEqual(f.__annotations__, + {'b': 1, 'c': 2, 'e': 3, 'g': 6}) + def f(a, b: 1, c: 2, d, e: 3 = 4, f=5, *g: 6, h: 7, i=8, j: 9 = 10, + **k: 11) -> 12: pass + self.assertEqual(f.__annotations__, + {'b': 1, 'c': 2, 'e': 3, 'g': 6, 'h': 7, 'j': 9, + 'k': 11, 'return': 12}) + # Check for issue #20625 -- annotations mangling + class Spam: + def f(self, *, __kw: 1): + pass + class Ham(Spam): pass + self.assertEqual(Spam.f.__annotations__, {'_Spam__kw': 1}) + self.assertEqual(Ham.f.__annotations__, {'_Spam__kw': 1}) + # Check for SF Bug #1697248 - mixing decorators and a return annotation + def null(x): return x + @null + def f(x) -> list: pass + self.assertEqual(f.__annotations__, {'return': list}) + + # test closures with a variety of opargs + closure = 1 + def f(): return closure + def f(x=1): return closure + def f(*, k=1): return closure + def f() -> int: return closure + + # Check trailing commas are permitted in funcdef argument list + def f(a,): pass + def f(*args,): pass + def f(**kwds,): pass + def f(a, *args,): pass + def f(a, **kwds,): pass + def f(*args, b,): pass + def f(*, b,): pass + def f(*args, **kwds,): pass + def f(a, *args, b,): pass + def f(a, *, b,): pass + def f(a, *args, **kwds,): pass + def f(*args, b, **kwds,): pass + def f(*, b, **kwds,): pass + def f(a, *args, b, **kwds,): pass + def f(a, *, b, **kwds,): pass + + def test_lambdef(self): + ### lambdef: 'lambda' [varargslist] ':' test + l1 = lambda: 0 + self.assertEqual(l1(), 0) + l2 = lambda: a[d] # XXX just testing the expression + l3 = lambda: [2 < x for x in [-1, 3, 0]] + self.assertEqual(l3(), [0, 1, 0]) + l4 = lambda x = lambda y = lambda z = 1: z: y(): x() + self.assertEqual(l4(), 1) + l5 = lambda x, y, z=2: x + y + z + self.assertEqual(l5(1, 2), 5) + self.assertEqual(l5(1, 2, 3), 6) + check_syntax_error(self, "lambda x: x = 2") + check_syntax_error(self, "lambda (None,): None") + l6 = lambda x, y, *, k=20: x + y + k + self.assertEqual(l6(1, 2), 1 + 2 + 20) + self.assertEqual(l6(1, 2, k=10), 1 + 2 + 10) + + # check that trailing commas are permitted + l10 = lambda a,: 0 + l11 = lambda *args,: 0 + l12 = lambda **kwds,: 0 + l13 = lambda a, *args,: 0 + l14 = lambda a, **kwds,: 0 + l15 = lambda *args, b,: 0 + l16 = lambda *, b,: 0 + l17 = lambda *args, **kwds,: 0 + l18 = lambda a, *args, b,: 0 + l19 = lambda a, *, b,: 0 + l20 = lambda a, *args, **kwds,: 0 + l21 = lambda *args, b, **kwds,: 0 + l22 = lambda *, b, **kwds,: 0 + l23 = lambda a, *args, b, **kwds,: 0 + l24 = lambda a, *, b, **kwds,: 0 + + + ### stmt: simple_stmt | compound_stmt + # Tested below + + def test_simple_stmt(self): + ### simple_stmt: small_stmt (';' small_stmt)* [';'] + x = 1; pass; del x + def foo(): + # verify statements that end with semi-colons + x = 1; pass; del x; + foo() + + ### small_stmt: expr_stmt | pass_stmt | del_stmt | flow_stmt | import_stmt | global_stmt | access_stmt + # Tested below + + def test_expr_stmt(self): + # (exprlist '=')* exprlist + 1 + 1, 2, 3 + x = 1 + x = 1, 2, 3 + x = y = z = 1, 2, 3 + x, y, z=1, 2, 3 + abc=a, b, c=x, y, z=xyz = 1, 2, (3, 4) + + check_syntax_error(self, "x + 1 = 1") + check_syntax_error(self, "a + 1 = b + 2") + + # Check the heuristic for print & exec covers significant cases + # As well as placing some limits on false positives + def test_former_statements_refer_to_builtins(self): + keywords = "print", "exec" + # Cases where we want the custom error + cases = [ + "{} foo", + "{} {{1:foo}}", + "if 1: {} foo", + "if 1: {} {{1:foo}}", + "if 1:\n {} foo", + "if 1:\n {} {{1:foo}}", + ] + for keyword in keywords: + custom_msg = "call to '{}'".format(keyword) + for case in cases: + source = case.format(keyword) + with self.subTest(source=source): + with self.assertRaisesRegex(SyntaxError, custom_msg): + exec(source) + source = source.replace("foo", "(foo.)") + with self.subTest(source=source): + with self.assertRaisesRegex(SyntaxError, "invalid syntax"): + exec(source) + + def test_del_stmt(self): + # 'del' exprlist + abc = [1, 2, 3] + x, y, z=abc + xyz = x, y, z + + del abc + del x, y, (z, xyz) + + def test_pass_stmt(self): + # 'pass' + pass + + # flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt + # Tested below + + def test_break_stmt(self): + # 'break' + while 1: break + + def test_continue_stmt(self): + # 'continue' + i = 1 + while i: i = 0; continue + + msg = "" + while not msg: + msg = "ok" + try: + continue + msg = "continue failed to continue inside try" + except: + msg = "continue inside try called except block" + if msg != "ok": + self.fail(msg) + + msg = "" + while not msg: + msg = "finally block not called" + try: + continue + finally: + msg = "ok" + if msg != "ok": + self.fail(msg) + + def test_break_continue_loop(self): + # This test warrants an explanation. It is a test specifically for SF bugs + # #463359 and #462937. The bug is that a 'break' statement executed or + # exception raised inside a try/except inside a loop, *after* a continue + # statement has been executed in that loop, will cause the wrong number of + # arguments to be popped off the stack and the instruction pointer reset to + # a very small number (usually 0.) Because of this, the following test + # *must* written as a function, and the tracking vars *must* be function + # arguments with default values. Otherwise, the test will loop and loop. + + def test_inner(extra_burning_oil=1, count=0): + big_hippo = 2 + while big_hippo: + count += 1 + try: + if extra_burning_oil and big_hippo == 1: + extra_burning_oil -= 1 + break + big_hippo -= 1 + continue + except: + raise + if count > 2 or big_hippo != 1: + self.fail("continue then break in try/except in loop broken!") + test_inner() + + def test_return(self): + # 'return' [testlist] + def g1(): return + def g2(): return 1 + g1() + x = g2() + check_syntax_error(self, "class foo:return 1") + + def test_break_in_finally(self): + count = 0 + while count < 2: + count += 1 + try: + pass + finally: + break + self.assertEqual(count, 1) + + count = 0 + while count < 2: + count += 1 + try: + continue + finally: + break + self.assertEqual(count, 1) + + count = 0 + while count < 2: + count += 1 + try: + 1 / 0 + finally: + break + self.assertEqual(count, 1) + + for count in [0, 1]: + self.assertEqual(count, 0) + try: + pass + finally: + break + self.assertEqual(count, 0) + + for count in [0, 1]: + self.assertEqual(count, 0) + try: + continue + finally: + break + self.assertEqual(count, 0) + + for count in [0, 1]: + self.assertEqual(count, 0) + try: + 1 / 0 + finally: + break + self.assertEqual(count, 0) + + def test_continue_in_finally(self): + count = 0 + while count < 2: + count += 1 + try: + pass + finally: + continue + break + self.assertEqual(count, 2) + + count = 0 + while count < 2: + count += 1 + try: + break + finally: + continue + self.assertEqual(count, 2) + + count = 0 + while count < 2: + count += 1 + try: + 1 / 0 + finally: + continue + break + self.assertEqual(count, 2) + + for count in [0, 1]: + try: + pass + finally: + continue + break + self.assertEqual(count, 1) + + for count in [0, 1]: + try: + break + finally: + continue + self.assertEqual(count, 1) + + for count in [0, 1]: + try: + 1 / 0 + finally: + continue + break + self.assertEqual(count, 1) + + def test_return_in_finally(self): + def g1(): + try: + pass + finally: + return 1 + self.assertEqual(g1(), 1) + + def g2(): + try: + return 2 + finally: + return 3 + self.assertEqual(g2(), 3) + + def g3(): + try: + 1 / 0 + finally: + return 4 + self.assertEqual(g3(), 4) + + def test_yield(self): + # Allowed as standalone statement + def g(): yield 1 + def g(): yield from () + # Allowed as RHS of assignment + def g(): x = yield 1 + def g(): x = yield from () + # Ordinary yield accepts implicit tuples + def g(): yield 1, 1 + def g(): x = yield 1, 1 + # 'yield from' does not + check_syntax_error(self, "def g(): yield from (), 1") + check_syntax_error(self, "def g(): x = yield from (), 1") + # Requires parentheses as subexpression + def g(): 1, (yield 1) + def g(): 1, (yield from ()) + check_syntax_error(self, "def g(): 1, yield 1") + check_syntax_error(self, "def g(): 1, yield from ()") + # Requires parentheses as call argument + def g(): f((yield 1)) + def g(): f((yield 1), 1) + def g(): f((yield from ())) + def g(): f((yield from ()), 1) + check_syntax_error(self, "def g(): f(yield 1)") + check_syntax_error(self, "def g(): f(yield 1, 1)") + check_syntax_error(self, "def g(): f(yield from ())") + check_syntax_error(self, "def g(): f(yield from (), 1)") + # Not allowed at top level + check_syntax_error(self, "yield") + check_syntax_error(self, "yield from") + # Not allowed at class scope + check_syntax_error(self, "class foo:yield 1") + check_syntax_error(self, "class foo:yield from ()") + # Check annotation refleak on SyntaxError + check_syntax_error(self, "def g(a:(yield)): pass") + + def test_yield_in_comprehensions(self): + # Check yield in comprehensions + def g(): [x for x in [(yield 1)]] + def g(): [x for x in [(yield from ())]] + + check = self.check_syntax_error + check("def g(): [(yield x) for x in ()]", + "'yield' inside list comprehension") + check("def g(): [x for x in () if not (yield x)]", + "'yield' inside list comprehension") + check("def g(): [y for x in () for y in [(yield x)]]", + "'yield' inside list comprehension") + check("def g(): {(yield x) for x in ()}", + "'yield' inside set comprehension") + check("def g(): {(yield x): x for x in ()}", + "'yield' inside dict comprehension") + check("def g(): {x: (yield x) for x in ()}", + "'yield' inside dict comprehension") + check("def g(): ((yield x) for x in ())", + "'yield' inside generator expression") + check("def g(): [(yield from x) for x in ()]", + "'yield' inside list comprehension") + check("class C: [(yield x) for x in ()]", + "'yield' inside list comprehension") + check("[(yield x) for x in ()]", + "'yield' inside list comprehension") + + def test_raise(self): + # 'raise' test [',' test] + try: raise RuntimeError('just testing') + except RuntimeError: pass + try: raise KeyboardInterrupt + except KeyboardInterrupt: pass + + def test_import(self): + # 'import' dotted_as_names + import sys + import time, sys + # 'from' dotted_name 'import' ('*' | '(' import_as_names ')' | import_as_names) + from time import time + from time import (time) + # not testable inside a function, but already done at top of the module + # from sys import * + from sys import path, argv + from sys import (path, argv) + from sys import (path, argv,) + + def test_global(self): + # 'global' NAME (',' NAME)* + global a + global a, b + global one, two, three, four, five, six, seven, eight, nine, ten + + def test_nonlocal(self): + # 'nonlocal' NAME (',' NAME)* + x = 0 + y = 0 + def f(): + nonlocal x + nonlocal x, y + + def test_assert(self): + # assertTruestmt: 'assert' test [',' test] + assert 1 + assert 1, 1 + assert lambda x: x + assert 1, lambda x: x + 1 + + try: + assert True + except AssertionError as e: + self.fail("'assert True' should not have raised an AssertionError") + + try: + assert True, 'this should always pass' + except AssertionError as e: + self.fail("'assert True, msg' should not have " + "raised an AssertionError") + + # these tests fail if python is run with -O, so check __debug__ + @unittest.skipUnless(__debug__, "Won't work if __debug__ is False") + def testAssert2(self): + try: + assert 0, "msg" + except AssertionError as e: + self.assertEqual(e.args[0], "msg") + else: + self.fail("AssertionError not raised by assert 0") + + try: + assert False + except AssertionError as e: + self.assertEqual(len(e.args), 0) + else: + self.fail("AssertionError not raised by 'assert False'") + + + ### compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | funcdef | classdef + # Tested below + + def test_if(self): + # 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite] + if 1: pass + if 1: pass + else: pass + if 0: pass + elif 0: pass + if 0: pass + elif 0: pass + elif 0: pass + elif 0: pass + else: pass + + def test_while(self): + # 'while' test ':' suite ['else' ':' suite] + while 0: pass + while 0: pass + else: pass + + # Issue1920: "while 0" is optimized away, + # ensure that the "else" clause is still present. + x = 0 + while 0: + x = 1 + else: + x = 2 + self.assertEqual(x, 2) + + def test_for(self): + # 'for' exprlist 'in' exprlist ':' suite ['else' ':' suite] + for i in 1, 2, 3: pass + for i, j, k in (): pass + else: pass + class Squares: + def __init__(self, max): + self.max = max + self.sofar = [] + def __len__(self): return len(self.sofar) + def __getitem__(self, i): + if not 0 <= i < self.max: raise IndexError + n = len(self.sofar) + while n <= i: + self.sofar.append(n * n) + n = n + 1 + return self.sofar[i] + n = 0 + for x in Squares(10): n = n + x + if n != 285: + self.fail('for over growing sequence') + + result = [] + for x, in [(1,), (2,), (3,)]: + result.append(x) + self.assertEqual(result, [1, 2, 3]) + + def test_try(self): + ### try_stmt: 'try' ':' suite (except_clause ':' suite)+ ['else' ':' suite] + ### | 'try' ':' suite 'finally' ':' suite + ### except_clause: 'except' [expr ['as' expr]] + try: + 1 / 0 + except ZeroDivisionError: + pass + else: + pass + try: 1 / 0 + except EOFError: pass + except TypeError as msg: pass + except: pass + else: pass + try: 1 / 0 + except (EOFError, TypeError, ZeroDivisionError): pass + try: 1 / 0 + except (EOFError, TypeError, ZeroDivisionError) as msg: pass + try: pass + finally: pass + + def test_suite(self): + # simple_stmt | NEWLINE INDENT NEWLINE* (stmt NEWLINE*)+ DEDENT + if 1: pass + if 1: + pass + if 1: + # + # + # + pass + pass + # + pass + # + + def test_test(self): + ### and_test ('or' and_test)* + ### and_test: not_test ('and' not_test)* + ### not_test: 'not' not_test | comparison + if not 1: pass + if 1 and 1: pass + if 1 or 1: pass + if not not not 1: pass + if not 1 and 1 and 1: pass + if 1 and 1 or 1 and 1 and 1 or not 1 and 1: pass + + def test_comparison(self): + ### comparison: expr (comp_op expr)* + ### comp_op: '<'|'>'|'=='|'>='|'<='|'!='|'in'|'not' 'in'|'is'|'is' 'not' + if 1: pass + x = (1 == 1) + if 1 == 1: pass + if 1 != 1: pass + if 1 < 1: pass + if 1 > 1: pass + if 1 <= 1: pass + if 1 >= 1: pass + if 1 is 1: pass + if 1 is not 1: pass + if 1 in (): pass + if 1 not in (): pass + if 1 < 1 > 1 == 1 >= 1 <= 1 != 1 in 1 not in 1 is 1 is not 1: pass + + def test_binary_mask_ops(self): + x = 1 & 1 + x = 1 ^ 1 + x = 1 | 1 + + def test_shift_ops(self): + x = 1 << 1 + x = 1 >> 1 + x = 1 << 1 >> 1 + + def test_additive_ops(self): + x = 1 + x = 1 + 1 + x = 1 - 1 - 1 + x = 1 - 1 + 1 - 1 + 1 + + def test_multiplicative_ops(self): + x = 1 * 1 + x = 1 / 1 + x = 1 % 1 + x = 1 / 1 * 1 % 1 + + def test_unary_ops(self): + x = +1 + x = -1 + x = ~1 + x = ~1 ^ 1 & 1 | 1 & 1 ^ -1 + x = -1 * 1 / 1 + 1 * 1 - -1 * 1 + + def test_selectors(self): + ### trailer: '(' [testlist] ')' | '[' subscript ']' | '.' NAME + ### subscript: expr | [expr] ':' [expr] + + import sys, time + c = sys.path[0] + x = time.time() + x = sys.modules['time'].time() + a = '01234' + c = a[0] + c = a[-1] + s = a[0:5] + s = a[:5] + s = a[0:] + s = a[:] + s = a[-5:] + s = a[:-1] + s = a[-4:-3] + # A rough test of SF bug 1333982. http://python.org/sf/1333982 + # The testing here is fairly incomplete. + # Test cases should include: commas with 1 and 2 colons + d = {} + d[1] = 1 + d[1,] = 2 + d[1, 2] = 3 + d[1, 2, 3] = 4 + L = list(d) + L.sort(key=lambda x: (type(x).__name__, x)) + self.assertEqual(str(L), '[1, (1,), (1, 2), (1, 2, 3)]') + + def test_atoms(self): + ### atom: '(' [testlist] ')' | '[' [testlist] ']' | '{' [dictsetmaker] '}' | NAME | NUMBER | STRING + ### dictsetmaker: (test ':' test (',' test ':' test)* [',']) | (test (',' test)* [',']) + + x = (1) + x = (1 or 2 or 3) + x = (1 or 2 or 3, 2, 3) + + x = [] + x = [1] + x = [1 or 2 or 3] + x = [1 or 2 or 3, 2, 3] + x = [] + + x = {} + x = {'one': 1} + x = {'one': 1,} + x = {'one' or 'two': 1 or 2} + x = {'one': 1, 'two': 2} + x = {'one': 1, 'two': 2,} + x = {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6} + + x = {'one'} + x = {'one', 1,} + x = {'one', 'two', 'three'} + x = {2, 3, 4,} + + x = x + x = 'x' + x = 123 + + ### exprlist: expr (',' expr)* [','] + ### testlist: test (',' test)* [','] + # These have been exercised enough above + + def test_classdef(self): + # 'class' NAME ['(' [testlist] ')'] ':' suite + class B: pass + class B2(): pass + class C1(B): pass + class C2(B): pass + class D(C1, C2, B): pass + class C: + def meth1(self): pass + def meth2(self, arg): pass + def meth3(self, a1, a2): pass + + # decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE + # decorators: decorator+ + # decorated: decorators (classdef | funcdef) + def class_decorator(x): return x + @class_decorator + class G: pass + + def test_dictcomps(self): + # dictorsetmaker: ( (test ':' test (comp_for | + # (',' test ':' test)* [','])) | + # (test (comp_for | (',' test)* [','])) ) + nums = [1, 2, 3] + self.assertEqual({i: i + 1 for i in nums}, {1: 2, 2: 3, 3: 4}) + + def test_listcomps(self): + # list comprehension tests + nums = [1, 2, 3, 4, 5] + strs = ["Apple", "Banana", "Coconut"] + spcs = [" Apple", " Banana ", "Coco nut "] + + self.assertEqual([s.strip() for s in spcs], ['Apple', 'Banana', 'Coco nut']) + self.assertEqual([3 * x for x in nums], [3, 6, 9, 12, 15]) + self.assertEqual([x for x in nums if x > 2], [3, 4, 5]) + self.assertEqual([(i, s) for i in nums for s in strs], + [(1, 'Apple'), (1, 'Banana'), (1, 'Coconut'), + (2, 'Apple'), (2, 'Banana'), (2, 'Coconut'), + (3, 'Apple'), (3, 'Banana'), (3, 'Coconut'), + (4, 'Apple'), (4, 'Banana'), (4, 'Coconut'), + (5, 'Apple'), (5, 'Banana'), (5, 'Coconut')]) + self.assertEqual([(i, s) for i in nums for s in [f for f in strs if "n" in f]], + [(1, 'Banana'), (1, 'Coconut'), (2, 'Banana'), (2, 'Coconut'), + (3, 'Banana'), (3, 'Coconut'), (4, 'Banana'), (4, 'Coconut'), + (5, 'Banana'), (5, 'Coconut')]) + self.assertEqual([(lambda a:[a ** i for i in range(a + 1)])(j) for j in range(5)], + [[1], [1, 1], [1, 2, 4], [1, 3, 9, 27], [1, 4, 16, 64, 256]]) + + def test_in_func(l): + return [0 < x < 3 for x in l if x > 2] + + self.assertEqual(test_in_func(nums), [False, False, False]) + + def test_nested_front(): + self.assertEqual([[y for y in [x, x + 1]] for x in [1, 3, 5]], + [[1, 2], [3, 4], [5, 6]]) + + test_nested_front() + + check_syntax_error(self, "[i, s for i in nums for s in strs]") + check_syntax_error(self, "[x if y]") + + suppliers = [ + (1, "Boeing"), + (2, "Ford"), + (3, "Macdonalds") + ] + + parts = [ + (10, "Airliner"), + (20, "Engine"), + (30, "Cheeseburger") + ] + + suppart = [ + (1, 10), (1, 20), (2, 20), (3, 30) + ] + + x = [ + (sname, pname) + for (sno, sname) in suppliers + for (pno, pname) in parts + for (sp_sno, sp_pno) in suppart + if sno == sp_sno and pno == sp_pno + ] + + self.assertEqual(x, [('Boeing', 'Airliner'), ('Boeing', 'Engine'), ('Ford', 'Engine'), + ('Macdonalds', 'Cheeseburger')]) + + def test_genexps(self): + # generator expression tests + g = ([x for x in range(10)] for x in range(1)) + self.assertEqual(next(g), [x for x in range(10)]) + try: + next(g) + self.fail('should produce StopIteration exception') + except StopIteration: + pass + + a = 1 + try: + g = (a for d in a) + next(g) + self.fail('should produce TypeError') + except TypeError: + pass + + self.assertEqual(list((x, y) for x in 'abcd' for y in 'abcd'), [(x, y) for x in 'abcd' for y in 'abcd']) + self.assertEqual(list((x, y) for x in 'ab' for y in 'xy'), [(x, y) for x in 'ab' for y in 'xy']) + + a = [x for x in range(10)] + b = (x for x in (y for y in a)) + self.assertEqual(sum(b), sum([x for x in range(10)])) + + self.assertEqual(sum(x ** 2 for x in range(10)), sum([x ** 2 for x in range(10)])) + self.assertEqual(sum(x * x for x in range(10) if x % 2), sum([x * x for x in range(10) if x % 2])) + self.assertEqual(sum(x for x in (y for y in range(10))), sum([x for x in range(10)])) + self.assertEqual(sum(x for x in (y for y in (z for z in range(10)))), sum([x for x in range(10)])) + self.assertEqual(sum(x for x in [y for y in (z for z in range(10))]), sum([x for x in range(10)])) + self.assertEqual(sum(x for x in (y for y in (z for z in range(10) if True)) if True), sum([x for x in range(10)])) + self.assertEqual(sum(x for x in (y for y in (z for z in range(10) if True) if False) if True), 0) + check_syntax_error(self, "foo(x for x in range(10), 100)") + check_syntax_error(self, "foo(100, x for x in range(10))") + + def test_comprehension_specials(self): + # test for outmost iterable precomputation + x = 10; g = (i for i in range(x)); x = 5 + self.assertEqual(len(list(g)), 10) + + # This should hold, since we're only precomputing outmost iterable. + x = 10; t = False; g = ((i, j) for i in range(x) if t for j in range(x)) + x = 5; t = True; + self.assertEqual([(i, j) for i in range(10) for j in range(5)], list(g)) + + # Grammar allows multiple adjacent 'if's in listcomps and genexps, + # even though it's silly. Make sure it works (ifelse broke this.) + self.assertEqual([x for x in range(10) if x % 2 if x % 3], [1, 5, 7]) + self.assertEqual(list(x for x in range(10) if x % 2 if x % 3), [1, 5, 7]) + + # verify unpacking single element tuples in listcomp/genexp. + self.assertEqual([x for x, in [(4,), (5,), (6,)]], [4, 5, 6]) + self.assertEqual(list(x for x, in [(7,), (8,), (9,)]), [7, 8, 9]) + + def test_with_statement(self): + class manager(object): + def __enter__(self): + return (1, 2) + def __exit__(self, *args): + pass + + with manager(): + pass + with manager() as x: + pass + with manager() as (x, y): + pass + with manager(), manager(): + pass + with manager() as x, manager() as y: + pass + with manager() as x, manager(): + pass + + def test_if_else_expr(self): + # Test ifelse expressions in various cases + def _checkeval(msg, ret): + "helper to check that evaluation of expressions is done correctly" + print(msg) + return ret + + # the next line is not allowed anymore + #self.assertEqual([ x() for x in lambda: True, lambda: False if x() ], [True]) + self.assertEqual([x() for x in (lambda:True, lambda:False) if x()], [True]) + self.assertEqual([x(False) for x in (lambda x:False if x else True, lambda x:True if x else False) if x(False)], [True]) + self.assertEqual((5 if 1 else _checkeval("check 1", 0)), 5) + self.assertEqual((_checkeval("check 2", 0) if 0 else 5), 5) + self.assertEqual((5 and 6 if 0 else 1), 1) + self.assertEqual(((5 and 6) if 0 else 1), 1) + self.assertEqual((5 and (6 if 1 else 1)), 6) + self.assertEqual((0 or _checkeval("check 3", 2) if 0 else 3), 3) + self.assertEqual((1 or _checkeval("check 4", 2) if 1 else _checkeval("check 5", 3)), 1) + self.assertEqual((0 or 5 if 1 else _checkeval("check 6", 3)), 5) + self.assertEqual((not 5 if 1 else 1), False) + self.assertEqual((not 5 if 0 else 1), 1) + self.assertEqual((6 + 1 if 1 else 2), 7) + self.assertEqual((6 - 1 if 1 else 2), 5) + self.assertEqual((6 * 2 if 1 else 4), 12) + self.assertEqual((6 / 2 if 1 else 3), 3) + self.assertEqual((6 < 4 if 0 else 2), 2) + + def test_paren_evaluation(self): + self.assertEqual(16 // (4 // 2), 8) + self.assertEqual((16 // 4) // 2, 2) + self.assertEqual(16 // 4 // 2, 2) + self.assertTrue(False is (2 is 3)) + self.assertFalse((False is 2) is 3) + self.assertFalse(False is 2 is 3) + + def test_matrix_mul(self): + # This is not intended to be a comprehensive test, rather just to be few + # samples of the @ operator in test_grammar.py. + class M: + def __matmul__(self, o): + return 4 + def __imatmul__(self, o): + self.other = o + return self + m = M() + self.assertEqual(m@m, 4) + m @= 42 + self.assertEqual(m.other, 42) + + def test_async_await(self): + async def test(): + def sum(): + pass + if 1: + await someobj() + + self.assertEqual(test.__name__, 'test') + self.assertTrue(bool(test.__code__.co_flags & inspect.CO_COROUTINE)) + + def decorator(func): + setattr(func, '_marked', True) + return func + + @decorator + async def test2(): + return 22 + self.assertTrue(test2._marked) + self.assertEqual(test2.__name__, 'test2') + self.assertTrue(bool(test2.__code__.co_flags & inspect.CO_COROUTINE)) + + def test_async_for(self): + class Done(Exception): pass + + class AIter: + def __aiter__(self): + return self + async def __anext__(self): + raise StopAsyncIteration + + async def foo(): + async for i in AIter(): + pass + async for i, j in AIter(): + pass + async for i in AIter(): + pass + else: + pass + raise Done + + with self.assertRaises(Done): + foo().send(None) + + def test_async_with(self): + class Done(Exception): pass + + class manager: + async def __aenter__(self): + return (1, 2) + async def __aexit__(self, *exc): + return False + + async def foo(): + async with manager(): + pass + async with manager() as x: + pass + async with manager() as (x, y): + pass + async with manager(), manager(): + pass + async with manager() as x, manager() as y: + pass + async with manager() as x, manager(): + pass + raise Done + + with self.assertRaises(Done): + foo().send(None) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/test/signature/signature.ptvs.test.ts b/src/test/signature/signature.ptvs.test.ts index ad8e58508342..8e2f630756d4 100644 --- a/src/test/signature/signature.ptvs.test.ts +++ b/src/test/signature/signature.ptvs.test.ts @@ -79,8 +79,7 @@ suite('Signatures (Analysis Engine)', () => { new SignatureHelpResult(0, 8, 1, 1, 'stop'), new SignatureHelpResult(0, 9, 1, 1, 'stop'), new SignatureHelpResult(0, 10, 1, 1, 'stop'), - new SignatureHelpResult(0, 11, 1, 2, 'step'), - new SignatureHelpResult(1, 0, 1, 2, 'step') + new SignatureHelpResult(0, 11, 1, 2, 'step') ]; const document = await openDocument(path.join(autoCompPath, 'basicSig.py'));