diff --git a/news/2 Fixes/10190.md b/news/2 Fixes/10190.md new file mode 100644 index 000000000000..c5b2bb7306ed --- /dev/null +++ b/news/2 Fixes/10190.md @@ -0,0 +1,2 @@ +Prevent mypy errors for other files showing in current file. +(thanks [Steve Dignam](https://github.com/sbdchd)) diff --git a/src/client/linters/mypy.ts b/src/client/linters/mypy.ts index b1da30513b1d..f106c8b36261 100644 --- a/src/client/linters/mypy.ts +++ b/src/client/linters/mypy.ts @@ -1,11 +1,14 @@ import { CancellationToken, OutputChannel, TextDocument } from 'vscode'; import '../common/extensions'; +import { escapeRegExp } from 'lodash'; import { Product } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { BaseLinter } from './baseLinter'; import { ILintMessage } from './types'; -export const REGEX = '(?[^:]+):(?\\d+)(:(?\\d+))?: (?\\w+): (?.*)\\r?(\\n|$)'; +export function getRegex(filepath: string): string { + return `${escapeRegExp(filepath)}:(?\\d+)(:(?\\d+))?: (?\\w+): (?.*)\\r?(\\n|$)`; +} const COLUMN_OFF_SET = 1; export class MyPy extends BaseLinter { @@ -14,7 +17,9 @@ export class MyPy extends BaseLinter { } protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - const messages = await this.run([document.uri.fsPath], document, cancellation, REGEX); + const relativeFilePath = document.uri.fsPath.slice(this.getWorkspaceRootPath(document).length + 1); + const regex = getRegex(relativeFilePath); + const messages = await this.run([document.uri.fsPath], document, cancellation, regex); messages.forEach((msg) => { msg.severity = this.parseMessagesSeverity(msg.type, this.pythonSettings.linting.mypyCategorySeverity); msg.code = msg.type; diff --git a/src/test/linters/mypy.unit.test.ts b/src/test/linters/mypy.unit.test.ts index 63bd5c8c34b8..b697a719a475 100644 --- a/src/test/linters/mypy.unit.test.ts +++ b/src/test/linters/mypy.unit.test.ts @@ -5,7 +5,7 @@ import { expect } from 'chai'; import { parseLine } from '../../client/linters/baseLinter'; -import { REGEX } from '../../client/linters/mypy'; +import { getRegex } from '../../client/linters/mypy'; import { ILintMessage, LinterId } from '../../client/linters/types'; // This following is a real-world example. See gh=2380. @@ -55,9 +55,45 @@ suite('Linting - MyPy', () => { ], ]; for (const [line, expected] of tests) { - const msg = parseLine(line, REGEX, LinterId.MyPy, 1); + const msg = parseLine(line, getRegex('provider.pyi'), LinterId.MyPy, 1); expect(msg).to.deep.equal(expected); } }); + test('regex excludes unexpected files', () => { + // mypy run against `foo/bar.py` returning errors for foo/__init__.py + const outputWithUnexpectedFile = `\ +foo/__init__.py:4:5: error: Statement is unreachable [unreachable] +foo/bar.py:2:14: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment] +Found 2 errors in 2 files (checked 1 source file) +`; + + const lines = outputWithUnexpectedFile.split('\n'); + const tests: [string, ILintMessage | undefined][] = [ + [lines[0], undefined], + [ + lines[1], + { + code: undefined, + message: + 'Incompatible types in assignment (expression has type "str", variable has type "int") [assignment]', + column: 13, + line: 2, + type: 'error', + provider: 'mypy', + }, + ], + [lines[2], undefined], + ]; + for (const [line, expected] of tests) { + const msg = parseLine(line, getRegex('foo/bar.py'), LinterId.MyPy, 1); + + expect(msg).to.deep.equal(expected); + } + }); + test('getRegex escapes filename correctly', () => { + expect(getRegex('foo/bar.py')).to.eql( + String.raw`foo/bar\.py:(?\d+)(:(?\d+))?: (?\w+): (?.*)\r?(\n|$)`, + ); + }); });