diff --git a/src/client/common/markdown/restTextConverter.ts b/src/client/common/markdown/restTextConverter.ts index e606fd46bfbc..2119775173fb 100644 --- a/src/client/common/markdown/restTextConverter.ts +++ b/src/client/common/markdown/restTextConverter.ts @@ -36,7 +36,8 @@ export class RestTextConverter { return text .replace(/\#/g, '\\#') .replace(/\*/g, '\\*') - .replace(/\_/g, '\\_'); + .replace(/\ _/g, ' \\_') + .replace(/^_/, '\\_'); } private transformLines(docstring: string): string { diff --git a/src/client/interpreter/locators/services/globalVirtualEnvService.ts b/src/client/interpreter/locators/services/globalVirtualEnvService.ts index 8f3160386dc8..12a03d006fac 100644 --- a/src/client/interpreter/locators/services/globalVirtualEnvService.ts +++ b/src/client/interpreter/locators/services/globalVirtualEnvService.ts @@ -42,9 +42,11 @@ export class GlobalVirtualEnvironmentsSearchPathProvider implements IVirtualEnvi folders.push(pyenvRoot); folders.push(path.join(pyenvRoot, 'versions')); } else { + // Check if .pyenv/versions is in the list const pyenvVersions = path.join('.pyenv', 'versions'); if (venvFolders.indexOf('.pyenv') >= 0 && venvFolders.indexOf(pyenvVersions) < 0) { - folders.push(pyenvVersions); + // if .pyenv is in the list, but .pyenv/versions is not, add it. + folders.push(path.join(homedir, pyenvVersions)); } } return folders; diff --git a/src/client/linters/linterCommands.ts b/src/client/linters/linterCommands.ts index 5ecde143c1cf..9ab7a673467a 100644 --- a/src/client/linters/linterCommands.ts +++ b/src/client/linters/linterCommands.ts @@ -79,9 +79,9 @@ export class LinterCommands implements vscode.Disposable { } } - public runLinting(): void { + public runLinting(): Promise { const engine = this.serviceContainer.get(ILintingEngine); - engine.lintOpenPythonFiles(); + return engine.lintOpenPythonFiles(); } private get settingsUri(): vscode.Uri | undefined { diff --git a/src/client/linters/lintingEngine.ts b/src/client/linters/lintingEngine.ts index b14d33454d04..650df50fa913 100644 --- a/src/client/linters/lintingEngine.ts +++ b/src/client/linters/lintingEngine.ts @@ -6,7 +6,8 @@ import { Minimatch } from 'minimatch'; import * as path from 'path'; import * as vscode from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../common/application/types'; -import { LinterErrors, PythonLanguage, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; +import { LinterErrors, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; +import { IFileSystem } from '../common/platform/types'; import { IConfigurationService, IOutputChannel } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { JupyterProvider } from '../jupyter/provider'; @@ -40,6 +41,7 @@ export class LintingEngine implements ILintingEngine { private diagnosticCollection: vscode.DiagnosticCollection; private pendingLintings = new Map(); private outputChannel: vscode.OutputChannel; + private fileSystem: IFileSystem; constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { this.documentHasJupyterCodeCells = (a, b) => Promise.resolve(false); @@ -48,34 +50,32 @@ export class LintingEngine implements ILintingEngine { this.configurationService = serviceContainer.get(IConfigurationService); this.outputChannel = serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); this.linterManager = serviceContainer.get(ILinterManager); + this.fileSystem = serviceContainer.get(IFileSystem); this.diagnosticCollection = vscode.languages.createDiagnosticCollection('python'); } - public lintOpenPythonFiles(): void { - this.documents.textDocuments.forEach(async document => { - if (document.languageId === PythonLanguage.language) { - await this.lintDocument(document, 'auto'); - } - }); + public get diagnostics(): vscode.DiagnosticCollection { + return this.diagnosticCollection; } - public async lintDocument(document: vscode.TextDocument, trigger: LinterTrigger): Promise { - // Check if we need to lint this document - const workspaceFolder = this.workspace.getWorkspaceFolder(document.uri); - const workspaceRootPath = (workspaceFolder && typeof workspaceFolder.uri.fsPath === 'string') ? workspaceFolder.uri.fsPath : undefined; - const relativeFileName = typeof workspaceRootPath === 'string' ? path.relative(workspaceRootPath, document.fileName) : document.fileName; - const settings = this.configurationService.getSettings(document.uri); - if (document.languageId !== PythonLanguage.language) { - return; + public clearDiagnostics(document: vscode.TextDocument): void { + if (this.diagnosticCollection.has(document.uri)) { + this.diagnosticCollection.delete(document.uri); } - if (!this.linterManager.isLintingEnabled(document.uri)) { - this.diagnosticCollection.set(document.uri, []); - } - const ignoreMinmatches = settings.linting.ignorePatterns.map(pattern => { - return new Minimatch(pattern); - }); + } - if (ignoreMinmatches.some(matcher => matcher.match(document.fileName) || matcher.match(relativeFileName))) { + public async lintOpenPythonFiles(): Promise { + this.diagnosticCollection.clear(); + const promises = this.documents.textDocuments.map(async document => await this.lintDocument(document, 'auto')); + await Promise.all(promises); + return this.diagnosticCollection; + } + + public async lintDocument(document: vscode.TextDocument, trigger: LinterTrigger): Promise { + this.diagnosticCollection.set(document.uri, []); + + // Check if we need to lint this document + if (!await this.shouldLintDocument(document)) { return; } @@ -107,6 +107,7 @@ export class LintingEngine implements ILintingEngine { // linters will resolve asynchronously - keep a track of all // diagnostics reported as them come in. let diagnostics: vscode.Diagnostic[] = []; + const settings = this.configurationService.getSettings(document.uri); for (const p of promises) { const msgs = await p; @@ -114,7 +115,6 @@ export class LintingEngine implements ILintingEngine { break; } - diagnostics = []; if (this.isDocumentOpen(document.uri)) { // Build the message and suffix the message with the name of the linter used. for (const m of msgs) { @@ -123,17 +123,16 @@ export class LintingEngine implements ILintingEngine { (m.code === LinterErrors.pylint.InvalidSyntax || m.code === LinterErrors.prospector.InvalidSyntax || m.code === LinterErrors.flake8.InvalidSyntax)) { - return; + continue; } diagnostics.push(this.createDiagnostics(m, document)); } - // Limit the number of messages to the max value. diagnostics = diagnostics.filter((value, index) => index <= settings.linting.maxNumberOfProblems); } - // Set all diagnostics found in this pass, as this method always clears existing diagnostics. - this.diagnosticCollection.set(document.uri, diagnostics); } + // Set all diagnostics found in this pass, as this method always clears existing diagnostics. + this.diagnosticCollection.set(document.uri, diagnostics); } // tslint:disable-next-line:no-any @@ -175,4 +174,29 @@ export class LintingEngine implements ILintingEngine { diagnostic.source = message.provider; return diagnostic; } + + private async shouldLintDocument(document: vscode.TextDocument): Promise { + if (!this.linterManager.isLintingEnabled(document.uri)) { + this.diagnosticCollection.set(document.uri, []); + return false; + } + + if (document.languageId !== PYTHON.language) { + return false; + } + + const workspaceFolder = this.workspace.getWorkspaceFolder(document.uri); + const workspaceRootPath = (workspaceFolder && typeof workspaceFolder.uri.fsPath === 'string') ? workspaceFolder.uri.fsPath : undefined; + const relativeFileName = typeof workspaceRootPath === 'string' ? path.relative(workspaceRootPath, document.fileName) : document.fileName; + + const settings = this.configurationService.getSettings(document.uri); + const ignoreMinmatches = settings.linting.ignorePatterns.map(pattern => new Minimatch(pattern)); + if (ignoreMinmatches.some(matcher => matcher.match(document.fileName) || matcher.match(relativeFileName))) { + return false; + } + if (document.uri.scheme !== 'file' || !document.uri.fsPath) { + return false; + } + return await this.fileSystem.fileExistsAsync(document.uri.fsPath); + } } diff --git a/src/client/linters/types.ts b/src/client/linters/types.ts index 21a63419ab39..bbd89558fa47 100644 --- a/src/client/linters/types.ts +++ b/src/client/linters/types.ts @@ -61,8 +61,10 @@ export enum LintMessageSeverity { export const ILintingEngine = Symbol('ILintingEngine'); export interface ILintingEngine { - lintOpenPythonFiles(): void; + readonly diagnostics: vscode.DiagnosticCollection; + lintOpenPythonFiles(): Promise; lintDocument(document: vscode.TextDocument, trigger: LinterTrigger): Promise; // tslint:disable-next-line:no-any linkJupiterExtension(jupiter: vscode.Extension | undefined): Promise; + clearDiagnostics(document: vscode.TextDocument): void; } diff --git a/src/client/providers/linterProvider.ts b/src/client/providers/linterProvider.ts index fd44b9b457c8..fb66aab3971b 100644 --- a/src/client/providers/linterProvider.ts +++ b/src/client/providers/linterProvider.ts @@ -6,16 +6,14 @@ import * as vscode from 'vscode'; import { ConfigurationTarget, Uri, workspace } from 'vscode'; import { IDocumentManager } from '../common/application/types'; import { ConfigSettingMonitor } from '../common/configSettingMonitor'; +import { isTestExecution } from '../common/constants'; import { IFileSystem } from '../common/platform/types'; import { IConfigurationService } from '../common/types'; import { IInterpreterService } from '../interpreter/contracts'; import { IServiceContainer } from '../ioc/types'; import { ILinterManager, ILintingEngine } from '../linters/types'; -const uriSchemesToIgnore = ['git', 'showModifications', 'svn']; - export class LinterProvider implements vscode.Disposable { - private diagnosticCollection: vscode.DiagnosticCollection; private context: vscode.ExtensionContext; private disposables: vscode.Disposable[]; private configMonitor: ConfigSettingMonitor; @@ -37,7 +35,6 @@ export class LinterProvider implements vscode.Disposable { this.documents = serviceContainer.get(IDocumentManager); this.configuration = serviceContainer.get(IConfigurationService); - this.diagnosticCollection = vscode.languages.createDiagnosticCollection('python'); this.disposables.push(this.interpreterService.onDidChangeInterpreter(() => this.engine.lintOpenPythonFiles())); this.documents.onDidOpenTextDocument(e => this.onDocumentOpened(e), this.context.subscriptions); @@ -46,10 +43,12 @@ export class LinterProvider implements vscode.Disposable { this.configMonitor = new ConfigSettingMonitor('linting'); this.configMonitor.on('change', this.lintSettingsChangedHandler.bind(this)); - } - public get diagnostics(): vscode.DiagnosticCollection { - return this.diagnosticCollection; + // On workspace reopen we don't get `onDocumentOpened` since it is first opened + // and then the extension is activated. So schedule linting pass now. + if (!isTestExecution()) { + setTimeout(() => this.engine.lintOpenPythonFiles().ignoreErrors(), 1200); + } } public dispose() { @@ -63,35 +62,23 @@ export class LinterProvider implements vscode.Disposable { private lintSettingsChangedHandler(configTarget: ConfigurationTarget, wkspaceOrFolder: Uri) { if (configTarget === ConfigurationTarget.Workspace) { - this.engine.lintOpenPythonFiles(); + this.engine.lintOpenPythonFiles().ignoreErrors(); return; } // Look for python files that belong to the specified workspace folder. workspace.textDocuments.forEach(async document => { const wkspaceFolder = workspace.getWorkspaceFolder(document.uri); if (wkspaceFolder && wkspaceFolder.uri.fsPath === wkspaceOrFolder.fsPath) { - await this.engine.lintDocument(document, 'auto'); + this.engine.lintDocument(document, 'auto').ignoreErrors(); } }); } - private async onDocumentOpened(document: vscode.TextDocument): Promise { - const settings = this.configuration.getSettings(document.uri); - if (document.languageId !== 'python' || !settings.linting.enabled) { - return; - } - // Exclude files opened by vscode when showing a diff view. - if (uriSchemesToIgnore.indexOf(document.uri.scheme) >= 0) { - return; - } - if (!document.uri.path || - (path.basename(document.uri.path) === document.uri.path && !await this.fs.fileExistsAsync(document.uri.path))) { - return; - } + private onDocumentOpened(document: vscode.TextDocument): void { this.engine.lintDocument(document, 'auto').ignoreErrors(); } - private onDocumentSaved(document: vscode.TextDocument) { + private onDocumentSaved(document: vscode.TextDocument): void { const settings = this.configuration.getSettings(document.uri); if (document.languageId === 'python' && settings.linting.enabled && settings.linting.lintOnSave) { this.engine.lintDocument(document, 'save').ignoreErrors(); @@ -111,8 +98,8 @@ export class LinterProvider implements vscode.Disposable { return; } // Check if this document is still open as a duplicate editor. - if (!this.isDocumentOpen(document.uri) && this.diagnosticCollection.has(document.uri)) { - this.diagnosticCollection.set(document.uri, []); + if (!this.isDocumentOpen(document.uri)) { + this.engine.clearDiagnostics(document); } } } diff --git a/src/test/interpreters/venv.test.ts b/src/test/interpreters/venv.test.ts index 7c26035e1096..13f064bcbc4a 100644 --- a/src/test/interpreters/venv.test.ts +++ b/src/test/interpreters/venv.test.ts @@ -49,7 +49,7 @@ suite('Virtual environments', () => { let paths = pathProvider.getSearchPaths(); let expected = folders.map(item => path.join(homedir, item)); - expected.push(path.join('.pyenv', 'versions')); + expected.push(path.join(homedir, '.pyenv', 'versions')); expect(paths).to.deep.equal(expected, 'Global search folder list is incorrect.'); diff --git a/src/test/linters/lint.provider.test.ts b/src/test/linters/lint.provider.test.ts index 53b0b56cedfa..023ee86223be 100644 --- a/src/test/linters/lint.provider.test.ts +++ b/src/test/linters/lint.provider.test.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { expect } from 'chai'; import { Container } from 'inversify'; import * as TypeMoq from 'typemoq'; import * as vscode from 'vscode'; @@ -150,14 +149,11 @@ suite('Linting - Provider', () => { document.setup(x => x.isClosed).returns(() => closed); docManager.setup(x => x.textDocuments).returns(() => closed ? [] : [document.object]); - + // tslint:disable-next-line:prefer-const no-unused-variable const provider = new LinterProvider(context.object, serviceContainer); - const diags: vscode.Diagnostic[] = []; - diags.push(new vscode.Diagnostic(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 1)), 'error')); - provider.diagnostics.set(uri, diags); emitter.fire(document.object); - const d = provider.diagnostics.get(uri); - expect(d).to.be.lengthOf(closed ? 0 : 1, 'Diagnostic collection not of expected length after file close.'); + const timesExpected = closed ? TypeMoq.Times.once() : TypeMoq.Times.never(); + engine.verify(x => x.clearDiagnostics(TypeMoq.It.isAny()), timesExpected); } }); diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index 906bdb2eef96..65b160da49a9 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -3,6 +3,7 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import { Uri } from 'vscode'; import * as vscode from 'vscode'; +import { ICommandManager } from '../../client/common/application/types'; import { STANDARD_OUTPUT_CHANNEL } from '../../client/common/constants'; import { Product } from '../../client/common/installer/productInstaller'; import { IConfigurationService, IOutputChannel } from '../../client/common/types'; @@ -250,4 +251,26 @@ suite('Linting', () => { await configService.updateSettingAsync('linting.pylintUseMinimalCheckers', false, workspaceUri); await testEnablingDisablingOfLinter(Product.pylint, true, file); }); + // tslint:disable-next-line:no-function-expression + test('Multiple linters', async function () { + // tslint:disable-next-line:no-invalid-this + this.timeout(40000); + + await closeActiveWindows(); + const document = await vscode.workspace.openTextDocument(path.join(pythoFilesPath, 'print.py')); + await vscode.window.showTextDocument(document); + await configService.updateSettingAsync('linting.enabled', true, workspaceUri); + await configService.updateSettingAsync('linting.pylintUseMinimalCheckers', false, workspaceUri); + await configService.updateSettingAsync('linting.pylintEnabled', true, workspaceUri); + await configService.updateSettingAsync('linting.flake8Enabled', true, workspaceUri); + + const commands = ioc.serviceContainer.get(ICommandManager); + const collection = await commands.executeCommand('python.runLinting') as vscode.DiagnosticCollection; + assert.notEqual(collection, undefined, 'python.runLinting did not return valid diagnostics collection.'); + + const messages = collection!.get(document.uri); + assert.notEqual(messages!.length, 0, 'No diagnostic messages.'); + assert.notEqual(messages!.filter(x => x.source === 'pylint').length, 0, 'No pylint messages.'); + assert.notEqual(messages!.filter(x => x.source === 'flake8').length, 0, 'No flake8 messages.'); + }); }); diff --git a/src/test/linters/lintengine.test.ts b/src/test/linters/lintengine.test.ts index 50b24f07700f..44520df46cdc 100644 --- a/src/test/linters/lintengine.test.ts +++ b/src/test/linters/lintengine.test.ts @@ -6,6 +6,7 @@ import { OutputChannel, TextDocument, Uri } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../../client/common/application/types'; import { PythonLanguage, STANDARD_OUTPUT_CHANNEL } from '../../client/common/constants'; import '../../client/common/extensions'; +import { IFileSystem } from '../../client/common/platform/types'; import { IConfigurationService, ILintingSettings, IOutputChannel, IPythonSettings } from '../../client/common/types'; import { IServiceContainer } from '../../client/ioc/types'; import { LintingEngine } from '../../client/linters/lintingEngine'; @@ -14,12 +15,16 @@ import { initialize } from '../initialize'; // tslint:disable-next-line:max-func-body-length suite('Linting - LintingEngine', () => { - let lintingEnging: ILintingEngine; - let document: TextDocument; + let serviceContainer: TypeMoq.IMock; let lintManager: TypeMoq.IMock; + let settings: TypeMoq.IMock; + let lintSettings: TypeMoq.IMock; + let fileSystem: TypeMoq.IMock; + let lintingEngine: ILintingEngine; + suiteSetup(initialize); setup(async () => { - const serviceContainer = TypeMoq.Mock.ofType(); + serviceContainer = TypeMoq.Mock.ofType(); const docManager = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDocumentManager), TypeMoq.It.isAny())).returns(() => docManager.object); @@ -27,40 +32,93 @@ suite('Linting - LintingEngine', () => { const workspaceService = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IWorkspaceService), TypeMoq.It.isAny())).returns(() => workspaceService.object); - const lintSettings = TypeMoq.Mock.ofType(); - lintSettings.setup(l => l.ignorePatterns).returns(() => []); - const settings = TypeMoq.Mock.ofType(); - settings.setup(x => x.linting).returns(() => lintSettings.object); + fileSystem = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem), TypeMoq.It.isAny())).returns(() => fileSystem.object); + + lintSettings = TypeMoq.Mock.ofType(); + settings = TypeMoq.Mock.ofType(); + const configService = TypeMoq.Mock.ofType(); configService.setup(x => x.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); + configService.setup(x => x.isTestExecution()).returns(() => true); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())).returns(() => configService.object); const outputChannel = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IOutputChannel), TypeMoq.It.isValue(STANDARD_OUTPUT_CHANNEL))).returns(() => outputChannel.object); lintManager = TypeMoq.Mock.ofType(); + lintManager.setup(x => x.isLintingEnabled(TypeMoq.It.isAny())).returns(() => true); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ILinterManager), TypeMoq.It.isAny())).returns(() => lintManager.object); - const mockDocument = TypeMoq.Mock.ofType(); - mockDocument.setup(d => d.uri).returns(() => Uri.file('a.py')); - mockDocument.setup(d => d.languageId).returns(() => PythonLanguage.language); - document = mockDocument.object; - - lintingEnging = new LintingEngine(serviceContainer.object); + lintingEngine = new LintingEngine(serviceContainer.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ILintingEngine), TypeMoq.It.isAny())).returns(() => lintingEngine); }); test('Ensure document.uri is passed into isLintingEnabled', () => { + const doc = mockTextDocument('a.py', PythonLanguage.language, true); try { - lintingEnging.lintDocument(document, 'auto').ignoreErrors(); + lintingEngine.lintDocument(doc, 'auto').ignoreErrors(); } catch { - lintManager.verify(l => l.isLintingEnabled(TypeMoq.It.isValue(document.uri)), TypeMoq.Times.once()); + lintManager.verify(l => l.isLintingEnabled(TypeMoq.It.isValue(doc.uri)), TypeMoq.Times.once()); } }); test('Ensure document.uri is passed into createLinter', () => { + const doc = mockTextDocument('a.py', PythonLanguage.language, true); try { - lintingEnging.lintDocument(document, 'auto').ignoreErrors(); + lintingEngine.lintDocument(doc, 'auto').ignoreErrors(); } catch { - lintManager.verify(l => l.createLinter(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isValue(document.uri)), TypeMoq.Times.atLeastOnce()); + lintManager.verify(l => l.createLinter(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isValue(doc.uri)), TypeMoq.Times.atLeastOnce()); } }); + + test('Verify files that match ignore pattern are not linted', async () => { + const doc = mockTextDocument('a1.py', PythonLanguage.language, true, ['a*.py']); + await lintingEngine.lintDocument(doc, 'auto'); + lintManager.verify(l => l.createLinter(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never()); + }); + + test('Ensure non-Python files are not linted', async () => { + const doc = mockTextDocument('a.ts', 'typescript', true); + await lintingEngine.lintDocument(doc, 'auto'); + lintManager.verify(l => l.createLinter(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never()); + }); + + test('Ensure files with git scheme are not linted', async () => { + const doc = mockTextDocument('a1.py', PythonLanguage.language, false, [], 'git'); + await lintingEngine.lintDocument(doc, 'auto'); + lintManager.verify(l => l.createLinter(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never()); + }); + test('Ensure files with showModifications scheme are not linted', async () => { + const doc = mockTextDocument('a1.py', PythonLanguage.language, false, [], 'showModifications'); + await lintingEngine.lintDocument(doc, 'auto'); + lintManager.verify(l => l.createLinter(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never()); + }); + test('Ensure files with svn scheme are not linted', async () => { + const doc = mockTextDocument('a1.py', PythonLanguage.language, false, [], 'svn'); + await lintingEngine.lintDocument(doc, 'auto'); + lintManager.verify(l => l.createLinter(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never()); + }); + + test('Ensure non-existing files are not linted', async () => { + const doc = mockTextDocument('file.py', PythonLanguage.language, false, []); + await lintingEngine.lintDocument(doc, 'auto'); + lintManager.verify(l => l.createLinter(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never()); + }); + + function mockTextDocument(fileName: string, language: string, exists: boolean, ignorePattern: string[] = [], scheme?: string): TextDocument { + fileSystem.setup(x => x.fileExistsAsync(TypeMoq.It.isAnyString())).returns(() => Promise.resolve(exists)); + + lintSettings.setup(l => l.ignorePatterns).returns(() => ignorePattern); + settings.setup(x => x.linting).returns(() => lintSettings.object); + + const doc = TypeMoq.Mock.ofType(); + if (scheme) { + doc.setup(d => d.uri).returns(() => Uri.parse(`${scheme}:${fileName}`)); + } else { + doc.setup(d => d.uri).returns(() => Uri.file(fileName)); + } + doc.setup(d => d.fileName).returns(() => fileName); + doc.setup(d => d.languageId).returns(() => language); + return doc.object; + } }); diff --git a/src/test/pythonFiles/linting/print.py b/src/test/pythonFiles/linting/print.py new file mode 100644 index 000000000000..fca61311fc84 --- /dev/null +++ b/src/test/pythonFiles/linting/print.py @@ -0,0 +1 @@ +print x \ No newline at end of file