Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0bd4c38

Browse files
committed
fixed issues in linting using pydocstyle and flake8 #148, #178
1 parent aafdf06 commit 0bd4c38

File tree

5 files changed

+89
-59
lines changed

5 files changed

+89
-59
lines changed

src/client/common/utils.ts

+22-34
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,15 @@ export function execPythonFile(file: string, args: string[], cwd: string, includ
9191
if (PathValidity.get(fullyQualifiedFile)) {
9292
tryUsingCommandArg = false;
9393
}
94-
return execFileInternal(fullyQualifiedFile, args, cwd, includeErrorAsResponse, tryUsingCommandArg);
94+
return execFileInternal(fullyQualifiedFile, args, cwd, includeErrorAsResponse);
9595
}
9696

9797
return validatePath(fullyQualifiedFile).then(f => {
9898
// If the file exists, then don't bother trying again
9999
if (f.length > 0) {
100100
tryUsingCommandArg = false;
101101
}
102-
return execFileInternal(fullyQualifiedFile, args, cwd, includeErrorAsResponse, true);
102+
return execFileInternal(fullyQualifiedFile, args, cwd, includeErrorAsResponse);
103103
});
104104
});
105105

@@ -114,43 +114,31 @@ export function execPythonFile(file: string, args: string[], cwd: string, includ
114114
});
115115
}
116116

117-
function handleResponse(error: Error, stdout: string, stderr: string, file: string, includeErrorAsResponse: boolean, rejectIfENOENTErrors: boolean = false): Promise<string> {
117+
function execFileInternal(file: string, args: string[], cwd: string, includeErrorAsResponse: boolean): Promise<string> {
118118
return new Promise<string>((resolve, reject) => {
119-
if (error && ((<any>error).code === "ENOENT") || (<any>error).code === 127) {
120-
if (!IN_VALID_FILE_PATHS.has(file)) {
121-
IN_VALID_FILE_PATHS.set(file, true);
119+
child_process.execFile(file, args, { cwd: cwd }, (error, stdout, stderr) => {
120+
if (error && ((<any>error).code === "ENOENT") || (<any>error).code === 127) {
121+
if (!IN_VALID_FILE_PATHS.has(file)) {
122+
IN_VALID_FILE_PATHS.set(file, true);
123+
}
124+
return reject(error);
122125
}
123-
return reject(error);
124-
}
125126

126-
// pylint:
127-
// In the case of pylint we have some messages (such as config file not found and using default etc...) being returned in stderr
128-
// These error messages are useless when using pylint
129-
if (includeErrorAsResponse && (stdout.length > 0 || stderr.length > 0)) {
130-
return resolve(stdout + "\n" + stderr);
131-
}
127+
// pylint:
128+
// In the case of pylint we have some messages (such as config file not found and using default etc...) being returned in stderr
129+
// These error messages are useless when using pylint
130+
if (includeErrorAsResponse && (stdout.length > 0 || stderr.length > 0)) {
131+
return resolve(stdout + "\n" + stderr);
132+
}
132133

133-
let hasErrors = (error && error.message.length > 0) || (stderr && stderr.length > 0);
134-
if (hasErrors && (typeof stdout !== "string" || stdout.length === 0)) {
135-
let errorMsg = (error && error.message) ? error.message : (stderr && stderr.length > 0 ? stderr + "" : "");
136-
return reject(errorMsg);
137-
}
134+
let hasErrors = (error && error.message.length > 0) || (stderr && stderr.length > 0);
135+
if (hasErrors && (typeof stdout !== "string" || stdout.length === 0)) {
136+
let errorMsg = (error && error.message) ? error.message : (stderr && stderr.length > 0 ? stderr + "" : "");
137+
return reject(errorMsg);
138+
}
138139

139-
resolve(stdout + "");
140-
});
141-
}
142-
function execFileInternal(file: string, args: string[], cwd: string, includeErrorAsResponse: boolean, useExectFile: boolean = false): Promise<string> {
143-
return new Promise<string>((resolve, reject) => {
144-
if (useExectFile) {
145-
child_process.execFile(file, args, { cwd: cwd }, (error, stdout, stderr) => {
146-
handleResponse(error, stdout, stderr, file, includeErrorAsResponse, useExectFile).then(resolve, reject);
147-
});
148-
}
149-
else {
150-
child_process.exec(file + " " + args.join(" "), { cwd: cwd }, (error, stdout, stderr) => {
151-
handleResponse(error, stdout, stderr, file, includeErrorAsResponse, useExectFile).then(resolve, reject);
152-
});
153-
}
140+
resolve(stdout + "");
141+
});
154142
});
155143
}
156144

src/client/linters/flake8.ts

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import * as path from 'path';
44
import * as baseLinter from './baseLinter';
55
import {OutputChannel, workspace} from 'vscode';
6-
76
export class Linter extends baseLinter.BaseLinter {
87
constructor(outputChannel: OutputChannel, workspaceRootPath: string) {
98
super("flake8", outputChannel, workspaceRootPath);

src/client/linters/pydocstyle.ts

+21-21
Original file line numberDiff line numberDiff line change
@@ -47,32 +47,33 @@ export class Linter extends baseLinter.BaseLinter {
4747
var diagnostics: ILintMessage[] = [];
4848
var baseFileName = path.basename(filePath);
4949

50-
//Remember, the first line of the response contains the file name and line number, the next line contains the error message
51-
//So we have two lines per message, hence we need to take lines in pairs
50+
// Remember, the first line of the response contains the file name and line number, the next line contains the error message
51+
// So we have two lines per message, hence we need to take lines in pairs
5252
var maxLines = this.pythonSettings.linting.maxNumberOfProblems * 2;
53-
//First line is almost always empty
54-
while (outputLines.length > 0 && outputLines[0].trim().length === 0) {
55-
outputLines.splice(0, 1);
53+
// First line is almost always empty
54+
let oldOutputLines = outputLines.filter(line => line.length > 0);
55+
outputLines = [];
56+
for (let counter = 0; counter < oldOutputLines.length / 2; counter++) {
57+
outputLines.push(oldOutputLines[2 * counter] + oldOutputLines[(2 * counter) + 1]);
5658
}
57-
outputLines = outputLines.filter((value, index) => index < maxLines);
59+
outputLines = outputLines.filter((value, index) => index < maxLines && value.indexOf(":") >= 0).map(line => line.substring(line.indexOf(":") + 1).trim());
5860

59-
//Iterate through the lines (skipping the messages)
60-
//So, just iterate the response in pairs
61-
for (var counter = 0; counter < outputLines.length; counter = counter + 2) {
61+
// Iterate through the lines (skipping the messages)
62+
// So, just iterate the response in pairs
63+
outputLines.forEach(line => {
6264
try {
63-
var line = outputLines[counter];
6465
if (line.trim().length === 0) {
65-
continue;
66+
return;
6667
}
67-
var messageLine = outputLines[counter + 1];
68-
var lineNumber = parseInt(line.substring(line.indexOf(baseFileName) + baseFileName.length + 1));
69-
var code = messageLine.substring(0, messageLine.indexOf(":")).trim();
70-
var message = messageLine.substring(messageLine.indexOf(":") + 1).trim();
68+
let lineNumber = parseInt(line.substring(0, line.indexOf(" ")));
69+
let part = line.substring(line.indexOf(":") + 1).trim();
70+
let code = part.substring(0, part.indexOf(":")).trim();
71+
let message = part.substring(part.indexOf(":") + 1).trim();
7172

72-
var sourceLine = txtDocumentLines[lineNumber - 1];
73-
var trmmedSourceLine = sourceLine.trim();
74-
var sourceStart = sourceLine.indexOf(trmmedSourceLine);
75-
var endCol = sourceStart + trmmedSourceLine.length;
73+
let sourceLine = txtDocumentLines[lineNumber - 1];
74+
let trmmedSourceLine = sourceLine.trim();
75+
let sourceStart = sourceLine.indexOf(trmmedSourceLine);
76+
let endCol = sourceStart + trmmedSourceLine.length;
7677

7778
diagnostics.push({
7879
code: code,
@@ -87,8 +88,7 @@ export class Linter extends baseLinter.BaseLinter {
8788
//Hmm, need to handle this later
8889
var y = "";
8990
}
90-
}
91-
91+
});
9292
resolve(diagnostics);
9393
}, error => {
9494
outputChannel.appendLine(`Linting with ${linterId} failed. If not installed please turn if off in settings.\n ${error}`);

src/test/extension.lint.test.ts

+44-3
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ let pythoFilesPath = path.join(__dirname, "..", "..", "src", "test", "pythonFile
2727

2828
let targetFlake8ConfigFile = path.join(__dirname, ".flake8");
2929
let targetPep8ConfigFile = path.join(__dirname, ".pep8");
30+
let targetPydocstyleConfigFile = path.join(__dirname, ".pydocstyle");
3031
let pylintFileToLintLines: string[] = [];
3132
let pyLintFileToLint = path.join(pythoFilesPath, "pylintSample.py");
3233
let targetPythonFileToLint = path.join(__dirname, "pythonFiles", "linting", path.basename(pyLintFileToLint));
3334
let sourceFlake8ConfigFile = path.join(__dirname, "..", "..", "src", "test", "pythonFiles", "linting", "pylintcfg", ".flake8");
3435
let sourcePep8ConfigFile = path.join(__dirname, "..", "..", "src", "test", "pythonFiles", "linting", "pylintcfg", ".pep8");
36+
let sourcePydocstyleConfigFile = path.join(__dirname, "..", "..", "src", "test", "pythonFiles", "linting", "pylintcfg", ".pydocstyle");
3537

3638
function deleteFile(file: string): Promise<any> {
3739
return new Promise<any>(resolve => {
@@ -48,7 +50,8 @@ suiteSetup(() => {
4850
pylintFileToLintLines = fs.readFileSync(pyLintFileToLint).toString("utf-8").split(/\r?\n/g);
4951
});
5052
suiteTeardown(done => {
51-
deleteFile(targetPythonFileToLint).then(done, done);
53+
// deleteFile(targetPythonFileToLint).then(done, done);
54+
done();
5255
});
5356

5457
suite("Linting", () => {
@@ -95,6 +98,29 @@ suite("Linting", () => {
9598
{ line: 80, column: 5, severity: baseLinter.LintMessageSeverity.Information, code: "E303", message: "too many blank lines (2)", possibleWord: "", provider: "", type: "" },
9699
{ line: 87, column: 24, severity: baseLinter.LintMessageSeverity.Information, code: "W292", message: "no newline at end of file", possibleWord: "", provider: "", type: "" }
97100
];
101+
let pydocstyleMessagseToBeReturned: baseLinter.ILintMessage[] = [
102+
{ "code": "D400", severity: baseLinter.LintMessageSeverity.Information, "message": "First line should end with a period (not 'e')", "column": 0, "line": 1, "type": "", "provider": "pydocstyle" },
103+
{ "code": "D400", severity: baseLinter.LintMessageSeverity.Information, "message": "First line should end with a period (not 't')", "column": 0, "line": 5, "type": "", "provider": "pydocstyle" },
104+
{ "code": "D102", severity: baseLinter.LintMessageSeverity.Information, "message": "Missing docstring in public method", "column": 4, "line": 8, "type": "", "provider": "pydocstyle" },
105+
{ "code": "D401", severity: baseLinter.LintMessageSeverity.Information, "message": "First line should be in imperative mood ('thi', not 'this')", "column": 4, "line": 11, "type": "", "provider": "pydocstyle" },
106+
{ "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" },
107+
{ "code": "D400", severity: baseLinter.LintMessageSeverity.Information, "message": "First line should end with a period (not 'e')", "column": 4, "line": 11, "type": "", "provider": "pydocstyle" },
108+
{ "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" },
109+
{ "code": "D400", severity: baseLinter.LintMessageSeverity.Information, "message": "First line should end with a period (not 't')", "column": 4, "line": 15, "type": "", "provider": "pydocstyle" },
110+
{ "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" },
111+
{ "code": "D400", severity: baseLinter.LintMessageSeverity.Information, "message": "First line should end with a period (not 'g')", "column": 4, "line": 21, "type": "", "provider": "pydocstyle" },
112+
{ "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" },
113+
{ "code": "D400", severity: baseLinter.LintMessageSeverity.Information, "message": "First line should end with a period (not 'g')", "column": 4, "line": 28, "type": "", "provider": "pydocstyle" },
114+
{ "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" },
115+
{ "code": "D400", severity: baseLinter.LintMessageSeverity.Information, "message": "First line should end with a period (not 'g')", "column": 4, "line": 38, "type": "", "provider": "pydocstyle" },
116+
{ "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" },
117+
{ "code": "D400", severity: baseLinter.LintMessageSeverity.Information, "message": "First line should end with a period (not 'g')", "column": 4, "line": 53, "type": "", "provider": "pydocstyle" },
118+
{ "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" },
119+
{ "code": "D400", severity: baseLinter.LintMessageSeverity.Information, "message": "First line should end with a period (not 'g')", "column": 4, "line": 68, "type": "", "provider": "pydocstyle" },
120+
{ "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" },
121+
{ "code": "D400", severity: baseLinter.LintMessageSeverity.Information, "message": "First line should end with a period (not 'g')", "column": 4, "line": 80, "type": "", "provider": "pydocstyle" }
122+
];
123+
98124
let filteredPylintMessagesToBeReturned: baseLinter.ILintMessage[] = [
99125
{ line: 26, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: "E1101", message: "Instance of 'Foo' has no 'blop' member", possibleWord: "", provider: "", type: "" },
100126
{ line: 36, column: 14, severity: baseLinter.LintMessageSeverity.Error, code: "E1101", message: "Instance of 'Foo' has no 'blip' member", possibleWord: "", provider: "", type: "" },
@@ -111,6 +137,9 @@ suite("Linting", () => {
111137
let filteredPep88MessagesToBeReturned: baseLinter.ILintMessage[] = [
112138
{ line: 87, column: 24, severity: baseLinter.LintMessageSeverity.Information, code: "W292", message: "no newline at end of file", possibleWord: "", provider: "", type: "" }
113139
];
140+
let fiteredPydocstyleMessagseToBeReturned: baseLinter.ILintMessage[] = [
141+
{ "code": "D102", severity: baseLinter.LintMessageSeverity.Information, "message": "Missing docstring in public method", "column": 4, "line": 8, "type": "", "provider": "pydocstyle" }
142+
];
114143

115144
setup(done => {
116145
initialize().then(() => {
@@ -124,7 +153,10 @@ suite("Linting", () => {
124153
});
125154

126155
teardown(done => {
127-
Promise.all([deleteFile(targetFlake8ConfigFile), deleteFile(targetPep8ConfigFile)]).then(() => done(), done);
156+
Promise.all([deleteFile(targetFlake8ConfigFile),
157+
deleteFile(targetPep8ConfigFile),
158+
deleteFile(targetPydocstyleConfigFile)
159+
]).then(() => done(), done);
128160
});
129161

130162
function testEnablingDisablingOfLinter(linter: baseLinter.BaseLinter, propertyName: string) {
@@ -156,7 +188,7 @@ suite("Linting", () => {
156188
messagesToBeReceived.forEach(msg => {
157189
let similarMessages = messages.filter(m => m.code === msg.code && m.column === msg.column &&
158190
m.line === msg.line && m.message === msg.message && m.severity === msg.severity);
159-
assert.equal(1, similarMessages.length, "Error not found, " + JSON.stringify(msg));
191+
assert.equal(true, similarMessages.length > 0, "Error not found, " + JSON.stringify(msg));
160192
});
161193
});
162194
}
@@ -172,6 +204,10 @@ suite("Linting", () => {
172204
let linter = new pep8.Linter(ch, __dirname);
173205
return testLinterMessages(linter, pyLintFileToLint, pylintFileToLintLines, pep8MessagesToBeReturned).then(done, done);
174206
});
207+
test("Pydocstyle", done => {
208+
let linter = new pydocstyle.Linter(ch, __dirname);
209+
return testLinterMessages(linter, pyLintFileToLint, pylintFileToLintLines, pydocstyleMessagseToBeReturned).then(done, done);
210+
});
175211
test("PyLint with config in root", done => {
176212
let rootDirContainingConfig = path.join(__dirname, "..", "..", "src", "test", "pythonFiles", "linting", "pylintcfg");
177213
let linter = new pyLint.Linter(ch, rootDirContainingConfig);
@@ -187,4 +223,9 @@ suite("Linting", () => {
187223
let linter = new pep8.Linter(ch, __dirname);
188224
return testLinterMessages(linter, targetPythonFileToLint, pylintFileToLintLines, filteredPep88MessagesToBeReturned).then(done, done);
189225
});
226+
test("Pydocstyle with config in root", done => {
227+
fs.copySync(sourcePydocstyleConfigFile, targetPydocstyleConfigFile);
228+
let linter = new pydocstyle.Linter(ch, __dirname);
229+
return testLinterMessages(linter, targetPythonFileToLint, pylintFileToLintLines, fiteredPydocstyleMessagseToBeReturned).then(done, done);
230+
});
190231
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[pydocstyle]
2+
ignore=D400,D401,D402,D403,D203

0 commit comments

Comments
 (0)