Skip to content

Commit 1b6fbfb

Browse files
yxliang01karthiknadig
authored andcommitted
fix(client/linters/mypy): call mypy incorrectly (#5834)
* fix(client/linters/mypy): call mypy incorrectly It's supposed to call `mypy` in the root package directory with the relative path to the python file being checked. This commit enforces this behavior. * fix(client/linters/mypy): syntax error * Fixes and tests * Add news item. * House keeping on tests
1 parent 3d5ce5a commit 1b6fbfb

File tree

3 files changed

+89
-7
lines changed

3 files changed

+89
-7
lines changed

news/2 Fixes/5326.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Use relative paths when invoking mypy.
2+
(thanks to [yxliang01](https://github.com/yxliang01))

src/client/linters/mypy.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as path from 'path';
12
import { CancellationToken, OutputChannel, TextDocument } from 'vscode';
23
import '../common/extensions';
34
import { Product } from '../common/types';
@@ -13,7 +14,9 @@ export class MyPy extends BaseLinter {
1314
}
1415

1516
protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise<ILintMessage[]> {
16-
const messages = await this.run([document.uri.fsPath], document, cancellation, REGEX);
17+
const cwd = this.getWorkspaceRootPath(document);
18+
const relativePath = path.relative(cwd, document.uri.fsPath);
19+
const messages = await this.run([relativePath], document, cancellation, REGEX);
1720
messages.forEach(msg => {
1821
msg.severity = this.parseMessagesSeverity(msg.type, this.pythonSettings.linting.mypyCategorySeverity);
1922
msg.code = msg.type;

src/test/linters/mypy.unit.test.ts

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,20 @@
66
// tslint:disable:no-object-literal-type-assertion
77

88
import { expect } from 'chai';
9+
import * as path from 'path';
10+
import * as sinon from 'sinon';
11+
import { anything, instance, mock, when } from 'ts-mockito';
12+
import { CancellationToken, CancellationTokenSource, TextDocument, Uri } from 'vscode';
13+
import { Product } from '../../client/common/types';
14+
import { ServiceContainer } from '../../client/ioc/container';
915
import { parseLine } from '../../client/linters/baseLinter';
10-
import { REGEX } from '../../client/linters/mypy';
11-
import { ILintMessage } from '../../client/linters/types';
16+
import { LinterManager } from '../../client/linters/linterManager';
17+
import { MyPy, REGEX } from '../../client/linters/mypy';
18+
import { ILinterManager, ILintMessage, LintMessageSeverity } from '../../client/linters/types';
19+
import { MockOutputChannel } from '../mockClasses';
1220

1321
// This following is a real-world example. See gh=2380.
14-
// tslint:disable-next-line:no-multiline-string
22+
// tslint:disable:no-multiline-string no-any max-func-body-length
1523
const output = `
1624
provider.pyi:10: error: Incompatible types in assignment (expression has type "str", variable has type "int")
1725
provider.pyi:11: error: Name 'not_declared_var' is not defined
@@ -29,23 +37,23 @@ suite('Linting - MyPy', () => {
2937
line: 10,
3038
type: 'error',
3139
provider: 'mypy'
32-
} as ILintMessage],
40+
} as ILintMessage],
3341
[lines[2], {
3442
code: undefined,
3543
message: 'Name \'not_declared_var\' is not defined',
3644
column: 0,
3745
line: 11,
3846
type: 'error',
3947
provider: 'mypy'
40-
} as ILintMessage],
48+
} as ILintMessage],
4149
[lines[3], {
4250
code: undefined,
4351
message: 'Expression has type "Any"',
4452
column: 21,
4553
line: 12,
4654
type: 'error',
4755
provider: 'mypy'
48-
} as ILintMessage]
56+
} as ILintMessage]
4957
];
5058
for (const [line, expected] of tests) {
5159
const msg = parseLine(line, REGEX, 'mypy');
@@ -54,3 +62,72 @@ suite('Linting - MyPy', () => {
5462
}
5563
});
5664
});
65+
66+
suite('Test Linter', () => {
67+
class TestMyPyLinter extends MyPy {
68+
// tslint:disable: no-unnecessary-override
69+
public async runLinter(document: TextDocument, cancellation: CancellationToken): Promise<ILintMessage[]> {
70+
return super.runLinter(document, cancellation);
71+
}
72+
public getWorkspaceRootPath(document: TextDocument): string {
73+
return super.getWorkspaceRootPath(document);
74+
}
75+
public async run(args: string[], document: TextDocument, cancellation: CancellationToken, regEx: string = REGEX): Promise<ILintMessage[]> {
76+
return super.run(args, document, cancellation, regEx);
77+
}
78+
public parseMessagesSeverity(error: string, severity: any): LintMessageSeverity {
79+
return super.parseMessagesSeverity(error, severity);
80+
}
81+
}
82+
83+
let linter: TestMyPyLinter;
84+
let getWorkspaceRootPathStub: sinon.SinonStub<[TextDocument], string>;
85+
let runStub: sinon.SinonStub<[string[], TextDocument, CancellationToken, (string | undefined)?], Promise<ILintMessage[]>>;
86+
const token = new CancellationTokenSource().token;
87+
teardown(() => sinon.restore());
88+
setup(() => {
89+
const linterManager = mock(LinterManager);
90+
when(linterManager.getLinterInfo(anything())).thenReturn({ product: Product.mypy } as any);
91+
const serviceContainer = mock(ServiceContainer);
92+
when(serviceContainer.get<ILinterManager>(ILinterManager)).thenReturn(instance(linterManager));
93+
getWorkspaceRootPathStub = sinon.stub(TestMyPyLinter.prototype, 'getWorkspaceRootPath');
94+
runStub = sinon.stub(TestMyPyLinter.prototype, 'run');
95+
linter = new TestMyPyLinter(instance(mock(MockOutputChannel)), instance(serviceContainer));
96+
});
97+
98+
test('Get cwd based on document', async () => {
99+
const fileUri = Uri.file(path.join('a', 'b', 'c', 'd', 'e', 'filename.py'));
100+
const cwd = path.join('a', 'b', 'c');
101+
const doc = { uri: fileUri } as any as TextDocument;
102+
getWorkspaceRootPathStub.callsFake(() => cwd);
103+
runStub.callsFake(() => Promise.resolve([]));
104+
105+
await linter.runLinter(doc, token);
106+
107+
expect(getWorkspaceRootPathStub.callCount).to.equal(1);
108+
expect(getWorkspaceRootPathStub.args[0]).to.deep.equal([doc]);
109+
});
110+
test('Pass relative path of document to linter', async () => {
111+
const fileUri = Uri.file(path.join('a', 'b', 'c', 'd', 'e', 'filename.py'));
112+
const cwd = path.join('a', 'b', 'c');
113+
const doc = { uri: fileUri } as any as TextDocument;
114+
getWorkspaceRootPathStub.callsFake(() => cwd);
115+
runStub.callsFake(() => Promise.resolve([]));
116+
117+
await linter.runLinter(doc, token);
118+
119+
expect(runStub.callCount).to.equal(1);
120+
expect(runStub.args[0]).to.deep.equal([[path.relative(cwd, fileUri.fsPath)], doc, token, REGEX]);
121+
});
122+
test('Return empty messages', async () => {
123+
const fileUri = Uri.file(path.join('a', 'b', 'c', 'd', 'e', 'filename.py'));
124+
const cwd = path.join('a', 'b', 'c');
125+
const doc = { uri: fileUri } as any as TextDocument;
126+
getWorkspaceRootPathStub.callsFake(() => cwd);
127+
runStub.callsFake(() => Promise.resolve([]));
128+
129+
const messages = await linter.runLinter(doc, token);
130+
131+
expect(messages).to.be.deep.equal([]);
132+
});
133+
});

0 commit comments

Comments
 (0)