Skip to content
This repository was archived by the owner on Aug 7, 2023. It is now read-only.

Commit e5e1265

Browse files
authored
Merge pull request #205 from AtomLinter/arcanemagus/linter-v2
Linter v2
2 parents 2115ed2 + 648e478 commit e5e1265

File tree

3 files changed

+193
-129
lines changed

3 files changed

+193
-129
lines changed

lib/main.js

+152-115
Original file line numberDiff line numberDiff line change
@@ -10,139 +10,176 @@ import { CompositeDisposable } from 'atom';
1010

1111
const lazyReq = require('lazy-req')(require);
1212

13-
const { basename, delimiter, dirname } = lazyReq('path')(
14-
'basename', 'delimiter', 'dirname',
15-
);
16-
const { exec, parse, generateRange, tempFile } = lazyReq('atom-linter')(
17-
'exec', 'parse', 'generateRange', 'tempFile',
18-
);
13+
const { delimiter, dirname } = lazyReq('path')('delimiter', 'dirname');
14+
const { exec, generateRange } = lazyReq('atom-linter')('exec', 'generateRange');
1915
const os = lazyReq('os');
2016

2117
// Some local variables
22-
let subscriptions;
2318
const errorWhitelist = [
2419
/^No config file found, using default configuration$/,
2520
];
26-
const lineRegex = '(?<line>\\d+),(?<col>\\d+),(?<type>\\w+),(\\w\\d+):(?<message>.*)\\r?(\\n|$)';
27-
28-
// Settings
29-
let executable;
30-
let rcFile;
31-
let messageFormat;
32-
let pythonPath;
33-
let workingDirectory;
34-
35-
export function activate() {
36-
require('atom-package-deps').install('linter-pylint');
37-
38-
subscriptions = new CompositeDisposable();
39-
40-
// FIXME: This should be executablePath, saved for a major version bump
41-
subscriptions.add(atom.config.observe('linter-pylint.executable', (value) => {
42-
executable = value;
43-
}));
44-
subscriptions.add(atom.config.observe('linter-pylint.rcFile', (value) => {
45-
rcFile = value;
46-
}));
47-
subscriptions.add(atom.config.observe('linter-pylint.messageFormat', (value) => {
48-
messageFormat = value;
49-
}));
50-
subscriptions.add(atom.config.observe('linter-pylint.pythonPath', (value) => {
51-
pythonPath = value;
52-
}));
53-
subscriptions.add(atom.config.observe('linter-pylint.workingDirectory', (value) => {
54-
workingDirectory = value.replace(delimiter, '');
55-
}));
56-
}
57-
58-
export function deactivate() {
59-
subscriptions.dispose();
60-
}
61-
62-
function getProjectDir(filePath) {
21+
22+
const getProjectDir = (filePath) => {
6323
const atomProject = atom.project.relativizePath(filePath)[0];
6424
if (atomProject === null) {
65-
// Default project dirextory to file directory if path cannot be determined
25+
// Default project to file directory if project path cannot be determined
6626
return dirname(filePath);
6727
}
6828
return atomProject;
69-
}
29+
};
7030

71-
function filterWhitelistedErrors(stderr) {
31+
const filterWhitelistedErrors = (stderr) => {
7232
// Split the input and remove blank lines
7333
const lines = stderr.split(os().EOL).filter(line => !!line);
7434
const filteredLines = lines.filter(line =>
7535
// Only keep the line if it is not ignored
7636
!errorWhitelist.some(errorRegex => errorRegex.test(line)),
7737
);
7838
return filteredLines.join(os().EOL);
79-
}
80-
81-
export function provideLinter() {
82-
return {
83-
name: 'Pylint',
84-
grammarScopes: ['source.python', 'source.python.django'],
85-
scope: 'file',
86-
lintOnFly: true,
87-
lint: (editor) => {
88-
const filePath = editor.getPath();
89-
const fileDir = dirname(filePath);
90-
const fileText = editor.getText();
91-
const projectDir = getProjectDir(filePath);
92-
const cwd = workingDirectory.replace(/%f/g, fileDir).replace(/%p/g, projectDir);
93-
const execPath = executable.replace(/%p/g, projectDir);
94-
let format = messageFormat;
95-
const patterns = {
96-
'%m': 'msg',
97-
'%i': 'msg_id',
98-
'%s': 'symbol',
99-
};
100-
Object.keys(patterns).forEach((pattern) => {
101-
format = format.replace(new RegExp(pattern, 'g'), `{${patterns[pattern]}}`);
102-
});
103-
const env = Object.create(process.env, {
104-
PYTHONPATH: {
105-
value: [
106-
process.env.PYTHONPATH, fileDir, projectDir,
107-
pythonPath.replace(/%f/g, fileDir).replace(/%p/g, projectDir),
108-
].filter(x => !!x).join(delimiter),
109-
enumerable: true,
110-
},
111-
LANG: { value: 'en_US.UTF-8', enumerable: true },
112-
});
113-
114-
const args = [
115-
`--msg-template='{line},{column},{category},{msg_id}:${format}'`,
116-
'--reports=n',
117-
'--output-format=text',
118-
];
119-
if (rcFile) {
120-
args.push(`--rcfile=${rcFile.replace(/%p/g, projectDir).replace(/%f/g, fileDir)}`);
39+
};
40+
41+
const fixPathString = (pathString, fileDir, projectDir) => {
42+
const string = pathString;
43+
const fRstring = string.replace(/%f/g, fileDir);
44+
const pRstring = fRstring.replace(/%p/g, projectDir);
45+
return pRstring;
46+
};
47+
48+
const determineSeverity = (severity) => {
49+
switch (severity) {
50+
case 'error':
51+
case 'warning':
52+
case 'info':
53+
return severity;
54+
case 'convention':
55+
return 'info';
56+
default:
57+
return 'warning';
58+
}
59+
};
60+
61+
export default {
62+
activate() {
63+
require('atom-package-deps').install('linter-pylint');
64+
65+
this.subscriptions = new CompositeDisposable();
66+
67+
// FIXME: Remove backwards compatibility in a future minor version
68+
const oldPath = atom.config.get('linter-pylint.executable');
69+
if (oldPath !== undefined) {
70+
atom.config.unset('linter-pylint.executable');
71+
if (oldPath !== 'pylint') {
72+
// If the old config wasn't set to the default migrate it over
73+
atom.config.set('linter-pylint.executablePath', oldPath);
12174
}
122-
return tempFile(basename(filePath), fileText, (tmpFileName) => {
123-
args.push(tmpFileName);
124-
return exec(execPath, args, { env, cwd, stream: 'both' }).then((data) => {
125-
if (editor.getText() !== fileText) {
126-
// Editor text was modified since the lint was triggered, tell Linter not to update
127-
return null;
128-
}
129-
const filteredErrors = filterWhitelistedErrors(data.stderr);
130-
if (filteredErrors) {
131-
throw new Error(filteredErrors);
132-
}
133-
return parse(data.stdout, lineRegex, { filePath })
134-
.filter(issue => issue.type !== 'info')
135-
.map((issue) => {
136-
const [[lineStart, colStart], [lineEnd, colEnd]] = issue.range;
137-
if (lineStart === lineEnd && (colStart <= colEnd || colEnd <= 0)) {
138-
Object.assign(issue, {
139-
range: generateRange(editor, lineStart, colStart),
140-
});
141-
}
142-
return issue;
143-
});
75+
}
76+
77+
this.subscriptions.add(atom.config.observe('linter-pylint.executablePath', (value) => {
78+
this.executablePath = value;
79+
}));
80+
this.subscriptions.add(atom.config.observe('linter-pylint.rcFile', (value) => {
81+
this.rcFile = value;
82+
}));
83+
this.subscriptions.add(atom.config.observe('linter-pylint.messageFormat', (value) => {
84+
this.messageFormat = value;
85+
}));
86+
this.subscriptions.add(atom.config.observe('linter-pylint.pythonPath', (value) => {
87+
this.pythonPath = value;
88+
}));
89+
this.subscriptions.add(atom.config.observe('linter-pylint.workingDirectory', (value) => {
90+
this.workingDirectory = value.replace(delimiter, '');
91+
}));
92+
this.subscriptions.add(atom.config.observe('linter-pylint.disableTimeout', (value) => {
93+
this.disableTimeout = value;
94+
}));
95+
},
96+
97+
deactivate() {
98+
this.subscriptions.dispose();
99+
},
100+
101+
provideLinter() {
102+
return {
103+
name: 'Pylint',
104+
scope: 'file',
105+
lintsOnChange: false,
106+
grammarScopes: ['source.python', 'source.python.django'],
107+
lint: async (editor) => {
108+
const filePath = editor.getPath();
109+
const fileDir = dirname(filePath);
110+
const fileText = editor.getText();
111+
const projectDir = getProjectDir(filePath);
112+
const cwd = fixPathString(this.workingDirectory, fileDir, projectDir);
113+
const execPath = fixPathString(this.executablePath, '', projectDir);
114+
let format = this.messageFormat;
115+
const patterns = {
116+
'%m': 'msg',
117+
'%i': 'msg_id',
118+
'%s': 'symbol',
119+
};
120+
Object.keys(patterns).forEach((pattern) => {
121+
format = format.replace(new RegExp(pattern, 'g'), `{${patterns[pattern]}}`);
144122
});
145-
});
146-
},
147-
};
148-
}
123+
const env = Object.create(process.env, {
124+
PYTHONPATH: {
125+
value: [
126+
process.env.PYTHONPATH, fileDir, projectDir,
127+
fixPathString(this.pythonPath, fileDir, projectDir),
128+
].filter(x => !!x).join(delimiter),
129+
enumerable: true,
130+
},
131+
LANG: { value: 'en_US.UTF-8', enumerable: true },
132+
});
133+
134+
const args = [
135+
`--msg-template='{line},{column},{category},{msg_id}:${format}'`,
136+
'--reports=n',
137+
'--output-format=text',
138+
];
139+
if (this.rcFile !== '') {
140+
args.push(`--rcfile=${fixPathString(this.rcFile, fileDir, projectDir)}`);
141+
}
142+
args.push(filePath);
143+
144+
const execOpts = { env, cwd, stream: 'both' };
145+
if (this.disableTimeout) {
146+
execOpts.timeout = Infinity;
147+
}
148+
149+
const data = await exec(execPath, args, execOpts);
150+
151+
if (editor.getText() !== fileText) {
152+
// Editor text was modified since the lint was triggered, tell Linter not to update
153+
return null;
154+
}
155+
156+
const filteredErrors = filterWhitelistedErrors(data.stderr);
157+
if (filteredErrors) {
158+
// pylint threw an error we aren't ignoring!
159+
throw new Error(filteredErrors);
160+
}
161+
162+
const lineRegex = /(\d+),(\d+),(\w+),(\w\d+):(.*)\r?(?:\n|$)/g;
163+
const toReturn = [];
164+
165+
let match = lineRegex.exec(data.stdout);
166+
while (match !== null) {
167+
const line = Number.parseInt(match[1], 10) - 1;
168+
const column = Number.parseInt(match[2], 10);
169+
const range = generateRange(editor, line, column);
170+
const message = {
171+
severity: determineSeverity(match[3]),
172+
excerpt: match[5],
173+
location: { file: filePath, range },
174+
url: `http://pylint-messages.wikidot.com/messages:${match[4]}`,
175+
};
176+
177+
toReturn.push(message);
178+
match = lineRegex.exec(data.stdout);
179+
}
180+
181+
return toReturn;
182+
},
183+
};
184+
},
185+
};

package.json

+21-9
Original file line numberDiff line numberDiff line change
@@ -14,42 +14,54 @@
1414
"atom": ">=1.4.0 <2.0.0"
1515
},
1616
"configSchema": {
17-
"executable": {
17+
"executablePath": {
1818
"type": "string",
1919
"default": "pylint",
20-
"description": "Command or path to executable. Use %p for current project directory (no trailing /)."
20+
"description": "Command or full path to pylint. Use %p for current project directory (no trailing /).",
21+
"order": 1
2122
},
2223
"pythonPath": {
2324
"type": "string",
2425
"default": "",
25-
"description": "Paths to be added to $PYTHONPATH. Use %p for current project directory or %f for the directory of the current file."
26+
"description": "Paths to be added to $PYTHONPATH. Use %p for current project directory or %f for the directory of the current file.",
27+
"order": 1
2628
},
2729
"rcFile": {
2830
"type": "string",
2931
"default": "",
30-
"description": "Path to pylintrc file. Use %p for the current project directory or %f for the directory of the current file."
32+
"description": "Path to pylintrc file. Use %p for the current project directory or %f for the directory of the current file.",
33+
"order": 2
3134
},
3235
"workingDirectory": {
3336
"type": "string",
3437
"default": "%p",
35-
"description": "Directory pylint is run from. Use %p for the current project directory or %f for the directory of the current file."
38+
"description": "Directory pylint is run from. Use %p for the current project directory or %f for the directory of the current file.",
39+
"order": 2
3640
},
3741
"messageFormat": {
3842
"type": "string",
3943
"default": "%i %m",
40-
"description": "Format for Pylint messages where %m is the message, %i is the numeric mesasge ID (e.g. W0613) and %s is the human-readable message ID (e.g. unused-argument)."
44+
"description": "Format for Pylint messages where %m is the message, %i is the numeric mesasge ID (e.g. W0613) and %s is the human-readable message ID (e.g. unused-argument).",
45+
"order": 2
46+
},
47+
"disableTimeout": {
48+
"title": "Disable Execution Timeout",
49+
"type": "boolean",
50+
"default": false,
51+
"description": "By default processes running longer than 10 seconds will be automatically terminated. Enable this option if you are getting messages about process execution timing out.",
52+
"order": 3
4153
}
4254
},
4355
"providedServices": {
4456
"linter": {
4557
"versions": {
46-
"1.0.0": "provideLinter"
58+
"2.0.0": "provideLinter"
4759
}
4860
}
4961
},
5062
"dependencies": {
5163
"atom-linter": "^9.0.0",
52-
"atom-package-deps": "^4.0.1",
64+
"atom-package-deps": "^4.5.0",
5365
"lazy-req": "^2.0.0"
5466
},
5567
"devDependencies": {
@@ -58,7 +70,7 @@
5870
"eslint-plugin-import": "^2.2.0"
5971
},
6072
"package-deps": [
61-
"linter"
73+
"linter:2.0.0"
6274
],
6375
"eslintConfig": {
6476
"extends": "airbnb-base",

0 commit comments

Comments
 (0)