Skip to content

Commit 1180941

Browse files
authored
Allow for negative column numbers in messages returned by pylint (microsoft#1629)
1 parent b96eb9a commit 1180941

5 files changed

Lines changed: 53 additions & 10 deletions

File tree

‎news/2 Fixes/1628.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow for negative column numbers in messages returned by `pylint`.

‎src/client/common/configSettings.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings {
3232
public venvPath = '';
3333
public venvFolders: string[] = [];
3434
public devOptions: string[] = [];
35-
public linting?: ILintingSettings;
35+
public linting!: ILintingSettings;
3636
public formatting!: IFormattingSettings;
3737
public autoComplete?: IAutoCompleteSettings;
3838
public unitTest!: IUnitTestSettings;

‎src/client/common/types.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export interface IPythonSettings {
103103
readonly jediPath: string;
104104
readonly jediMemoryLimit: number;
105105
readonly devOptions: string[];
106-
readonly linting?: ILintingSettings;
106+
readonly linting: ILintingSettings;
107107
readonly formatting: IFormattingSettings;
108108
readonly unitTest: IUnitTestSettings;
109109
readonly autoComplete?: IAutoCompleteSettings;

‎src/client/linters/baseLinter.ts‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@ import * as vscode from 'vscode';
33
import { IWorkspaceService } from '../common/application/types';
44
import '../common/extensions';
55
import { IPythonToolExecutionService } from '../common/process/types';
6-
import { IConfigurationService, IPythonSettings } from '../common/types';
7-
import { ExecutionInfo, ILogger, Product } from '../common/types';
6+
import { ExecutionInfo, IConfigurationService, ILogger, IPythonSettings, Product } from '../common/types';
87
import { IServiceContainer } from '../ioc/types';
98
import { ErrorHandler } from './errorHandlers/errorHandler';
109
import { ILinter, ILinterInfo, ILinterManager, ILintMessage, LintMessageSeverity } from './types';
1110

1211
// tslint:disable-next-line:no-require-imports no-var-requires
1312
const namedRegexp = require('named-js-regexp');
14-
15-
const REGEX = '(?<line>\\d+),(?<column>\\d+),(?<type>\\w+),(?<code>\\w\\d+):(?<message>.*)\\r?(\\n|$)';
13+
// Allow negative column numbers (https://github.com/PyCQA/pylint/issues/1822)
14+
const REGEX = '(?<line>\\d+),(?<column>-?\\d+),(?<type>\\w+),(?<code>\\w\\d+):(?<message>.*)\\r?(\\n|$)';
1615

1716
export interface IRegexGroup {
1817
line: number;
@@ -36,7 +35,7 @@ export abstract class BaseLinter implements ILinter {
3635
protected readonly configService: IConfigurationService;
3736

3837
private errorHandler: ErrorHandler;
39-
private _pythonSettings: IPythonSettings;
38+
private _pythonSettings!: IPythonSettings;
4039
private _info: ILinterInfo;
4140
private workspace: IWorkspaceService;
4241

@@ -142,7 +141,7 @@ export abstract class BaseLinter implements ILinter {
142141
return {
143142
code: match.code,
144143
message: match.message,
145-
column: isNaN(match.column) || match.column === 0 ? 0 : match.column - this.columnOffset,
144+
column: isNaN(match.column) || match.column <= 0 ? 0 : match.column - this.columnOffset,
146145
line: match.line,
147146
type: match.type,
148147
provider: this.info.id

‎src/test/linters/pylint.test.ts‎

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Container } from 'inversify';
66
import * as os from 'os';
77
import * as path from 'path';
88
import * as TypeMoq from 'typemoq';
9-
import { CancellationTokenSource, OutputChannel, TextDocument, Uri, WorkspaceFolder } from 'vscode';
9+
import { CancellationTokenSource, DiagnosticSeverity, OutputChannel, TextDocument, Uri, WorkspaceFolder } from 'vscode';
1010
import { IWorkspaceService } from '../../client/common/application/types';
1111
import { IFileSystem, IPlatformService } from '../../client/common/platform/types';
1212
import { IPythonToolExecutionService } from '../../client/common/process/types';
@@ -19,7 +19,7 @@ import { ILinterManager } from '../../client/linters/types';
1919
import { MockLintingSettings } from '../mockClasses';
2020

2121
// tslint:disable-next-line:max-func-body-length
22-
suite('Linting - Pylintrc search', () => {
22+
suite('Linting - Pylint', () => {
2323
const basePath = '/user/a/b/c/d';
2424
const pylintrc = 'pylintrc';
2525
const dotPylintrc = '.pylintrc';
@@ -199,4 +199,47 @@ suite('Linting - Pylintrc search', () => {
199199
expect(execInfo!.args.findIndex(x => x.indexOf('--disable=all') >= 0),
200200
'Minimal args passed to pylint while pylintrc exists.').to.be.eq(expectedMinArgs ? 0 : -1);
201201
}
202+
test('Negative column numbers should be treated 0', async () => {
203+
const fileFolder = '/user/a/b/c';
204+
const outputChannel = TypeMoq.Mock.ofType<OutputChannel>();
205+
const pylinter = new Pylint(outputChannel.object, serviceContainer);
206+
207+
const document = TypeMoq.Mock.ofType<TextDocument>();
208+
document.setup(x => x.uri).returns(() => Uri.file(path.join(fileFolder, 'test.py')));
209+
210+
const wsf = TypeMoq.Mock.ofType<WorkspaceFolder>();
211+
wsf.setup(x => x.uri).returns(() => Uri.file(fileFolder));
212+
213+
workspace.setup(x => x.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => wsf.object);
214+
215+
const linterOutput = ['No config file found, using default configuration',
216+
'************* Module test',
217+
'1,1,convention,C0111:Missing module docstring',
218+
'3,-1,error,E1305:Too many arguments for format string'].join(os.EOL);
219+
execService
220+
.setup(x => x.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
221+
.returns(() => Promise.resolve({ stdout: linterOutput, stderr: '' }));
222+
223+
const lintSettings = new MockLintingSettings();
224+
lintSettings.pylintUseMinimalCheckers = false;
225+
lintSettings.maxNumberOfProblems = 1000;
226+
lintSettings.pylintPath = 'pyLint';
227+
lintSettings.pylintEnabled = true;
228+
lintSettings.pylintCategorySeverity = {
229+
convention: DiagnosticSeverity.Hint,
230+
error: DiagnosticSeverity.Error,
231+
fatal: DiagnosticSeverity.Error,
232+
refactor: DiagnosticSeverity.Hint,
233+
warning: DiagnosticSeverity.Warning
234+
};
235+
236+
const settings = TypeMoq.Mock.ofType<IPythonSettings>();
237+
settings.setup(x => x.linting).returns(() => lintSettings);
238+
config.setup(x => x.getSettings(TypeMoq.It.isAny())).returns(() => settings.object);
239+
240+
const messages = await pylinter.lint(document.object, new CancellationTokenSource().token);
241+
expect(messages).to.be.lengthOf(2);
242+
expect(messages[0].column).to.be.equal(1);
243+
expect(messages[1].column).to.be.equal(0);
244+
});
202245
});

0 commit comments

Comments
 (0)