Skip to content

Commit 156e204

Browse files
committed
Improve plugin-related error messages
Address feedback by @gvanrossum in #3517.
1 parent 20135b4 commit 156e204

File tree

2 files changed

+47
-19
lines changed

2 files changed

+47
-19
lines changed

mypy/build.py

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import hashlib
1717
import json
1818
import os.path
19+
import re
1920
import sys
2021
import time
2122
from os.path import dirname, basename
@@ -343,23 +344,29 @@ def load_custom_plugins(default_plugin: Plugin, options: Options, errors: Errors
343344
back to default_plugin.
344345
"""
345346

347+
if not options.config_file:
348+
return default_plugin
349+
350+
line = find_config_file_line_number(options.config_file, 'plugins')
351+
if line == -1:
352+
line = 1 # We need to pick some line number that doesn't look too confusing
353+
346354
def plugin_error(message: str) -> None:
347-
errors.report(0, 0, message)
355+
errors.report(line, 0, message)
348356
errors.raise_error()
349357

358+
errors.set_file(options.config_file, None)
350359
custom_plugins = []
351360
for plugin_path in options.plugins:
352-
if options.config_file:
353-
# Plugin paths are relative to the config file location.
354-
plugin_path = os.path.join(os.path.dirname(options.config_file), plugin_path)
355-
errors.set_file(plugin_path, None)
361+
# Plugin paths are relative to the config file location.
362+
plugin_path = os.path.join(os.path.dirname(options.config_file), plugin_path)
356363

357364
if not os.path.isfile(plugin_path):
358-
plugin_error("Can't find plugin")
365+
plugin_error("Can't find plugin '{}'".format(plugin_path))
359366
plugin_dir = os.path.dirname(plugin_path)
360367
fnam = os.path.basename(plugin_path)
361368
if not fnam.endswith('.py'):
362-
plugin_error("Plugin must have .py extension")
369+
plugin_error("Plugin '{}' does not have a .py extension".format(fnam))
363370
module_name = fnam[:-3]
364371
import importlib
365372
sys.path.insert(0, plugin_dir)
@@ -372,19 +379,21 @@ def plugin_error(message: str) -> None:
372379
assert sys.path[0] == plugin_dir
373380
del sys.path[0]
374381
if not hasattr(m, 'plugin'):
375-
plugin_error('Plugin does not define entry point function "plugin"')
382+
plugin_error('Plugin "{}" does not define entry point function "plugin"'.format(
383+
module_name))
376384
try:
377385
plugin_type = getattr(m, 'plugin')(__version__)
378386
except Exception:
379387
print('Error calling the plugin(version) entry point of {}\n'.format(plugin_path))
380388
raise # Propagate to display traceback
381389
if not isinstance(plugin_type, type):
382390
plugin_error(
383-
'Type object expected as the return value of "plugin" (got {!r})'.format(
384-
plugin_type))
391+
'Type object expected as the return value of "{}.plugin" (got {!r})'.format(
392+
module_name, plugin_type))
385393
if not issubclass(plugin_type, Plugin):
386394
plugin_error(
387-
'Return value of "plugin" must be a subclass of "mypy.plugin.Plugin"')
395+
'Return value of "{}.plugin" must be a subclass of "mypy.plugin.Plugin"'.format(
396+
module_name))
388397
try:
389398
custom_plugins.append(plugin_type(options.python_version))
390399
except Exception:
@@ -397,6 +406,24 @@ def plugin_error(message: str) -> None:
397406
return ChainedPlugin(options.python_version, custom_plugins + [default_plugin])
398407

399408

409+
def find_config_file_line_number(path: str, setting_name: str) -> int:
410+
"""Return the approximate location of setting_name within mypy config file.
411+
412+
Return -1 if can't determine the line unambiguously.
413+
"""
414+
try:
415+
results = []
416+
with open(path) as f:
417+
for i, line in enumerate(f):
418+
if re.match(r'\s*{}\s*='.format(setting_name), line):
419+
results.append(i + 1)
420+
if len(results) == 1:
421+
return results[0]
422+
except OSError:
423+
pass
424+
return -1
425+
426+
400427
# TODO: Get rid of all_types. It's not used except for one log message.
401428
# Maybe we could instead publish a map from module ID to its type_map.
402429
class BuildManager:

test-data/unit/check-custom-plugin.test

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,35 +30,36 @@ plugins=<ROOT>/test-data/unit/plugins/fnplugin.py,
3030
[[mypy]
3131
plugins=missing.py
3232
[out]
33-
tmp/missing.py:0: error: Can't find plugin
33+
tmp/mypy.ini:2: error: Can't find plugin 'tmp/missing.py'
3434
--' (work around syntax highlighting)
3535

3636
[case testInvalidPluginExtension]
3737
# flags: --config-file tmp/mypy.ini
3838
[file mypy.ini]
3939
[[mypy]
40-
plugins=badext.pyi
41-
[file badext.pyi]
40+
plugins=dir/badext.pyi
41+
[file dir/badext.pyi]
4242
[out]
43-
tmp/badext.pyi:0: error: Plugin must have .py extension
43+
tmp/mypy.ini:2: error: Plugin 'badext.pyi' does not have a .py extension
4444

4545
[case testMissingPluginEntryPoint]
4646
# flags: --config-file tmp/mypy.ini
4747
[file mypy.ini]
4848
[[mypy]
49-
plugins=<ROOT>/test-data/unit/plugins/noentry.py
49+
plugins = <ROOT>/test-data/unit/plugins/noentry.py
5050
[out]
51-
<ROOT>/test-data/unit/plugins/noentry.py:0: error: Plugin does not define entry point function "plugin"
51+
tmp/mypy.ini:2: error: Plugin "noentry" does not define entry point function "plugin"
5252

5353
[case testInvalidPluginEntryPointReturnValue]
5454
# flags: --config-file tmp/mypy.ini
5555
def f(): pass
5656
f()
5757
[file mypy.ini]
5858
[[mypy]
59+
5960
plugins=<ROOT>/test-data/unit/plugins/badreturn.py
6061
[out]
61-
<ROOT>/test-data/unit/plugins/badreturn.py:0: error: Type object expected as the return value of "plugin" (got None)
62+
tmp/mypy.ini:3: error: Type object expected as the return value of "badreturn.plugin" (got None)
6263

6364
[case testInvalidPluginEntryPointReturnValue2]
6465
# flags: --config-file tmp/mypy.ini
@@ -68,4 +69,4 @@ f()
6869
[[mypy]
6970
plugins=<ROOT>/test-data/unit/plugins/badreturn2.py
7071
[out]
71-
<ROOT>/test-data/unit/plugins/badreturn2.py:0: error: Return value of "plugin" must be a subclass of "mypy.plugin.Plugin"
72+
tmp/mypy.ini:2: error: Return value of "badreturn2.plugin" must be a subclass of "mypy.plugin.Plugin"

0 commit comments

Comments
 (0)