diff --git a/.editorconfig b/.editorconfig index 10b09a5924b6..4f92a467d0ef 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,6 +8,7 @@ root = true indent_style = space indent_size = 4 trim_trailing_whitespace = true +insert_final_newline = true # The indent size used in the `package.json` file cannot be changed # https://github.com/npm/npm/pull/3180#issuecomment-16336516 diff --git a/.gitignore b/.gitignore index 058d4e514ff2..a5334358937a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ node_modules .vscode-test __pycache__ npm-debug.log +**/.mypy_cache/** diff --git a/.vscode/settings.json b/.vscode/settings.json index 3210d5f385f9..1884ba9a0255 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,7 +5,9 @@ "**/*.pyc": true, "**/__pycache__": true, "node_modules": true, - ".vscode-test":true + ".vscode-test": true, + "**/.mypy_cache/**": true, + "**/.ropeproject/**": true }, "search.exclude": { "out": true // set this to false to include "out" folder in search results diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 9fe3969ccf07..bfcb80a350f7 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -53,6 +53,18 @@ "fileLocation": "relative" } ] + }, + { + "label": "lint-staged", + "type": "npm", + "script": "lint-staged", + "problemMatcher": [ + "$tsc", + { + "base": "$tslint5", + "fileLocation": "relative" + } + ] } ] } diff --git a/gulpfile.js b/gulpfile.js index 1dfa803235a2..052c3aa979fd 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -80,7 +80,7 @@ function reportFailures(failures) { const line = position.lineAndCharacter ? position.lineAndCharacter.line : position.line; const character = position.lineAndCharacter ? position.lineAndCharacter.character : position.character; - // Output in format similar to tslint for the linter to pickup + // Output in format similar to tslint for the linter to pickup. console.error(`ERROR: (${failure.ruleName}) ${relative(__dirname, name)}[${line + 1}, ${character + 1}]: ${failure.failure}`); }); } @@ -103,9 +103,9 @@ const hygiene = exports.hygiene = (some, options) => { .split(/\r\n|\r|\n/) .forEach((line, i) => { if (/^\s*$/.test(line)) { - // empty or whitespace lines are OK + // Empty or whitespace lines are OK. } else if (/^(\s\s\s\s)+.*/.test(line)) { - // good indent + // Good indent. } else if (/^[\t]+.*/.test(line)) { console.error(file.relative + '(' + (i + 1) + ',1): Bad whitespace indentation'); errorCount++; @@ -118,9 +118,10 @@ const hygiene = exports.hygiene = (some, options) => { const formatting = es.map(function (file, cb) { tsfmt.processString(file.path, file.contents.toString('utf8'), { verify: true, - tsfmt: true, - editorconfig: true - // verbose: true + tsconfig: true, + tslint: true, + editorconfig: true, + tsfmt: true }).then(result => { if (result.error) { console.error(result.message); @@ -176,7 +177,7 @@ const hygiene = exports.hygiene = (some, options) => { console.error(error.message); }, finish: function () { - // forget the summary + // forget the summary. } }; } @@ -213,7 +214,7 @@ const hygiene = exports.hygiene = (some, options) => { gulp.task('hygiene', () => hygiene()); -// this allows us to run hygiene as a git pre-commit hook +// this allows us to run hygiene as a git pre-commit hook. if (require.main === module) { const cp = require('child_process'); diff --git a/package.json b/package.json index 415fdd2e42a2..c4be011f6f9f 100644 --- a/package.json +++ b/package.json @@ -1595,6 +1595,7 @@ "postinstall": "node ./node_modules/vscode/bin/install", "test": "node ./out/test/standardTest.js && node ./out/test/multiRootTest.js", "precommit": "node gulpfile.js", + "lint-staged": "node gulpfile.js", "lint": "tslint src/**/*.ts -t verbose" }, "dependencies": { @@ -1662,4 +1663,4 @@ "vscode": "^1.1.5", "webpack": "^1.13.2" } -} \ No newline at end of file +} diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 97222940b071..bece06d457fa 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -1,11 +1,12 @@ 'use strict'; -import * as vscode from 'vscode'; -import * as path from 'path'; import * as child_process from 'child_process'; +import { EventEmitter } from 'events'; +import * as path from 'path'; +import * as vscode from 'vscode'; import { Uri } from 'vscode'; import { SystemVariables } from './systemVariables'; -import { EventEmitter } from 'events'; +// tslint:disable-next-line:no-require-imports no-var-requires const untildify = require('untildify'); export const IS_WINDOWS = /^win/.test(process.platform); @@ -56,6 +57,7 @@ export interface IPep8CategorySeverity { W: vscode.DiagnosticSeverity; E: vscode.DiagnosticSeverity; } +// tslint:disable-next-line:interface-name export interface Flake8CategorySeverity { F: vscode.DiagnosticSeverity; E: vscode.DiagnosticSeverity; @@ -125,18 +127,39 @@ export interface ITerminalSettings { executeInFileDir: boolean; launchArgs: string[]; } +// tslint:disable-next-line:interface-name export interface JupyterSettings { appendResults: boolean; defaultKernel: string; startupCode: string[]; } +// tslint:disable-next-line:no-string-literal const IS_TEST_EXECUTION = process.env['PYTHON_DONJAYAMANNE_TEST'] === '1'; +// tslint:disable-next-line:completed-docs export class PythonSettings extends EventEmitter implements IPythonSettings { private static pythonSettings: Map = new Map(); - private workspaceRoot?: vscode.Uri; + + public jediPath: string; + public envFile: string; + public disablePromptForFeatures: string[]; + public venvPath: string; + public devOptions: string[]; + public linting: ILintingSettings; + public formatting: IFormattingSettings; + public autoComplete: IAutoCompeteSettings; + public unitTest: IUnitTestSettings; + public terminal: ITerminalSettings; + public jupyter: JupyterSettings; + public sortImports: ISortImportSettings; + public workspaceSymbols: IWorkspaceSymbolSettings; + + private workspaceRoot: vscode.Uri; private disposables: vscode.Disposable[] = []; + // tslint:disable-next-line:variable-name + private _pythonPath: string; + constructor(workspaceFolder?: Uri) { super(); this.workspaceRoot = workspaceFolder ? workspaceFolder : vscode.Uri.file(__dirname); @@ -146,15 +169,10 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.initializeSettings(); } - public static dispose() { - if (!IS_TEST_EXECUTION) { - throw new Error('Dispose can only be called from unit tests'); - } - PythonSettings.pythonSettings.clear(); - } + // tslint:disable-next-line:function-name public static getInstance(resource?: Uri): PythonSettings { const workspaceFolder = resource ? vscode.workspace.getWorkspaceFolder(resource) : undefined; - let workspaceFolderUri: Uri = workspaceFolder ? workspaceFolder.uri : undefined; + let workspaceFolderUri: Uri | undefined = workspaceFolder ? workspaceFolder.uri : undefined; if (!workspaceFolderUri && Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 0) { workspaceFolderUri = vscode.workspace.workspaceFolders[0].uri; } @@ -163,15 +181,35 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { const settings = new PythonSettings(workspaceFolderUri); PythonSettings.pythonSettings.set(workspaceFolderKey, settings); } - return PythonSettings.pythonSettings.get(workspaceFolderKey); + // tslint:disable-next-line:no-non-null-assertion + return PythonSettings.pythonSettings.get(workspaceFolderKey)!; } + // tslint:disable-next-line:function-name + public static dispose() { + if (!IS_TEST_EXECUTION) { + throw new Error('Dispose can only be called from unit tests'); + } + // tslint:disable-next-line:no-void-expression + PythonSettings.pythonSettings.forEach(item => item.dispose()); + PythonSettings.pythonSettings.clear(); + } + public dispose() { + // tslint:disable-next-line:no-unsafe-any + this.disposables.forEach(disposable => disposable.dispose()); + this.disposables = []; + } + + // tslint:disable-next-line:cyclomatic-complexity max-func-body-length private initializeSettings() { const workspaceRoot = this.workspaceRoot.fsPath; const systemVariables: SystemVariables = new SystemVariables(this.workspaceRoot ? this.workspaceRoot.fsPath : undefined); const pythonSettings = vscode.workspace.getConfiguration('python', this.workspaceRoot); + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion this.pythonPath = systemVariables.resolveAny(pythonSettings.get('pythonPath'))!; this.pythonPath = getAbsolutePath(this.pythonPath, workspaceRoot); + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion this.venvPath = systemVariables.resolveAny(pythonSettings.get('venvPath'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion this.jediPath = systemVariables.resolveAny(pythonSettings.get('jediPath'))!; if (typeof this.jediPath === 'string' && this.jediPath.length > 0) { this.jediPath = getAbsolutePath(systemVariables.resolveAny(this.jediPath), workspaceRoot); @@ -179,10 +217,15 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { else { this.jediPath = ''; } + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion this.envFile = systemVariables.resolveAny(pythonSettings.get('envFile'))!; + // tslint:disable-next-line:no-any + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion no-any this.devOptions = systemVariables.resolveAny(pythonSettings.get('devOptions'))!; this.devOptions = Array.isArray(this.devOptions) ? this.devOptions : []; - let lintingSettings = systemVariables.resolveAny(pythonSettings.get('linting'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const lintingSettings = systemVariables.resolveAny(pythonSettings.get('linting'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion this.disablePromptForFeatures = pythonSettings.get('disablePromptForFeatures')!; this.disablePromptForFeatures = Array.isArray(this.disablePromptForFeatures) ? this.disablePromptForFeatures : []; if (this.linting) { @@ -191,16 +234,17 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { else { this.linting = lintingSettings; } - let sortImportSettings = systemVariables.resolveAny(pythonSettings.get('sortImports'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const sortImportSettings = systemVariables.resolveAny(pythonSettings.get('sortImports'))!; if (this.sortImports) { Object.assign(this.sortImports, sortImportSettings); } else { this.sortImports = sortImportSettings; } - // Support for travis + // Support for travis. this.sortImports = this.sortImports ? this.sortImports : { path: '', args: [] }; - // Support for travis + // Support for travis. this.linting = this.linting ? this.linting : { enabled: false, enabledWithoutWorkspace: false, @@ -242,14 +286,15 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.linting.pydocstylePath = getAbsolutePath(systemVariables.resolveAny(this.linting.pydocstylePath), workspaceRoot); this.linting.mypyPath = getAbsolutePath(systemVariables.resolveAny(this.linting.mypyPath), workspaceRoot); - let formattingSettings = systemVariables.resolveAny(pythonSettings.get('formatting'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const formattingSettings = systemVariables.resolveAny(pythonSettings.get('formatting'))!; if (this.formatting) { Object.assign(this.formatting, formattingSettings); } else { this.formatting = formattingSettings; } - // Support for travis + // Support for travis. this.formatting = this.formatting ? this.formatting : { autopep8Args: [], autopep8Path: 'autopep8', outputWindow: 'python', @@ -260,45 +305,49 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.formatting.autopep8Path = getAbsolutePath(systemVariables.resolveAny(this.formatting.autopep8Path), workspaceRoot); this.formatting.yapfPath = getAbsolutePath(systemVariables.resolveAny(this.formatting.yapfPath), workspaceRoot); - let autoCompleteSettings = systemVariables.resolveAny(pythonSettings.get('autoComplete'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const autoCompleteSettings = systemVariables.resolveAny(pythonSettings.get('autoComplete'))!; if (this.autoComplete) { Object.assign(this.autoComplete, autoCompleteSettings); } else { this.autoComplete = autoCompleteSettings; } - // Support for travis + // Support for travis. this.autoComplete = this.autoComplete ? this.autoComplete : { extraPaths: [], addBrackets: false, preloadModules: [] }; - let workspaceSymbolsSettings = systemVariables.resolveAny(pythonSettings.get('workspaceSymbols'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const workspaceSymbolsSettings = systemVariables.resolveAny(pythonSettings.get('workspaceSymbols'))!; if (this.workspaceSymbols) { Object.assign(this.workspaceSymbols, workspaceSymbolsSettings); } else { this.workspaceSymbols = workspaceSymbolsSettings; } - // Support for travis + // Support for travis. this.workspaceSymbols = this.workspaceSymbols ? this.workspaceSymbols : { ctagsPath: 'ctags', enabled: true, exclusionPatterns: [], rebuildOnFileSave: true, rebuildOnStart: true, - tagFilePath: path.join(workspaceRoot, "tags") + tagFilePath: path.join(workspaceRoot, 'tags') }; this.workspaceSymbols.tagFilePath = getAbsolutePath(systemVariables.resolveAny(this.workspaceSymbols.tagFilePath), workspaceRoot); - let unitTestSettings = systemVariables.resolveAny(pythonSettings.get('unitTest'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const unitTestSettings = systemVariables.resolveAny(pythonSettings.get('unitTest'))!; if (this.unitTest) { Object.assign(this.unitTest, unitTestSettings); } else { this.unitTest = unitTestSettings; if (IS_TEST_EXECUTION && !this.unitTest) { + // tslint:disable-next-line:prefer-type-cast this.unitTest = { nosetestArgs: [], pyTestArgs: [], unittestArgs: [], promptToConfigure: true, debugPort: 3000, @@ -308,7 +357,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { } } - // Support for travis + // Support for travis. this.unitTest = this.unitTest ? this.unitTest : { promptToConfigure: true, debugPort: 3000, @@ -323,18 +372,20 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.unitTest.cwd = getAbsolutePath(systemVariables.resolveAny(this.unitTest.cwd), workspaceRoot); } - // Resolve any variables found in the test arguments + // Resolve any variables found in the test arguments. this.unitTest.nosetestArgs = this.unitTest.nosetestArgs.map(arg => systemVariables.resolveAny(arg)); this.unitTest.pyTestArgs = this.unitTest.pyTestArgs.map(arg => systemVariables.resolveAny(arg)); this.unitTest.unittestArgs = this.unitTest.unittestArgs.map(arg => systemVariables.resolveAny(arg)); - let terminalSettings = systemVariables.resolveAny(pythonSettings.get('terminal'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const terminalSettings = systemVariables.resolveAny(pythonSettings.get('terminal'))!; if (this.terminal) { Object.assign(this.terminal, terminalSettings); } else { this.terminal = terminalSettings; if (IS_TEST_EXECUTION && !this.terminal) { + // tslint:disable-next-line:prefer-type-cast this.terminal = {} as ITerminalSettings; } } @@ -344,8 +395,9 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { launchArgs: [] }; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion this.jupyter = pythonSettings.get('jupyter')!; - // Support for travis + // Support for travis. this.jupyter = this.jupyter ? this.jupyter : { appendResults: true, defaultKernel: '', startupCode: [] }; @@ -353,7 +405,6 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.emit('change'); } - private _pythonPath: string; public get pythonPath(): string { return this._pythonPath; } @@ -361,8 +412,8 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { if (this._pythonPath === value) { return; } - // Add support for specifying just the directory where the python executable will be located - // E.g. virtual directory name + // Add support for specifying just the directory where the python executable will be located. + // E.g. virtual directory name. try { this._pythonPath = getPythonExecutable(value); } @@ -370,23 +421,11 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this._pythonPath = value; } } - public jediPath: string; - public envFile: string; - public disablePromptForFeatures: string[]; - public venvPath: string; - public devOptions: string[]; - public linting: ILintingSettings; - public formatting: IFormattingSettings; - public autoComplete: IAutoCompeteSettings; - public unitTest: IUnitTestSettings; - public terminal: ITerminalSettings; - public jupyter: JupyterSettings; - public sortImports: ISortImportSettings; - public workspaceSymbols: IWorkspaceSymbolSettings; } function getAbsolutePath(pathToCheck: string, rootDir: string): string { - pathToCheck = untildify(pathToCheck); + // tslint:disable-next-line:prefer-type-cast no-unsafe-any + pathToCheck = untildify(pathToCheck) as string; if (IS_TEST_EXECUTION && !pathToCheck) { return rootDir; } if (pathToCheck.indexOf(path.sep) === -1) { return pathToCheck; @@ -395,9 +434,10 @@ function getAbsolutePath(pathToCheck: string, rootDir: string): string { } function getPythonExecutable(pythonPath: string): string { - pythonPath = untildify(pythonPath); + // tslint:disable-next-line:prefer-type-cast no-unsafe-any + pythonPath = untildify(pythonPath) as string; - // If only 'python' + // If only 'python'. if (pythonPath === 'python' || pythonPath.indexOf(path.sep) === -1 || path.basename(pythonPath) === path.dirname(pythonPath)) { @@ -407,13 +447,14 @@ function getPythonExecutable(pythonPath: string): string { if (isValidPythonPath(pythonPath)) { return pythonPath; } - // Keep python right on top, for backwards compatibility + // Keep python right on top, for backwards compatibility. + // tslint:disable-next-line:variable-name const KnownPythonExecutables = ['python', 'python4', 'python3.6', 'python3.5', 'python3', 'python2.7', 'python2']; for (let executableName of KnownPythonExecutables) { - // Suffix with 'python' for linux and 'osx', and 'python.exe' for 'windows' + // Suffix with 'python' for linux and 'osx', and 'python.exe' for 'windows'. if (IS_WINDOWS) { - executableName = executableName + '.exe'; + executableName = `${executableName}.exe`; if (isValidPythonPath(path.join(pythonPath, executableName))) { return path.join(pythonPath, executableName); } @@ -436,7 +477,7 @@ function getPythonExecutable(pythonPath: string): string { function isValidPythonPath(pythonPath: string): boolean { try { - let output = child_process.execFileSync(pythonPath, ['-c', 'print(1234)'], { encoding: 'utf8' }); + const output = child_process.execFileSync(pythonPath, ['-c', 'print(1234)'], { encoding: 'utf8' }); return output.startsWith('1234'); } catch (ex) { diff --git a/src/client/interpreter/display/index.ts b/src/client/interpreter/display/index.ts index de114a51faf5..6ce8fdc4be93 100644 --- a/src/client/interpreter/display/index.ts +++ b/src/client/interpreter/display/index.ts @@ -1,16 +1,16 @@ 'use strict'; -import * as path from 'path'; -import * as utils from '../../common/utils'; import * as child_process from 'child_process'; -import { StatusBarItem, Disposable } from 'vscode'; -import { PythonSettings } from '../../common/configSettings'; import { EOL } from 'os'; +import * as path from 'path'; +import { Disposable, StatusBarItem } from 'vscode'; +import { PythonSettings } from '../../common/configSettings'; +import * as utils from '../../common/utils'; import { IInterpreterLocatorService } from '../contracts'; +import { getFirstNonEmptyLineFromMultilineString } from '../helpers'; import { IInterpreterVersionService } from '../interpreterVersion'; import { VirtualEnvironmentManager } from '../virtualEnvs/index'; -import { getFirstNonEmptyLineFromMultilineString } from '../helpers'; -const settings = PythonSettings.getInstance(); +// tslint:disable-next-line:completed-docs export class InterpreterDisplay implements Disposable { constructor(private statusBar: StatusBarItem, private interpreterLocator: IInterpreterLocatorService, @@ -19,12 +19,13 @@ export class InterpreterDisplay implements Disposable { this.statusBar.command = 'python.setInterpreter'; } public dispose() { + // } public async refresh() { - const pythonPath = await this.getFullyQualifiedPathToInterpreter(settings.pythonPath); + const pythonPath = await this.getFullyQualifiedPathToInterpreter(PythonSettings.getInstance().pythonPath); await this.updateDisplay(pythonPath); } - private getInterpreters() { + private async getInterpreters() { return this.interpreterLocator.getInterpreters(); } private async updateDisplay(pythonPath: string) { @@ -34,6 +35,7 @@ export class InterpreterDisplay implements Disposable { this.statusBar.color = ''; this.statusBar.tooltip = pythonPath; if (interpreter) { + // tslint:disable-next-line:no-non-null-assertion this.statusBar.text = interpreter.displayName!; if (interpreter.companyDisplayName) { const toolTipSuffix = `${EOL}${interpreter.companyDisplayName}`; @@ -42,10 +44,11 @@ export class InterpreterDisplay implements Disposable { } else { const defaultDisplayName = `${path.basename(pythonPath)} [Environment]`; - const interpreterExists = utils.fsExistsAsync(pythonPath); - const displayName = this.versionProvider.getVersion(pythonPath, defaultDisplayName); - const virtualEnvName = this.getVirtualEnvironmentName(pythonPath); - await Promise.all([interpreterExists, displayName, virtualEnvName]) + await Promise.all([ + utils.fsExistsAsync(pythonPath), + this.versionProvider.getVersion(pythonPath, defaultDisplayName), + this.getVirtualEnvironmentName(pythonPath) + ]) .then(([interpreterExists, displayName, virtualEnvName]) => { const dislayNameSuffix = virtualEnvName.length > 0 ? ` (${virtualEnvName})` : ''; this.statusBar.text = `${displayName}${dislayNameSuffix}`; @@ -63,13 +66,12 @@ export class InterpreterDisplay implements Disposable { .detect(pythonPath) .then(env => env ? env.name : ''); } - private getFullyQualifiedPathToInterpreter(pythonPath: string) { + private async getFullyQualifiedPathToInterpreter(pythonPath: string) { return new Promise(resolve => { - child_process.execFile(pythonPath, ["-c", "import sys;print(sys.executable)"], (_, stdout) => { + child_process.execFile(pythonPath, ['-c', 'import sys;print(sys.executable)'], (_, stdout) => { resolve(getFirstNonEmptyLineFromMultilineString(stdout)); }); }) .then(value => value.length === 0 ? pythonPath : value); } } - diff --git a/src/client/providers/importSortProvider.ts b/src/client/providers/importSortProvider.ts index c3628427f48b..9084547de29f 100644 --- a/src/client/providers/importSortProvider.ts +++ b/src/client/providers/importSortProvider.ts @@ -1,57 +1,54 @@ -"use strict"; - -import * as vscode from "vscode"; -import * as path from "path"; -import * as fs from "fs"; -import * as child_process from "child_process"; -import * as settings from '../common/configSettings'; -import { getTextEditsFromPatch, getTempFileWithDocumentContents } from "../common/editor"; +'use strict'; +import * as child_process from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { PythonSettings } from '../common/configSettings'; +import { getTempFileWithDocumentContents, getTextEditsFromPatch } from '../common/editor'; +// tslint:disable-next-line:completed-docs export class PythonImportSortProvider { - public sortImports(extensionDir: string, document: vscode.TextDocument): Promise { + public async sortImports(extensionDir: string, document: vscode.TextDocument): Promise { if (document.lineCount === 1) { - return Promise.resolve([]); + return []; + } + // isort does have the ability to read from the process input stream and return the formatted code out of the output stream. + // However they don't support returning the diff of the formatted text when reading data from the input stream. + // Yes getting text formatted that way avoids having to create a temporary file, however the diffing will have + // to be done here in node (extension), i.e. extension cpu, i.e. less responsive solution. + const importScript = path.join(extensionDir, 'pythonFiles', 'sortImports.py'); + const tmpFileCreated = document.isDirty; + const filePath = tmpFileCreated ? await getTempFileWithDocumentContents(document) : document.fileName; + const settings = PythonSettings.getInstance(document.uri); + const pythonPath = settings.pythonPath; + const isort = settings.sortImports.path; + const args = settings.sortImports.args.join(' '); + let isortCmd = ''; + if (typeof isort === 'string' && isort.length > 0) { + if (isort.indexOf(' ') > 0) { + isortCmd = `"${isort}" "${filePath}" --diff ${args}`; + } else { + isortCmd = `${isort} "${filePath}" --diff ${args}`; + } + } else { + if (pythonPath.indexOf(' ') > 0) { + isortCmd = `"${pythonPath}" "${importScript}" "${filePath}" --diff ${args}`; + } else { + isortCmd = `${pythonPath} "${importScript}" "${filePath}" --diff ${args}`; + } } - return new Promise((resolve, reject) => { - // isort does have the ability to read from the process input stream and return the formatted code out of the output stream - // However they don't support returning the diff of the formatted text when reading data from the input stream - // Yes getting text formatted that way avoids having to create a temporary file, however the diffing will have - // to be done here in node (extension), i.e. extension cpu, i.e. les responsive solution - let importScript = path.join(extensionDir, "pythonFiles", "sortImports.py"); - let tmpFileCreated = document.isDirty; - let filePromise = tmpFileCreated ? getTempFileWithDocumentContents(document) : Promise.resolve(document.fileName); - filePromise.then(filePath => { - const pythonPath = settings.PythonSettings.getInstance().pythonPath; - const isort = settings.PythonSettings.getInstance().sortImports.path; - const args = settings.PythonSettings.getInstance().sortImports.args.join(' '); - let isort_cmd = ''; - if (typeof isort === 'string' && isort.length > 0) { - if (isort.indexOf(' ') > 0) { - isort_cmd = `"${isort}" "${filePath}" --diff ${args}`; - } - else { - isort_cmd = `${isort} "${filePath}" --diff ${args}`; - } + // tslint:disable-next-line:promise-must-complete + return await new Promise((resolve, reject) => { + child_process.exec(isortCmd, (error, stdout, stderr) => { + if (tmpFileCreated) { + fs.unlink(filePath); + } + if (error || (stderr && stderr.length > 0)) { + reject(error ? error : stderr); } else { - if (pythonPath.indexOf(' ') > 0) { - isort_cmd = `"${pythonPath}" "${importScript}" "${filePath}" --diff ${args}`; - } - else { - isort_cmd = `${pythonPath} "${importScript}" "${filePath}" --diff ${args}`; - } + resolve(getTextEditsFromPatch(document.getText(), stdout)); } - child_process.exec(isort_cmd, (error, stdout, stderr) => { - if (tmpFileCreated) { - fs.unlink(filePath); - } - if (error || (stderr && stderr.length > 0)) { - return reject(error ? error : stderr); - } - - let edits = getTextEditsFromPatch(document.getText(), stdout); - resolve(edits); - }); - }).catch(reject); + }); }); } } diff --git a/src/test/.vscode/settings.json b/src/test/.vscode/settings.json index bfbbba081622..1d54a0da6b81 100644 --- a/src/test/.vscode/settings.json +++ b/src/test/.vscode/settings.json @@ -1,12 +1,23 @@ { - "python.pythonPath": "python", - "python.linting.pylintEnabled": true, - "python.linting.flake8Enabled": true, - "python.workspaceSymbols.enabled": true, + "python.linting.pylintEnabled": false, + "python.linting.flake8Enabled": false, + "python.workspaceSymbols.enabled": false, "python.unitTest.nosetestArgs": [], "python.unitTest.pyTestArgs": [], "python.unitTest.unittestArgs": [ "-s=./tests", "-p=test_*.py" - ] + ], + "python.pythonPath": "python", + "python.formatting.formatOnSave": false, + "python.sortImports.args": [], + "python.linting.lintOnSave": false, + "python.linting.lintOnTextChange": false, + "python.linting.enabled": true, + "python.linting.pep8Enabled": false, + "python.linting.prospectorEnabled": false, + "python.linting.pydocstyleEnabled": false, + "python.linting.pylamaEnabled": false, + "python.linting.mypyEnabled": false, + "python.formatting.provider": "yapf" } \ No newline at end of file diff --git a/src/test/common.ts b/src/test/common.ts index cbab3da30f19..2013b9487c59 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -8,21 +8,21 @@ export const rootWorkspaceUri = getWorkspaceRoot(); export type PythonSettingKeys = 'workspaceSymbols.enabled' | 'pythonPath' | 'linting.lintOnSave' | 'linting.lintOnTextChange' | 'linting.enabled' | 'linting.pylintEnabled' | - 'linting.flake8Enabled' | 'linting.pep8Enabled' | - 'linting.prospectorEnabled' | 'linting.pydocstyleEnabled' | + 'linting.flake8Enabled' | 'linting.pep8Enabled' | 'linting.pylamaEnabled' | + 'linting.prospectorEnabled' | 'linting.pydocstyleEnabled' | 'linting.mypyEnabled' | 'unitTest.nosetestArgs' | 'unitTest.pyTestArgs' | 'unitTest.unittestArgs' | 'formatting.formatOnSave' | 'formatting.provider' | 'sortImports.args'; - -export async function updateSetting(setting: PythonSettingKeys, value: any, resource: Uri, configTarget: ConfigurationTarget) { - let settings = workspace.getConfiguration('python', resource); +export async function updateSetting(setting: PythonSettingKeys, value: {}, resource: Uri, configTarget: ConfigurationTarget) { + const settings = workspace.getConfiguration('python', resource); const currentValue = settings.inspect(setting); - if ((configTarget === ConfigurationTarget.Global && currentValue && currentValue.globalValue === value) || - (configTarget === ConfigurationTarget.Workspace && currentValue && currentValue.workspaceValue === value) || - (configTarget === ConfigurationTarget.WorkspaceFolder && currentValue && currentValue.workspaceFolderValue === value)) { + if (currentValue !== undefined && ((configTarget === ConfigurationTarget.Global && currentValue.globalValue === value) || + (configTarget === ConfigurationTarget.Workspace && currentValue.workspaceValue === value) || + (configTarget === ConfigurationTarget.WorkspaceFolder && currentValue.workspaceFolderValue === value))) { PythonSettings.dispose(); return; } + // tslint:disable-next-line:await-promise await settings.update(setting, value, configTarget); PythonSettings.dispose(); } @@ -34,5 +34,6 @@ function getWorkspaceRoot() { if (workspace.workspaceFolders.length === 1) { return workspace.workspaceFolders[0].uri; } - return workspace.getWorkspaceFolder(Uri.file(fileInNonRootWorkspace)).uri; + const workspaceFolder = workspace.getWorkspaceFolder(Uri.file(fileInNonRootWorkspace)); + return workspaceFolder ? workspaceFolder.uri : workspace.workspaceFolders[0].uri; } diff --git a/src/test/common/configSettings.multiroot.test.ts b/src/test/common/configSettings.multiroot.test.ts index 08172cb10171..17c96c631dd3 100644 --- a/src/test/common/configSettings.multiroot.test.ts +++ b/src/test/common/configSettings.multiroot.test.ts @@ -1,18 +1,19 @@ import * as assert from 'assert'; import * as path from 'path'; import { ConfigurationTarget, Uri, workspace } from 'vscode'; -import { initialize, closeActiveWindows, initializeTest } from '../initialize'; import { PythonSettings } from '../../client/common/configSettings'; +import { closeActiveWindows, initialize, initializeTest } from '../initialize'; const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); +// tslint:disable-next-line:max-func-body-length suite('Multiroot Config Settings', () => { suiteSetup(async () => { await initialize(); await resetSettings(); }); - setup(() => initializeTest()); - suiteTeardown(() => closeActiveWindows()); + setup(initializeTest); + suiteTeardown(closeActiveWindows); teardown(async () => { await resetSettings(); await closeActiveWindows(); @@ -20,28 +21,25 @@ suite('Multiroot Config Settings', () => { async function resetSettings() { const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); - let settings = workspace.getConfiguration('python', workspaceUri); - let value = settings.inspect('pythonPath'); - if (value.workspaceValue && value.workspaceValue !== 'python') { - await settings.update('pythonPath', undefined, ConfigurationTarget.Workspace); - } - if (value.workspaceFolderValue && value.workspaceFolderValue !== 'python') { + const settings = workspace.getConfiguration('python', workspaceUri); + const value = settings.inspect('pythonPath'); + if (value && typeof value.workspaceFolderValue === 'string') { await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); } PythonSettings.dispose(); } - async function enableDisableSetting(resource: Uri, configTarget: ConfigurationTarget, setting: string, value: boolean) { - let settings = workspace.getConfiguration('python.linting', resource); - await settings.update(setting, value, configTarget); - settings = workspace.getConfiguration('python.linting', resource); - return settings.get(setting); - } - async function testLinterSetting(resource: Uri, configTarget: ConfigurationTarget, setting: string, value: boolean) { - const valueInSetting = await enableDisableSetting(resource, configTarget, setting, value); + async function enableDisableLinterSetting(resource: Uri, configTarget: ConfigurationTarget, setting: string, enabled: boolean | undefined): Promise { + const settings = workspace.getConfiguration('python.linting', resource); + const cfgValue = settings.inspect(setting); + if (configTarget === ConfigurationTarget.Workspace && cfgValue && cfgValue.workspaceValue === enabled) { + return; + } + if (configTarget === ConfigurationTarget.WorkspaceFolder && cfgValue && cfgValue.workspaceFolderValue === enabled) { + return; + } + await settings.update(setting, enabled, configTarget); PythonSettings.dispose(); - const cfgSetting = PythonSettings.getInstance(resource); - assert.equal(valueInSetting, cfgSetting.linting[setting], `Both settings ${setting} should be ${value} for ${resource.fsPath}`); } test('Workspace folder should inherit Python Path from workspace root', async () => { @@ -49,26 +47,32 @@ suite('Multiroot Config Settings', () => { let settings = workspace.getConfiguration('python', workspaceUri); const pythonPath = `x${new Date().getTime()}`; await settings.update('pythonPath', pythonPath, ConfigurationTarget.Workspace); - + const value = settings.inspect('pythonPath'); + if (value && typeof value.workspaceFolderValue === 'string') { + await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); + } + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); settings = workspace.getConfiguration('python', workspaceUri); - assert.equal(settings.get('pythonPath'), pythonPath, 'Python path not set in workspace root'); - + PythonSettings.dispose(); const cfgSetting = PythonSettings.getInstance(workspaceUri); assert.equal(cfgSetting.pythonPath, pythonPath, 'Python Path not inherited from workspace'); }); test('Workspace folder should not inherit Python Path from workspace root', async () => { const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); - let settings = workspace.getConfiguration('python', workspaceUri); + const settings = workspace.getConfiguration('python', workspaceUri); const pythonPath = `x${new Date().getTime()}`; await settings.update('pythonPath', pythonPath, ConfigurationTarget.Workspace); - await settings.update('pythonPath', 'privatePythonPath', ConfigurationTarget.WorkspaceFolder); + const privatePythonPath = `x${new Date().getTime()}`; + await settings.update('pythonPath', privatePythonPath, ConfigurationTarget.WorkspaceFolder); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); const cfgSetting = PythonSettings.getInstance(workspaceUri); - assert.equal(cfgSetting.pythonPath, 'privatePythonPath', 'Python Path for workspace folder is incorrect'); + assert.equal(cfgSetting.pythonPath, privatePythonPath, 'Python Path for workspace folder is incorrect'); }); - test('Workspace folder should inherit Python Path from workspace root when opening a document', async () => { const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); const fileToOpen = path.join(multirootPath, 'workspace1', 'file.py'); @@ -76,6 +80,11 @@ suite('Multiroot Config Settings', () => { const settings = workspace.getConfiguration('python', workspaceUri); const pythonPath = `x${new Date().getTime()}`; await settings.update('pythonPath', pythonPath, ConfigurationTarget.Workspace); + // Update workspace folder to something else so it gets refreshed. + await settings.update('pythonPath', `x${new Date().getTime()}`, ConfigurationTarget.WorkspaceFolder); + await settings.update('pythonPath', undefined, ConfigurationTarget.WorkspaceFolder); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); const document = await workspace.openTextDocument(fileToOpen); const cfg = PythonSettings.getInstance(document.uri); @@ -89,37 +98,48 @@ suite('Multiroot Config Settings', () => { const settings = workspace.getConfiguration('python', workspaceUri); const pythonPath = `x${new Date().getTime()}`; await settings.update('pythonPath', pythonPath, ConfigurationTarget.Workspace); - await settings.update('pythonPath', 'privatePythonPath', ConfigurationTarget.WorkspaceFolder); + const privatePythonPath = `x${new Date().getTime()}`; + await settings.update('pythonPath', privatePythonPath, ConfigurationTarget.WorkspaceFolder); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); const document = await workspace.openTextDocument(fileToOpen); const cfg = PythonSettings.getInstance(document.uri); - assert.equal(cfg.pythonPath, 'privatePythonPath', 'Python Path for workspace folder is incorrect'); - }); - - test('Enabling/Disabling Pylint in root should be reflected in config settings', async () => { - const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); + assert.equal(cfg.pythonPath, privatePythonPath, 'Python Path for workspace folder is incorrect'); }); test('Enabling/Disabling Pylint in root should be reflected in config settings', async () => { const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', undefined); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); + let settings = PythonSettings.getInstance(workspaceUri); + assert.equal(settings.linting.pylintEnabled, true, 'Pylint not enabled when it should be'); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); + + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); + settings = PythonSettings.getInstance(workspaceUri); + assert.equal(settings.linting.pylintEnabled, false, 'Pylint enabled when it should not be'); }); test('Enabling/Disabling Pylint in root and workspace should be reflected in config settings', async () => { const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); - await testLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', false); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', false); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); let cfgSetting = PythonSettings.getInstance(workspaceUri); assert.equal(cfgSetting.linting.pylintEnabled, false, 'Workspace folder pylint setting is true when it should not be'); PythonSettings.dispose(); - await testLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', true); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', true); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); cfgSetting = PythonSettings.getInstance(workspaceUri); assert.equal(cfgSetting.linting.pylintEnabled, true, 'Workspace folder pylint setting is false when it should not be'); @@ -129,15 +149,19 @@ suite('Multiroot Config Settings', () => { const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); const fileToOpen = path.join(multirootPath, 'workspace1', 'file.py'); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); - await testLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', true); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', true); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); let document = await workspace.openTextDocument(fileToOpen); let cfg = PythonSettings.getInstance(document.uri); assert.equal(cfg.linting.pylintEnabled, true, 'Pylint should be enabled in workspace'); PythonSettings.dispose(); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); - await testLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', false); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', false); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); document = await workspace.openTextDocument(fileToOpen); cfg = PythonSettings.getInstance(document.uri); assert.equal(cfg.linting.pylintEnabled, false, 'Pylint should not be enabled in workspace'); @@ -147,20 +171,25 @@ suite('Multiroot Config Settings', () => { const workspaceUri = Uri.file(path.join(multirootPath, 'workspace1')); const fileToOpen = path.join(multirootPath, 'workspace1', 'file.py'); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); - await testLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', true); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', false); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', true); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); let document = await workspace.openTextDocument(fileToOpen); let cfg = PythonSettings.getInstance(document.uri); assert.equal(cfg.linting.pylintEnabled, true, 'Pylint should be enabled in workspace'); PythonSettings.dispose(); - await testLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); - await testLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', false); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.Workspace, 'pylintEnabled', true); + await enableDisableLinterSetting(workspaceUri, ConfigurationTarget.WorkspaceFolder, 'pylintEnabled', false); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); document = await workspace.openTextDocument(fileToOpen); cfg = PythonSettings.getInstance(document.uri); assert.equal(cfg.linting.pylintEnabled, false, 'Pylint should not be enabled in workspace'); }); + // tslint:disable-next-line:no-invalid-template-strings test('${workspaceRoot} variable in settings should be replaced with the right value', async () => { const workspace2Uri = Uri.file(path.join(multirootPath, 'workspace2')); let fileToOpen = path.join(workspace2Uri.fsPath, 'file.py'); diff --git a/src/test/common/configSettings.test.ts b/src/test/common/configSettings.test.ts index 3b2e7a1c85eb..616c29d5afab 100644 --- a/src/test/common/configSettings.test.ts +++ b/src/test/common/configSettings.test.ts @@ -10,7 +10,7 @@ import * as assert from 'assert'; // as well as import your extension to test it import * as vscode from 'vscode'; import * as path from 'path'; -import { initialize, IS_TRAVIS } from './../initialize'; +import { initialize, IS_MULTI_ROOT_TEST, IS_TRAVIS } from './../initialize'; import { PythonSettings } from '../../client/common/configSettings'; import { SystemVariables } from '../../client/common/systemVariables'; import { rootWorkspaceUri } from '../common'; @@ -21,7 +21,7 @@ const workspaceRoot = path.join(__dirname, '..', '..', '..', 'src', 'test'); suite('Configuration Settings', () => { setup(() => initialize()); - if (!IS_TRAVIS) { + if (!IS_MULTI_ROOT_TEST) { test('Check Values', done => { const systemVariables: SystemVariables = new SystemVariables(workspaceRoot); const pythonConfig = vscode.workspace.getConfiguration('python'); diff --git a/src/test/format/extension.format.test.ts b/src/test/format/extension.format.test.ts index 1f47e8ea247d..ed792f087f6b 100644 --- a/src/test/format/extension.format.test.ts +++ b/src/test/format/extension.format.test.ts @@ -15,7 +15,7 @@ import * as fs from 'fs-extra'; import { EOL } from 'os'; import { PythonSettings } from '../../client/common/configSettings'; import { AutoPep8Formatter } from '../../client/formatters/autoPep8Formatter'; -import { initialize, IS_TRAVIS, closeActiveWindows, initializeTest } from '../initialize'; +import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST, IS_TRAVIS } from '../initialize'; import { YapfFormatter } from '../../client/formatters/yapfFormatter'; import { execPythonFile } from '../../client/common/utils'; @@ -29,6 +29,8 @@ const autoPep8FileToAutoFormat = path.join(pythoFilesPath, 'autoPep8FileToAutoFo const yapfFileToFormat = path.join(pythoFilesPath, 'yapfFileToFormat.py'); const yapfFileToAutoFormat = path.join(pythoFilesPath, 'yapfFileToAutoFormat.py'); +const configUpdateTarget = IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace; + let formattedYapf = ''; let formattedAutoPep8 = ''; @@ -46,17 +48,14 @@ suite('Formatting', () => { formattedAutoPep8 = formattedResults[1]; }).then(() => { }); }); - setup(async () => { - await initializeTest(); - updateSetting('formatting.formatOnSave', false, vscode.Uri.file(pythoFilesPath), vscode.ConfigurationTarget.Workspace) - }); + setup(() => initializeTest()); suiteTeardown(async () => { [autoPep8FileToFormat, autoPep8FileToAutoFormat, yapfFileToFormat, yapfFileToAutoFormat].forEach(file => { if (fs.existsSync(file)) { fs.unlinkSync(file); } }); - await updateSetting('formatting.formatOnSave', false, vscode.Uri.file(pythoFilesPath), vscode.ConfigurationTarget.Workspace) + await updateSetting('formatting.formatOnSave', false, vscode.Uri.file(pythoFilesPath), configUpdateTarget) await closeActiveWindows(); }); teardown(() => closeActiveWindows()); @@ -90,8 +89,8 @@ suite('Formatting', () => { }); async function testAutoFormatting(formatter: string, formattedContents: string, fileToFormat: string): Promise { - await updateSetting('formatting.formatOnSave', true, vscode.Uri.file(fileToFormat), vscode.ConfigurationTarget.Workspace); - await updateSetting('formatting.provider', formatter, vscode.Uri.file(fileToFormat), vscode.ConfigurationTarget.Workspace); + await updateSetting('formatting.formatOnSave', true, vscode.Uri.file(fileToFormat), configUpdateTarget); + await updateSetting('formatting.provider', formatter, vscode.Uri.file(fileToFormat), configUpdateTarget); const textDocument = await vscode.workspace.openTextDocument(fileToFormat); const editor = await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); @@ -104,7 +103,7 @@ suite('Formatting', () => { resolve(); }, 5000); }); - const text = textDocument.getText(); + const text = textDocument.getText(); assert.equal(text === formattedContents, true, 'Formatted contents are not the same'); } test('AutoPep8 autoformat on save', done => { diff --git a/src/test/format/extension.sort.test.ts b/src/test/format/extension.sort.test.ts index 31ddcf26b1f4..f5768eb0da99 100644 --- a/src/test/format/extension.sort.test.ts +++ b/src/test/format/extension.sort.test.ts @@ -1,21 +1,11 @@ -import { updateSetting } from '../common'; - -// 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'; - -// 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 * as fs from 'fs'; import { EOL } from 'os'; +import * as path from 'path'; +import { commands, ConfigurationTarget, Position, Range, Uri, window, workspace } from 'vscode'; import { PythonImportSortProvider } from '../../client/providers/importSortProvider'; -import { initialize, IS_TRAVIS, closeActiveWindows, initializeTest } from '../initialize'; +import { updateSetting } from '../common'; +import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; const sortingPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'sorting'); const fileToFormatWithoutConfig = path.join(sortingPath, 'noconfig', 'before.py'); @@ -26,142 +16,90 @@ const fileToFormatWithConfig1 = path.join(sortingPath, 'withconfig', 'before.1.p const originalFileToFormatWithConfig1 = path.join(sortingPath, 'withconfig', 'original.1.py'); const extensionDir = path.join(__dirname, '..', '..', '..'); +// tslint:disable-next-line:max-func-body-length suite('Sorting', () => { - suiteSetup(() => initialize()); - setup(() => initializeTest()); + const configTarget = IS_MULTI_ROOT_TEST ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Workspace; + suiteSetup(initialize); + setup(initializeTest); suiteTeardown(async () => { fs.writeFileSync(fileToFormatWithConfig, fs.readFileSync(originalFileToFormatWithConfig)); fs.writeFileSync(fileToFormatWithConfig1, fs.readFileSync(originalFileToFormatWithConfig1)); fs.writeFileSync(fileToFormatWithoutConfig, fs.readFileSync(originalFileToFormatWithoutConfig)); - await updateSetting('sortImports.args', [], vscode.Uri.file(sortingPath), vscode.ConfigurationTarget.Workspace); + await updateSetting('sortImports.args', [], Uri.file(sortingPath), configTarget); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); await closeActiveWindows(); }); setup(async () => { fs.writeFileSync(fileToFormatWithConfig, fs.readFileSync(originalFileToFormatWithConfig)); fs.writeFileSync(fileToFormatWithoutConfig, fs.readFileSync(originalFileToFormatWithoutConfig)); fs.writeFileSync(fileToFormatWithConfig1, fs.readFileSync(originalFileToFormatWithConfig1)); - await updateSetting('sortImports.args', [], vscode.Uri.file(sortingPath), vscode.ConfigurationTarget.Workspace); + await updateSetting('sortImports.args', [], Uri.file(sortingPath), configTarget); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); await closeActiveWindows(); }); - test('Without Config', done => { - let textEditor: vscode.TextEditor; - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileToFormatWithoutConfig).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - textEditor = editor; - assert(vscode.window.activeTextEditor, 'No active editor'); - const sorter = new PythonImportSortProvider(); - return sorter.sortImports(extensionDir, textDocument); - }).then(edits => { - assert.equal(edits.filter(value => value.newText === EOL && value.range.isEqual(new vscode.Range(2, 0, 2, 0))).length, 1, 'EOL not found'); - assert.equal(edits.filter(value => value.newText === '' && value.range.isEqual(new vscode.Range(3, 0, 4, 0))).length, 1, '"" not found'); - assert.equal(edits.filter(value => value.newText === `from rope.base import libutils${EOL}from rope.refactor.extract import ExtractMethod, ExtractVariable${EOL}from rope.refactor.rename import Rename${EOL}` && value.range.isEqual(new vscode.Range(6, 0, 6, 0))).length, 1, 'Text not found'); - assert.equal(edits.filter(value => value.newText === '' && value.range.isEqual(new vscode.Range(13, 0, 18, 0))).length, 1, '"" not found'); - }).then(done, done); + test('Without Config', async () => { + const textDocument = await workspace.openTextDocument(fileToFormatWithoutConfig); + await window.showTextDocument(textDocument); + const sorter = new PythonImportSortProvider(); + const edits = await sorter.sortImports(extensionDir, textDocument); + assert.equal(edits.filter(value => value.newText === EOL && value.range.isEqual(new Range(2, 0, 2, 0))).length, 1, 'EOL not found'); + assert.equal(edits.filter(value => value.newText === '' && value.range.isEqual(new Range(3, 0, 4, 0))).length, 1, '"" not found'); + assert.equal(edits.filter(value => value.newText === `from rope.base import libutils${EOL}from rope.refactor.extract import ExtractMethod, ExtractVariable${EOL}from rope.refactor.rename import Rename${EOL}` && value.range.isEqual(new Range(6, 0, 6, 0))).length, 1, 'Text not found'); + assert.equal(edits.filter(value => value.newText === '' && value.range.isEqual(new Range(13, 0, 18, 0))).length, 1, '"" not found'); }); - test('Without Config (via Command)', done => { - let textEditor: vscode.TextEditor; - let textDocument: vscode.TextDocument; - let originalContent = ''; - vscode.workspace.openTextDocument(fileToFormatWithoutConfig).then(document => { - textDocument = document; - originalContent = textDocument.getText(); - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - textEditor = editor; - return vscode.commands.executeCommand('python.sortImports'); - }).then(() => { - assert.notEqual(originalContent, textDocument.getText(), 'Contents have not changed'); - }).then(done, done); + test('Without Config (via Command)', async () => { + const textDocument = await workspace.openTextDocument(fileToFormatWithoutConfig); + const originalContent = textDocument.getText(); + await window.showTextDocument(textDocument); + await commands.executeCommand('python.sortImports'); + assert.notEqual(originalContent, textDocument.getText(), 'Contents have not changed'); }); - test('With Config', done => { - let textEditor: vscode.TextEditor; - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileToFormatWithConfig).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - textEditor = editor; - const sorter = new PythonImportSortProvider(); - return sorter.sortImports(extensionDir, textDocument); - }).then(edits => { - const newValue = `from third_party import lib2${EOL}from third_party import lib3${EOL}from third_party import lib4${EOL}from third_party import lib5${EOL}from third_party import lib6${EOL}from third_party import lib7${EOL}from third_party import lib8${EOL}from third_party import lib9${EOL}`; - assert.equal(edits.filter(value => value.newText === newValue && value.range.isEqual(new vscode.Range(0, 0, 3, 0))).length, 1, 'New Text not found'); - }).then(done, done); + test('With Config', async () => { + const textDocument = await workspace.openTextDocument(fileToFormatWithConfig); + await window.showTextDocument(textDocument); + const sorter = new PythonImportSortProvider(); + const edits = await sorter.sortImports(extensionDir, textDocument); + const newValue = `from third_party import lib2${EOL}from third_party import lib3${EOL}from third_party import lib4${EOL}from third_party import lib5${EOL}from third_party import lib6${EOL}from third_party import lib7${EOL}from third_party import lib8${EOL}from third_party import lib9${EOL}`; + assert.equal(edits.filter(value => value.newText === newValue && value.range.isEqual(new Range(0, 0, 3, 0))).length, 1, 'New Text not found'); }); - test('With Config (via Command)', done => { - let textEditor: vscode.TextEditor; - let textDocument: vscode.TextDocument; - let originalContent = ''; - vscode.workspace.openTextDocument(fileToFormatWithConfig).then(document => { - textDocument = document; - originalContent = document.getText(); - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - textEditor = editor; - return vscode.commands.executeCommand('python.sortImports'); - }).then(() => { - assert.notEqual(originalContent, textDocument.getText(), 'Contents have not changed'); - }).then(done, done); + test('With Config (via Command)', async () => { + const textDocument = await workspace.openTextDocument(fileToFormatWithConfig); + const originalContent = textDocument.getText(); + await window.showTextDocument(textDocument); + await commands.executeCommand('python.sortImports'); + assert.notEqual(originalContent, textDocument.getText(), 'Contents have not changed'); }); - // Doesn't always work on Travis !?! - if (!IS_TRAVIS) { - test('With Changes and Config in Args', done => { - let textEditor: vscode.TextEditor; - let textDocument: vscode.TextDocument; - updateSetting('sortImports.args', ['-sp', path.join(sortingPath, 'withconfig')], vscode.Uri.file(sortingPath), vscode.ConfigurationTarget.Workspace) - .then(() => vscode.workspace.openTextDocument(fileToFormatWithConfig)) - .then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - textEditor = editor; - return editor.edit(editor => { - editor.insert(new vscode.Position(0, 0), 'from third_party import lib0' + EOL); - }); - }).then(() => { - const sorter = new PythonImportSortProvider(); - return sorter.sortImports(extensionDir, textDocument); - }).then(edits => { - const newValue = `from third_party import lib2${EOL}from third_party import lib3${EOL}from third_party import lib4${EOL}from third_party import lib5${EOL}from third_party import lib6${EOL}from third_party import lib7${EOL}from third_party import lib8${EOL}from third_party import lib9${EOL}`; - assert.equal(edits.length, 1, 'Incorrect number of edits'); - assert.equal(edits[0].newText, newValue, 'New Value is not the same'); - assert.equal(`${edits[0].range.start.line},${edits[0].range.start.character}`, '1,0', 'Start position is not the same'); - assert.equal(`${edits[0].range.end.line},${edits[0].range.end.character}`, '4,0', 'End position is not the same'); - }).then(done, done); + test('With Changes and Config in Args', async () => { + await updateSetting('sortImports.args', ['-sp', path.join(sortingPath, 'withconfig')], Uri.file(sortingPath), ConfigurationTarget.Workspace); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); + const textDocument = await workspace.openTextDocument(fileToFormatWithConfig); + const editor = await window.showTextDocument(textDocument); + await editor.edit(builder => { + builder.insert(new Position(0, 0), `from third_party import lib0${EOL}`); + }); + const sorter = new PythonImportSortProvider(); + const edits = await sorter.sortImports(extensionDir, textDocument); + assert.notEqual(edits.length, 0, 'No edits'); + }); + test('With Changes and Config in Args (via Command)', async () => { + await updateSetting('sortImports.args', ['-sp', path.join(sortingPath, 'withconfig')], Uri.file(sortingPath), configTarget); + // tslint:disable-next-line:no-string-based-set-timeout + await new Promise(resolve => setTimeout(resolve, 2000)); + const textDocument = await workspace.openTextDocument(fileToFormatWithConfig); + const editor = await window.showTextDocument(textDocument); + await editor.edit(builder => { + builder.insert(new Position(0, 0), `from third_party import lib0${EOL}`); }); - } - test('With Changes and Config in Args (via Command)', done => { - let textEditor: vscode.TextEditor; - let textDocument: vscode.TextDocument; - let originalContent = ''; - updateSetting('sortImports.args', ['-sp', path.join(sortingPath, 'withconfig')], vscode.Uri.file(sortingPath), vscode.ConfigurationTarget.Workspace) - .then(() => vscode.workspace.openTextDocument(fileToFormatWithConfig)) - .then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - textEditor = editor; - return editor.edit(editor => { - editor.insert(new vscode.Position(0, 0), 'from third_party import lib0' + EOL); - }); - }).then(() => { - originalContent = textDocument.getText(); - return vscode.commands.executeCommand('python.sortImports'); - }).then(edits => { - assert.notEqual(originalContent, textDocument.getText(), 'Contents have not changed'); - }).then(done, done); + const originalContent = textDocument.getText(); + await commands.executeCommand('python.sortImports'); + assert.notEqual(originalContent, textDocument.getText(), 'Contents have not changed'); }); }); diff --git a/src/test/index.ts b/src/test/index.ts index defee3a510e4..10bd39fbd4bb 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -1,27 +1,22 @@ import { initializePython, IS_MULTI_ROOT_TEST } from './initialize'; -// -// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING -// -// This file is providing the test runner to use when running extension tests. -// By default the test runner in use is Mocha based. -// -// You can provide your own test runner if you want to override it by exporting -// a function run(testRoot: string, clb: (error:Error) => void) that the extension -// host can call to run the tests. The test runner is expected to use console.log -// to report the results back to the caller. When the tests are finished, return -// a possible error to the callback or null if none. - const testRunner = require('vscode/lib/testrunner'); -const invert = IS_MULTI_ROOT_TEST ? undefined : 'invert'; -// You can directly control Mocha options by uncommenting the following lines -// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info -testRunner.configure({ - ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) - useColors: true, // colored output from test results +const singleWorkspaceTestConfig = { + ui: 'tdd', + useColors: true, timeout: 25000, grep: 'Multiroot', - invert -}); + invert: 'invert' +}; +const multiWorkspaceTestConfig = { + ui: 'tdd', + useColors: true, + timeout: 25000, + grep: 'Jupyter', + invert: 'invert' +}; +// You can directly control Mocha options by uncommenting the following lines. +// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info. +testRunner.configure(IS_MULTI_ROOT_TEST ? multiWorkspaceTestConfig : singleWorkspaceTestConfig); module.exports = testRunner; diff --git a/src/test/linters/base.test.ts b/src/test/linters/base.test.ts deleted file mode 100644 index e6efa35b7add..000000000000 --- a/src/test/linters/base.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -// // -// // Note: This example test is leveraging the Mocha test framework. -// // Please refer to their documentation on https://mochajs.org/ for help. -// // Place this right on top -// import { initialize, IS_TRAVIS, PYTHON_PATH, closeActiveWindows, setPythonExecutable } from '../initialize'; -// // The module \'assert\' provides assertion methods from node -// import * as assert from 'assert'; - -// // You can import and use all API from the \'vscode\' module -// // as well as import your extension to test it -// import { EnumEx } from '../../client/common/enumUtils'; -// import { LinterFactor } from '../../client/linters/main'; -// import { SettingToDisableProduct, Product } from '../../client/common/installer'; -// import * as baseLinter from '../../client/linters/baseLinter'; -// import * as path from 'path'; -// import * as settings from '../../client/common/configSettings'; -// import { MockOutputChannel } from '../mockClasses'; -// import { Disposable } from 'vscode'; - -// const pythonSettings = settings.PythonSettings.getInstance(); -// const pythoFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'linting'); -// let disposable: Disposable; -// suite('Linting', () => { -// suiteSetup(() => { -// disposable = setPythonExecutable(pythonSettings); -// }); -// setup(() => { -// pythonSettings.linting.enabled = true; -// pythonSettings.linting.pylintEnabled = true; -// pythonSettings.linting.flake8Enabled = true; -// pythonSettings.linting.pep8Enabled = true; -// pythonSettings.linting.prospectorEnabled = true; -// pythonSettings.linting.pydocstyleEnabled = true; -// pythonSettings.linting.mypyEnabled = true; -// pythonSettings.linting.pylamaEnabled = true; -// }); -// suiteTeardown(done => { -// if (disposable) { disposable.dispose(); } -// closeActiveWindows().then(() => done(), () => done()); -// }); -// teardown(done => { -// closeActiveWindows().then(() => done(), () => done()); -// }); - -// function testEnablingDisablingOfLinter(linter: baseLinter.BaseLinter, propertyName: string) { -// pythonSettings.linting[propertyName] = true; -// assert.equal(true, linter.isEnabled()); - -// pythonSettings.linting[propertyName] = false; -// assert.equal(false, linter.isEnabled()); -// } -// EnumEx.getNamesAndValues(Product).forEach(product => { -// if (product.value === Product.autopep8 || -// product.value === Product.ctags || -// product.value === Product.pytest || -// product.value === Product.unittest || -// product.value === Product.yapf || -// product.value === Product.nosetest) { -// return; -// } -// test(`Enable and Disable ${product.name}`, () => { -// let ch = new MockOutputChannel('Lint'); -// const settingPath = SettingToDisableProduct.get(product.value); -// const settingName = path.extname(settingPath).substring(1); -// testEnablingDisablingOfLinter(LinterFactor.createLinter(product.value, ch, pythoFilesPath), settingName); -// }); -// }); -// }); \ No newline at end of file diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index c5d06918b801..de989eab0355 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -1,20 +1,19 @@ import * as assert from 'assert'; +import * as fs from 'fs-extra'; +import * as path from 'path'; import * as vscode from 'vscode'; +import { PythonSettings } from '../../client/common/configSettings'; +import { createDeferred } from '../../client/common/helpers'; +import { SettingToDisableProduct } from '../../client/common/installer'; +import { execPythonFile } from '../../client/common/utils'; import * as baseLinter from '../../client/linters/baseLinter'; -import * as pyLint from '../../client/linters/pylint'; -import * as pep8 from '../../client/linters/pep8Linter'; import * as flake8 from '../../client/linters/flake8'; +import * as pep8 from '../../client/linters/pep8Linter'; import * as prospector from '../../client/linters/prospector'; import * as pydocstyle from '../../client/linters/pydocstyle'; -import * as path from 'path'; -import * as fs from 'fs-extra'; -import { rootWorkspaceUri, updateSetting, PythonSettingKeys } from '../common'; -import { PythonSettings } from '../../client/common/configSettings'; -import { initialize, IS_TRAVIS, closeActiveWindows, IS_MULTI_ROOT_TEST, initializeTest } from '../initialize'; -import { execPythonFile } from '../../client/common/utils'; -import { createDeferred } from '../../client/common/helpers'; -import { Product, SettingToDisableProduct, Linters } from '../../client/common/installer'; -import { EnumEx } from '../../client/common/enumUtils'; +import * as pyLint from '../../client/linters/pylint'; +import { PythonSettingKeys, rootWorkspaceUri, updateSetting } from '../common'; +import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; import { MockOutputChannel } from '../mockClasses'; const pythoFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'linting'); @@ -25,7 +24,7 @@ const pylintConfigPath = path.join(pythoFilesPath, 'pylintconfig'); const fileToLint = path.join(pythoFilesPath, 'file.py'); let pylintFileToLintLines: string[] = []; -let pylintMessagesToBeReturned: baseLinter.ILintMessage[] = [ +const pylintMessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 24, column: 0, severity: baseLinter.LintMessageSeverity.Information, code: 'I0011', message: 'Locally disabling no-member (E1101)', provider: '', type: '' }, { line: 30, column: 0, severity: baseLinter.LintMessageSeverity.Information, code: 'I0011', message: 'Locally disabling no-member (E1101)', provider: '', type: '' }, { line: 34, column: 0, severity: baseLinter.LintMessageSeverity.Information, code: 'I0012', message: 'Locally enabling no-member (E1101)', provider: '', type: '' }, @@ -47,10 +46,7 @@ let pylintMessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 77, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' }, { line: 83, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' } ]; -let pyLint3MessagesToBeReturned: baseLinter.ILintMessage[] = [ - { line: 13, column: 0, severity: baseLinter.LintMessageSeverity.Error, code: 'E0001', message: 'Missing parentheses in call to \'print\'', provider: '', type: '' } -]; -let flake8MessagesToBeReturned: baseLinter.ILintMessage[] = [ +const flake8MessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 5, column: 1, severity: baseLinter.LintMessageSeverity.Error, code: 'E302', message: 'expected 2 blank lines, found 1', provider: '', type: '' }, { line: 19, column: 15, severity: baseLinter.LintMessageSeverity.Error, code: 'E127', message: 'continuation line over-indented for visual indent', provider: '', type: '' }, { line: 24, column: 23, severity: baseLinter.LintMessageSeverity.Error, code: 'E261', message: 'at least two spaces before inline comment', provider: '', type: '' }, @@ -59,7 +55,7 @@ let flake8MessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 80, column: 5, severity: baseLinter.LintMessageSeverity.Error, code: 'E303', message: 'too many blank lines (2)', provider: '', type: '' }, { line: 87, column: 24, severity: baseLinter.LintMessageSeverity.Warning, code: 'W292', message: 'no newline at end of file', provider: '', type: '' } ]; -let pep8MessagesToBeReturned: baseLinter.ILintMessage[] = [ +const pep8MessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 5, column: 1, severity: baseLinter.LintMessageSeverity.Error, code: 'E302', message: 'expected 2 blank lines, found 1', provider: '', type: '' }, { line: 19, column: 15, severity: baseLinter.LintMessageSeverity.Error, code: 'E127', message: 'continuation line over-indented for visual indent', provider: '', type: '' }, { line: 24, column: 23, severity: baseLinter.LintMessageSeverity.Error, code: 'E261', message: 'at least two spaces before inline comment', provider: '', type: '' }, @@ -68,30 +64,30 @@ let pep8MessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 80, column: 5, severity: baseLinter.LintMessageSeverity.Error, code: 'E303', message: 'too many blank lines (2)', provider: '', type: '' }, { line: 87, column: 24, severity: baseLinter.LintMessageSeverity.Warning, code: 'W292', message: 'no newline at end of file', provider: '', type: '' } ]; -let pydocstyleMessagseToBeReturned: baseLinter.ILintMessage[] = [ - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'e\')', 'column': 0, 'line': 1, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'t\')', 'column': 0, 'line': 5, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D102', severity: baseLinter.LintMessageSeverity.Information, 'message': 'Missing docstring in public method', 'column': 4, 'line': 8, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D401', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should be in imperative mood (\'thi\', not \'this\')', 'column': 4, 'line': 11, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D403', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First word of the first line should be properly capitalized (\'This\', not \'this\')', 'column': 4, 'line': 11, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'e\')', 'column': 4, 'line': 11, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D403', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First word of the first line should be properly capitalized (\'And\', not \'and\')', 'column': 4, 'line': 15, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'t\')', 'column': 4, 'line': 15, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D403', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', 'column': 4, 'line': 21, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'g\')', 'column': 4, 'line': 21, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D403', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', 'column': 4, 'line': 28, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'g\')', 'column': 4, 'line': 28, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D403', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', 'column': 4, 'line': 38, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'g\')', 'column': 4, 'line': 38, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D403', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', 'column': 4, 'line': 53, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'g\')', 'column': 4, 'line': 53, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D403', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', 'column': 4, 'line': 68, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'g\')', 'column': 4, 'line': 68, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D403', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', 'column': 4, 'line': 80, 'type': '', 'provider': 'pydocstyle' }, - { 'code': 'D400', severity: baseLinter.LintMessageSeverity.Information, 'message': 'First line should end with a period (not \'g\')', 'column': 4, 'line': 80, 'type': '', 'provider': 'pydocstyle' } +const pydocstyleMessagseToBeReturned: baseLinter.ILintMessage[] = [ + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'e\')', column: 0, line: 1, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'t\')', column: 0, line: 5, type: '', provider: 'pydocstyle' }, + { code: 'D102', severity: baseLinter.LintMessageSeverity.Information, message: 'Missing docstring in public method', column: 4, line: 8, type: '', provider: 'pydocstyle' }, + { code: 'D401', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should be in imperative mood (\'thi\', not \'this\')', column: 4, line: 11, type: '', provider: 'pydocstyle' }, + { code: 'D403', severity: baseLinter.LintMessageSeverity.Information, message: 'First word of the first line should be properly capitalized (\'This\', not \'this\')', column: 4, line: 11, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'e\')', column: 4, line: 11, type: '', provider: 'pydocstyle' }, + { code: 'D403', severity: baseLinter.LintMessageSeverity.Information, message: 'First word of the first line should be properly capitalized (\'And\', not \'and\')', column: 4, line: 15, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'t\')', column: 4, line: 15, type: '', provider: 'pydocstyle' }, + { code: 'D403', severity: baseLinter.LintMessageSeverity.Information, message: 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', column: 4, line: 21, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'g\')', column: 4, line: 21, type: '', provider: 'pydocstyle' }, + { code: 'D403', severity: baseLinter.LintMessageSeverity.Information, message: 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', column: 4, line: 28, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'g\')', column: 4, line: 28, type: '', provider: 'pydocstyle' }, + { code: 'D403', severity: baseLinter.LintMessageSeverity.Information, message: 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', column: 4, line: 38, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'g\')', column: 4, line: 38, type: '', provider: 'pydocstyle' }, + { code: 'D403', severity: baseLinter.LintMessageSeverity.Information, message: 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', column: 4, line: 53, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'g\')', column: 4, line: 53, type: '', provider: 'pydocstyle' }, + { code: 'D403', severity: baseLinter.LintMessageSeverity.Information, message: 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', column: 4, line: 68, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'g\')', column: 4, line: 68, type: '', provider: 'pydocstyle' }, + { code: 'D403', severity: baseLinter.LintMessageSeverity.Information, message: 'First word of the first line should be properly capitalized (\'Test\', not \'test\')', column: 4, line: 80, type: '', provider: 'pydocstyle' }, + { code: 'D400', severity: baseLinter.LintMessageSeverity.Information, message: 'First line should end with a period (not \'g\')', column: 4, line: 80, type: '', provider: 'pydocstyle' } ]; -let filteredPylintMessagesToBeReturned: baseLinter.ILintMessage[] = [ +const filteredPylintMessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 26, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blop\' member', provider: '', type: '' }, { line: 36, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' }, { line: 46, column: 18, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' }, @@ -101,19 +97,19 @@ let filteredPylintMessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 77, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' }, { line: 83, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: 'E1101', message: 'Instance of \'Foo\' has no \'blip\' member', provider: '', type: '' } ]; -let filteredPylint3MessagesToBeReturned: baseLinter.ILintMessage[] = [ +const filteredPylint3MessagesToBeReturned: baseLinter.ILintMessage[] = [ ]; -let filteredFlake8MessagesToBeReturned: baseLinter.ILintMessage[] = [ +const filteredFlake8MessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 87, column: 24, severity: baseLinter.LintMessageSeverity.Warning, code: 'W292', message: 'no newline at end of file', provider: '', type: '' } ]; -let filteredPep88MessagesToBeReturned: baseLinter.ILintMessage[] = [ +const filteredPep88MessagesToBeReturned: baseLinter.ILintMessage[] = [ { line: 87, column: 24, severity: baseLinter.LintMessageSeverity.Warning, code: 'W292', message: 'no newline at end of file', provider: '', type: '' } ]; -let fiteredPydocstyleMessagseToBeReturned: baseLinter.ILintMessage[] = [ - { 'code': 'D102', severity: baseLinter.LintMessageSeverity.Information, 'message': 'Missing docstring in public method', 'column': 4, 'line': 8, 'type': '', 'provider': 'pydocstyle' } +const fiteredPydocstyleMessagseToBeReturned: baseLinter.ILintMessage[] = [ + { code: 'D102', severity: baseLinter.LintMessageSeverity.Information, message: 'Missing docstring in public method', column: 4, line: 8, type: '', provider: 'pydocstyle' } ]; - +// tslint:disable-next-line:max-func-body-length suite('Linting', () => { const isPython3Deferred = createDeferred(); const isPython3 = isPython3Deferred.promise; @@ -127,148 +123,155 @@ suite('Linting', () => { await initializeTest(); await resetSettings(); }); - suiteTeardown(() => closeActiveWindows()); + suiteTeardown(closeActiveWindows); teardown(async () => { await closeActiveWindows(); await resetSettings(); }); async function resetSettings() { + // Don't run these updates in parallel, as they are updating the same file. + await updateSetting('linting.enabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + if (IS_MULTI_ROOT_TEST) { + await updateSetting('linting.enabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + } await updateSetting('linting.lintOnSave', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); await updateSetting('linting.lintOnTextChange', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); - await updateSetting('linting.enabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); - await updateSetting('linting.pylintEnabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); - await updateSetting('linting.flake8Enabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); - await updateSetting('linting.pep8Enabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); - await updateSetting('linting.prospectorEnabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); - await updateSetting('linting.pydocstyleEnabled', true, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + await updateSetting('linting.pylintEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + await updateSetting('linting.flake8Enabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + await updateSetting('linting.pep8Enabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + await updateSetting('linting.prospectorEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + await updateSetting('linting.mypyEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + await updateSetting('linting.pydocstyleEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + await updateSetting('linting.pylamaEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.Workspace); + + if (IS_MULTI_ROOT_TEST) { + await updateSetting('linting.lintOnSave', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + await updateSetting('linting.lintOnTextChange', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + await updateSetting('linting.pylintEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + await updateSetting('linting.flake8Enabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + await updateSetting('linting.pep8Enabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + await updateSetting('linting.prospectorEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + await updateSetting('linting.mypyEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + await updateSetting('linting.pydocstyleEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + await updateSetting('linting.pylamaEnabled', false, rootWorkspaceUri, vscode.ConfigurationTarget.WorkspaceFolder); + } } - async function testEnablingDisablingOfLinter(linter: baseLinter.BaseLinter, setting: PythonSettingKeys) { - updateSetting(setting, true, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.Workspace : vscode.ConfigurationTarget.WorkspaceFolder) - let cancelToken = new vscode.CancellationTokenSource(); - disableAllButThisLinter(linter.product); + async function testEnablingDisablingOfLinter(linter: baseLinter.BaseLinter, setting: PythonSettingKeys, enabled: boolean, output: MockOutputChannel) { + await updateSetting(setting, enabled, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace); const document = await vscode.workspace.openTextDocument(fileToLint); const editor = await vscode.window.showTextDocument(document); - try { - const messages = await linter.lint(editor.document, cancelToken.token); - assert.equal(messages.length, 0, 'Errors returned even when linter is disabled'); - } catch (error) { - assert.fail(error, null, 'Linter error'); + const cancelToken = new vscode.CancellationTokenSource(); + const messages = await linter.lint(editor.document, cancelToken.token); + if (enabled) { + assert.notEqual(messages.length, 0, `No linter errors when linter is enabled, Output - ${output.output}`); + } + else { + assert.equal(messages.length, 0, `Errors returned when linter is disabled, Output - ${output.output}`); } } - test('Enable and Disable Pylint', () => { - let ch = new MockOutputChannel('Lint'); - testEnablingDisablingOfLinter(new pyLint.Linter(ch), 'linting.pylintEnabled'); + test('Disable Pylint and test linter', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new pyLint.Linter(ch), 'linting.pylintEnabled', false, ch); + }); + test('Enable Pylint and test linter', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new pyLint.Linter(ch), 'linting.pylintEnabled', true, ch); + }); + test('Disable Pep8 and test linter', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new pep8.Linter(ch), 'linting.pep8Enabled', false, ch); }); - test('Enable and Disable Pep8', () => { - let ch = new MockOutputChannel('Lint'); - testEnablingDisablingOfLinter(new pep8.Linter(ch), 'linting.pep8Enabled'); + test('Enable Pep8 and test linter', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new pep8.Linter(ch), 'linting.pep8Enabled', true, ch); }); - test('Enable and Disable Flake8', () => { - let ch = new MockOutputChannel('Lint'); - testEnablingDisablingOfLinter(new flake8.Linter(ch), 'linting.flake8Enabled'); + test('Disable Flake8 and test linter', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new flake8.Linter(ch), 'linting.flake8Enabled', false, ch); }); - test('Enable and Disable Prospector', () => { - let ch = new MockOutputChannel('Lint'); - testEnablingDisablingOfLinter(new prospector.Linter(ch), 'linting.prospectorEnabled'); + test('Enable Flake8 and test linter', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new flake8.Linter(ch), 'linting.flake8Enabled', true, ch); }); - test('Enable and Disable Pydocstyle', () => { - let ch = new MockOutputChannel('Lint'); - testEnablingDisablingOfLinter(new pydocstyle.Linter(ch), 'linting.pydocstyleEnabled'); + test('Disable Prospector and test linter', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new prospector.Linter(ch), 'linting.prospectorEnabled', false, ch); + }); + test('Disable Pydocstyle and test linter', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new pydocstyle.Linter(ch), 'linting.pydocstyleEnabled', false, ch); + }); + test('Enable Pydocstyle and test linter', async () => { + const ch = new MockOutputChannel('Lint'); + await testEnablingDisablingOfLinter(new pydocstyle.Linter(ch), 'linting.pydocstyleEnabled', true, ch); }); - async function disableAllButThisLinter(linterToEnable: Product) { - const promises = EnumEx.getNamesAndValues(Product).map(async linter => { - if (Linters.indexOf(linter.value) === -1) { - return; + // tslint:disable-next-line:no-any + async function testLinterMessages(linter: baseLinter.BaseLinter, outputChannel: MockOutputChannel, pythonFile: string, messagesToBeReceived: baseLinter.ILintMessage[]): Promise { + const cancelToken = new vscode.CancellationTokenSource(); + const settingToEnable = SettingToDisableProduct.get(linter.product); + // tslint:disable-next-line:no-any prefer-type-cast + await updateSetting(settingToEnable as any, true, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace); + const document = await vscode.workspace.openTextDocument(pythonFile); + const editor = await vscode.window.showTextDocument(document); + const messages = await linter.lint(editor.document, cancelToken.token); + if (messagesToBeReceived.length === 0) { + assert.equal(messages.length, 0, `No errors in linter, Output - ${outputChannel.output}`); + } + else { + if (outputChannel.output.indexOf('ENOENT') === -1) { + // Pylint for Python Version 2.7 could return 80 linter messages, where as in 3.5 it might only return 1. + // Looks like pylint stops linting as soon as it comes across any ERRORS. + assert.notEqual(messages.length, 0, `No errors in linter, Output - ${outputChannel.output}`); } - var setting = SettingToDisableProduct.get(linter.value); - return updateSetting(setting as any, true, rootWorkspaceUri, IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.Workspace : vscode.ConfigurationTarget.WorkspaceFolder); - }); - await Promise.all(promises); - } - function testLinterMessages(linter: baseLinter.BaseLinter, outputChannel: MockOutputChannel, pythonFile: string, messagesToBeReceived: baseLinter.ILintMessage[]): Thenable { - - let cancelToken = new vscode.CancellationTokenSource(); - return disableAllButThisLinter(linter.product) - .then(() => vscode.workspace.openTextDocument(pythonFile)) - .then(document => vscode.window.showTextDocument(document)) - .then(editor => { - return linter.lint(editor.document, cancelToken.token); - }) - .then(messages => { - // Different versions of python return different errors, - if (messagesToBeReceived.length === 0) { - assert.equal(messages.length, 0, 'No errors in linter, Output - ' + outputChannel.output); - } - else { - if (outputChannel.output.indexOf('ENOENT') === -1) { - // Pylint for Python Version 2.7 could return 80 linter messages, where as in 3.5 it might only return 1 - // Looks like pylint stops linting as soon as it comes across any ERRORS - assert.notEqual(messages.length, 0, 'No errors in linter, Output - ' + outputChannel.output); - } - else { - assert.ok('Linter not installed', 'Linter not installed'); - } - } - // messagesToBeReceived.forEach(msg => { - // let similarMessages = messages.filter(m => m.code === msg.code && m.column === msg.column && - // m.line === msg.line && m.message === msg.message && m.severity === msg.severity); - // assert.equal(true, similarMessages.length > 0, 'Error not found, ' + JSON.stringify(msg) + '\n, Output - ' + outputChannel.output); - // }); - }, error => { - assert.fail(error, null, 'Linter error, Output - ' + outputChannel.output, ''); - }); + } } - test('PyLint', done => { - let ch = new MockOutputChannel('Lint'); - let linter = new pyLint.Linter(ch); - testLinterMessages(linter, ch, fileToLint, pylintMessagesToBeReturned).then(done, done); + test('PyLint', async () => { + const ch = new MockOutputChannel('Lint'); + const linter = new pyLint.Linter(ch); + await testLinterMessages(linter, ch, fileToLint, pylintMessagesToBeReturned); }); - test('Flake8', done => { - let ch = new MockOutputChannel('Lint'); - let linter = new flake8.Linter(ch); - testLinterMessages(linter, ch, fileToLint, flake8MessagesToBeReturned).then(done, done); + test('Flake8', async () => { + const ch = new MockOutputChannel('Lint'); + const linter = new flake8.Linter(ch); + await testLinterMessages(linter, ch, fileToLint, flake8MessagesToBeReturned); }); - test('Pep8', done => { - let ch = new MockOutputChannel('Lint'); - let linter = new pep8.Linter(ch); - testLinterMessages(linter, ch, fileToLint, pep8MessagesToBeReturned).then(done, done); + test('Pep8', async () => { + const ch = new MockOutputChannel('Lint'); + const linter = new pep8.Linter(ch); + await testLinterMessages(linter, ch, fileToLint, pep8MessagesToBeReturned); }); - if (!isPython3) { - test('Pydocstyle', done => { - let ch = new MockOutputChannel('Lint'); - let linter = new pydocstyle.Linter(ch); - testLinterMessages(linter, ch, fileToLint, pydocstyleMessagseToBeReturned).then(done, done); - }); - } - // Version dependenant, will be enabled once we have fixed this - // TODO: Check version of python running and accordingly change the values - if (!IS_TRAVIS) { - isPython3.then(value => { - const messagesToBeReturned = value ? filteredPylint3MessagesToBeReturned : filteredPylintMessagesToBeReturned; - test('PyLint with config in root', done => { - let ch = new MockOutputChannel('Lint'); - let linter = new pyLint.Linter(ch); - testLinterMessages(linter, ch, path.join(pylintConfigPath, 'file.py'), messagesToBeReturned).then(done, done); - }); + test('Pydocstyle', async () => { + const ch = new MockOutputChannel('Lint'); + const linter = new pydocstyle.Linter(ch); + await testLinterMessages(linter, ch, fileToLint, pydocstyleMessagseToBeReturned); + }); + // tslint:disable-next-line:no-floating-promises + isPython3.then(value => { + const messagesToBeReturned = value ? filteredPylint3MessagesToBeReturned : filteredPylintMessagesToBeReturned; + test('PyLint with config in root', async () => { + const ch = new MockOutputChannel('Lint'); + const linter = new pyLint.Linter(ch); + await testLinterMessages(linter, ch, path.join(pylintConfigPath, 'file.py'), messagesToBeReturned); }); - } - test('Flake8 with config in root', done => { - let ch = new MockOutputChannel('Lint'); - let linter = new flake8.Linter(ch); - testLinterMessages(linter, ch, path.join(flake8ConfigPath, 'file.py'), filteredFlake8MessagesToBeReturned).then(done, done); }); - test('Pep8 with config in root', done => { - let ch = new MockOutputChannel('Lint'); - let linter = new pep8.Linter(ch); - testLinterMessages(linter, ch, path.join(pep8ConfigPath, 'file.py'), filteredPep88MessagesToBeReturned).then(done, done); + test('Flake8 with config in root', async () => { + const ch = new MockOutputChannel('Lint'); + const linter = new flake8.Linter(ch); + await testLinterMessages(linter, ch, path.join(flake8ConfigPath, 'file.py'), filteredFlake8MessagesToBeReturned); + }); + test('Pep8 with config in root', async () => { + const ch = new MockOutputChannel('Lint'); + const linter = new pep8.Linter(ch); + await testLinterMessages(linter, ch, path.join(pep8ConfigPath, 'file.py'), filteredPep88MessagesToBeReturned); }); + // tslint:disable-next-line:no-floating-promises isPython3.then(value => { const messagesToBeReturned = value ? [] : fiteredPydocstyleMessagseToBeReturned; - test('Pydocstyle with config in root', done => { - let ch = new MockOutputChannel('Lint'); - let linter = new pydocstyle.Linter(ch); - testLinterMessages(linter, ch, path.join(pydocstyleConfigPath27, 'file.py'), messagesToBeReturned).then(done, done); + test('Pydocstyle with config in root', async () => { + const ch = new MockOutputChannel('Lint'); + const linter = new pydocstyle.Linter(ch); + await testLinterMessages(linter, ch, path.join(pydocstyleConfigPath27, 'file.py'), messagesToBeReturned); }); }); }); diff --git a/src/test/pythonFiles/linting/file.py b/src/test/pythonFiles/linting/file.py index 047ba0dc679e..7b625a769243 100644 --- a/src/test/pythonFiles/linting/file.py +++ b/src/test/pythonFiles/linting/file.py @@ -10,78 +10,78 @@ def __init__(self): def meth1(self, arg): """this issues a message""" - print self + print (self) def meth2(self, arg): """and this one not""" # pylint: disable=unused-argument - print self\ - + "foo" + print (self\ + + "foo") def meth3(self): """test one line disabling""" # no error - print self.bla # pylint: disable=no-member + print (self.bla) # pylint: disable=no-member # error - print self.blop + print (self.blop) def meth4(self): """test re-enabling""" # pylint: disable=no-member # no error - print self.bla - print self.blop + print (self.bla) + print (self.blop) # pylint: enable=no-member # error - print self.blip + print (self.blip) def meth5(self): """test IF sub-block re-enabling""" # pylint: disable=no-member # no error - print self.bla + print (self.bla) if self.blop: # pylint: enable=no-member # error - print self.blip + print (self.blip) else: # no error - print self.blip + print (self.blip) # no error - print self.blip + print (self.blip) def meth6(self): """test TRY/EXCEPT sub-block re-enabling""" # pylint: disable=no-member # no error - print self.bla + print (self.bla) try: # pylint: enable=no-member # error - print self.blip + print (self.blip) except UndefinedName: # pylint: disable=undefined-variable # no error - print self.blip + print (self.blip) # no error - print self.blip + print (self.blip) def meth7(self): """test one line block opening disabling""" if self.blop: # pylint: disable=no-member # error - print self.blip + print (self.blip) else: # error - print self.blip + print (self.blip) # error - print self.blip + print (self.blip) def meth8(self): """test late disabling""" # error - print self.blip + print (self.blip) # pylint: disable=no-member # no error - print self.bla - print self.blop \ No newline at end of file + print (self.bla) + print (self.blop) diff --git a/src/test/unittests/unittest.test.ts b/src/test/unittests/unittest.test.ts index 211423086476..478111c1d620 100644 --- a/src/test/unittests/unittest.test.ts +++ b/src/test/unittests/unittest.test.ts @@ -1,13 +1,13 @@ import * as assert from 'assert'; -import * as path from 'path'; import * as fs from 'fs-extra'; -import * as unittest from '../../client/unittests/unittest/main'; -import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; +import * as path from 'path'; +import { ConfigurationTarget } from 'vscode'; import { TestsToRun } from '../../client/unittests/common/contracts'; import { TestResultDisplay } from '../../client/unittests/display/main'; -import { MockOutputChannel } from './../mockClasses'; -import { ConfigurationTarget } from 'vscode'; +import * as unittest from '../../client/unittests/unittest/main'; import { rootWorkspaceUri, updateSetting } from '../common'; +import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; +import { MockOutputChannel } from './../mockClasses'; const testFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles'); const UNITTEST_TEST_FILES_PATH = path.join(testFilesPath, 'standard'); @@ -15,14 +15,19 @@ const UNITTEST_SINGLE_TEST_FILE_PATH = path.join(testFilesPath, 'single'); const unitTestTestFilesCwdPath = path.join(testFilesPath, 'cwd', 'src'); const unitTestSpecificTestFilesPath = path.join(testFilesPath, 'specificTest'); const defaultUnitTestArgs = [ - "-v", - "-s", - ".", - "-p", - "*test*.py" + '-v', + '-s', + '.', + '-p', + '*test*.py' ]; +// tslint:disable-next-line:max-func-body-length suite('Unit Tests (unittest)', () => { + let testManager: unittest.TestManager; + let testResultDisplay: TestResultDisplay; + let outChannel: MockOutputChannel; + const rootDirectory = UNITTEST_TEST_FILES_PATH; const configTarget = IS_MULTI_ROOT_TEST ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Workspace; suiteSetup(async () => { await initialize(); @@ -31,7 +36,10 @@ suite('Unit Tests (unittest)', () => { setup(async () => { outChannel = new MockOutputChannel('Python Test Log'); testResultDisplay = new TestResultDisplay(outChannel); - await fs.remove(path.join(UNITTEST_TEST_FILES_PATH, '.cache')).catch(() => undefined); + const cachePath = path.join(UNITTEST_TEST_FILES_PATH, '.cache'); + if (await fs.pathExists(cachePath)) { + await fs.remove(cachePath); + } await initializeTest(); }); teardown(() => { @@ -42,10 +50,6 @@ suite('Unit Tests (unittest)', () => { function createTestManager(rootDir: string = rootDirectory) { testManager = new unittest.TestManager(rootDir, outChannel); } - const rootDirectory = UNITTEST_TEST_FILES_PATH; - let testManager: unittest.TestManager; - let testResultDisplay: TestResultDisplay; - let outChannel: MockOutputChannel; test('Discover Tests (single test file)', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_*.py'], rootWorkspaceUri, configTarget); @@ -88,22 +92,6 @@ suite('Unit Tests (unittest)', () => { assert.equal(results.summary.skipped, 1, 'skipped'); }); - // test('Fail Fast', done => { - // pythonSettings.unitTest.unittestArgs = [ - // '-s=./tests', - // '-p=*test*.py', - // '--failfast' - // ]; - // createTestManager(); - // testManager.runTest().then(results => { - // assert.equal(results.summary.errors, 1, 'Errors'); - // assert.equal(results.summary.failures, 5, 'Failures'); - // assert.equal(results.summary.passed, 4, 'Passed'); - // assert.equal(results.summary.skipped, 1, 'skipped'); - // done(); - // }).catch(done); - // }); - test('Run Failed Tests', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_unittest*.py'], rootWorkspaceUri, configTarget); createTestManager(); @@ -125,7 +113,8 @@ suite('Unit Tests (unittest)', () => { createTestManager(unitTestSpecificTestFilesPath); const tests = await testManager.discoverTests(true, true); - const testFileToTest = tests.testFiles.find(f => f.name === 'test_unittest_one.py'); + // tslint:disable-next-line:no-non-null-assertion + const testFileToTest = tests.testFiles.find(f => f.name === 'test_unittest_one.py')!; const testFile: TestsToRun = { testFile: [testFileToTest], testFolder: [], testFunction: [], testSuite: [] }; const results = await testManager.runTest(testFile); @@ -140,6 +129,7 @@ suite('Unit Tests (unittest)', () => { createTestManager(unitTestSpecificTestFilesPath); const tests = await testManager.discoverTests(true, true); + // tslint:disable-next-line:no-non-null-assertion const testSuiteToTest = tests.testSuits.find(s => s.testSuite.name === 'Test_test_one_1')!.testSuite; const testSuite: TestsToRun = { testFile: [], testFolder: [], testFunction: [], testSuite: [testSuiteToTest] }; const results = await testManager.runTest(testSuite); @@ -172,4 +162,4 @@ suite('Unit Tests (unittest)', () => { assert.equal(tests.testFunctions.length, 1, 'Incorrect number of test functions'); assert.equal(tests.testSuits.length, 1, 'Incorrect number of test suites'); }); -}); +}); diff --git a/src/test/workspaceSymbols/standard.test.ts b/src/test/workspaceSymbols/standard.test.ts index 3251ad85ccdb..b2859b0fc428 100644 --- a/src/test/workspaceSymbols/standard.test.ts +++ b/src/test/workspaceSymbols/standard.test.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as path from 'path'; import { CancellationTokenSource, ConfigurationTarget, Uri } from 'vscode'; -import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; import { Generator } from '../../client/workspaceSymbols/generator'; import { MockOutputChannel } from '../mockClasses'; import { WorkspaceSymbolProvider } from '../../client/workspaceSymbols/provider'; @@ -9,6 +9,7 @@ import { updateSetting } from './../common'; import { PythonSettings } from '../../client/common/configSettings'; const workspaceUri = Uri.file(path.join(__dirname, '..', '..', '..', 'src', 'test')); +const configUpdateTarget = IS_MULTI_ROOT_TEST ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Workspace; suite('Workspace Symbols', () => { suiteSetup(() => initialize()); @@ -16,12 +17,12 @@ suite('Workspace Symbols', () => { setup(() => initializeTest()); teardown(async () => { await closeActiveWindows(); - await updateSetting('workspaceSymbols.enabled', false, workspaceUri, ConfigurationTarget.Workspace); + await updateSetting('workspaceSymbols.enabled', false, workspaceUri, configUpdateTarget); }); test(`symbols should be returned when enabeld and vice versa`, async () => { const outputChannel = new MockOutputChannel('Output'); - await updateSetting('workspaceSymbols.enabled', false, workspaceUri, ConfigurationTarget.Workspace); + await updateSetting('workspaceSymbols.enabled', false, workspaceUri, configUpdateTarget); // The workspace will be in the output test folder // So lets modify the settings so it sees the source test folder @@ -34,7 +35,7 @@ suite('Workspace Symbols', () => { assert.equal(symbols.length, 0, 'Symbols returned even when workspace symbols are turned off'); generator.dispose(); - await updateSetting('workspaceSymbols.enabled', true, workspaceUri, ConfigurationTarget.Workspace); + await updateSetting('workspaceSymbols.enabled', true, workspaceUri, configUpdateTarget); // The workspace will be in the output test folder // So lets modify the settings so it sees the source test folder @@ -49,7 +50,7 @@ suite('Workspace Symbols', () => { test(`symbols should be filtered correctly`, async () => { const outputChannel = new MockOutputChannel('Output'); - await updateSetting('workspaceSymbols.enabled', true, workspaceUri, ConfigurationTarget.Workspace); + await updateSetting('workspaceSymbols.enabled', true, workspaceUri, configUpdateTarget); // The workspace will be in the output test folder // So lets modify the settings so it sees the source test folder diff --git a/src/testMultiRootWkspc/multi.code-workspace b/src/testMultiRootWkspc/multi.code-workspace index 891cdf0bd28d..09c3fbb10f1b 100644 --- a/src/testMultiRootWkspc/multi.code-workspace +++ b/src/testMultiRootWkspc/multi.code-workspace @@ -21,13 +21,19 @@ ], "settings": { "python.linting.flake8Enabled": false, - "python.linting.mypyEnabled": true, - "python.linting.pydocstyleEnabled": true, - "python.linting.pylamaEnabled": true, + "python.linting.mypyEnabled": false, + "python.linting.pydocstyleEnabled": false, + "python.linting.pylamaEnabled": false, "python.linting.pylintEnabled": false, - "python.linting.pep8Enabled": true, - "python.linting.prospectorEnabled": true, - "python.workspaceSymbols.enabled": true, + "python.linting.pep8Enabled": false, + "python.linting.prospectorEnabled": false, + "python.workspaceSymbols.enabled": false, + "python.formatting.formatOnSave": false, + "python.formatting.provider": "yapf", + "python.sortImports.args": [], + "python.linting.lintOnSave": false, + "python.linting.lintOnTextChange": false, + "python.linting.enabled": true, "python.pythonPath": "python" } } diff --git a/tsfmt.json b/tsfmt.json index 088362e2043d..fffcf07c1998 100644 --- a/tsfmt.json +++ b/tsfmt.json @@ -1,6 +1,7 @@ { "tabSize": 4, "indentSize": 4, + "newLineCharacter": "\n", "convertTabsToSpaces": false, "insertSpaceAfterCommaDelimiter": true, "insertSpaceAfterSemicolonInForStatements": true, diff --git a/tslint.json b/tslint.json index 8868d292a5b8..a3918b71ef07 100644 --- a/tslint.json +++ b/tslint.json @@ -18,12 +18,20 @@ "typedef": false, "no-string-throw": true, "missing-jsdoc": false, - "one-line": false, + "one-line": [true, "check-catch", "check-finally", "check-else"], "no-parameter-properties": false, "no-reserved-keywords": false, "newline-before-return": false, "export-name": false, "align": false, - "linebreak-style": false + "linebreak-style": false, + "strict-boolean-expressions": [ + true, + "allow-null-union", + "allow-undefined-union", + "allow-string", + "allow-number" + ], + "await-promise": [true, "Thenable"] } }